From f6687d48f45aff65b7fa3062b69ee71b5e3f1558 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 26 Sep 2016 14:51:39 +0900
Subject: [PATCH 6/8] Support for SCRAM-SHA-256 authentication (RFC 5802 and
 7677)

SHA-256 is used. This commit introduces the basic SASL communication
protocol plugged in on top of the existing infrastructure. Note that
this feature does not add any grammar extension to CREATE and ALTER
ROLE, which is left for a future patch. SCRAM authentication can
be enabled via password_encryption that gains a new value: 'scram'.

Support for channel binding, aka SCRAM-SHA-256-PLUS is left for
later, but there is the necessary infrastructure to support it.
---
 contrib/passwordcheck/passwordcheck.c         |  19 +-
 doc/src/sgml/catalogs.sgml                    |  19 +-
 doc/src/sgml/config.sgml                      |   9 +-
 doc/src/sgml/protocol.sgml                    | 147 ++++-
 doc/src/sgml/ref/create_role.sgml             |  14 +-
 src/backend/commands/user.c                   |  18 +-
 src/backend/libpq/Makefile                    |   2 +-
 src/backend/libpq/auth-scram.c                | 764 ++++++++++++++++++++++++++
 src/backend/libpq/auth.c                      | 132 +++++
 src/backend/libpq/hba.c                       |  13 +
 src/backend/libpq/pg_hba.conf.sample          |   8 +-
 src/backend/postmaster/postmaster.c           |   1 +
 src/backend/utils/misc/guc.c                  |   1 +
 src/backend/utils/misc/postgresql.conf.sample |   2 +-
 src/common/Makefile                           |   2 +-
 src/common/scram-common.c                     | 196 +++++++
 src/include/commands/user.h                   |   3 +-
 src/include/common/scram-common.h             |  55 ++
 src/include/libpq/auth.h                      |   5 +
 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/interfaces/libpq/.gitignore               |   4 +
 src/interfaces/libpq/Makefile                 |  11 +-
 src/interfaces/libpq/fe-auth-scram.c          | 605 ++++++++++++++++++++
 src/interfaces/libpq/fe-auth.c                | 106 ++++
 src/interfaces/libpq/fe-auth.h                |   8 +
 src/interfaces/libpq/fe-connect.c             |  52 ++
 src/interfaces/libpq/libpq-int.h              |   5 +
 30 files changed, 2190 insertions(+), 44 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 a0db89b..faf7208 100644
--- a/contrib/passwordcheck/passwordcheck.c
+++ b/contrib/passwordcheck/passwordcheck.c
@@ -22,6 +22,7 @@
 
 #include "commands/user.h"
 #include "common/md5.h"
+#include "libpq/scram.h"
 #include "fmgr.h"
 
 PG_MODULE_MAGIC;
@@ -57,7 +58,7 @@ check_password(const char *username,
 {
 	int			namelen = strlen(username);
 	int			pwdlen = strlen(password);
-	char		encrypted[MD5_PASSWD_LEN + 1];
+	char	   *encrypted;
 	int			i;
 	bool		pwd_has_letter,
 				pwd_has_nonletter;
@@ -65,6 +66,7 @@ check_password(const char *username,
 	switch (password_type)
 	{
 		case PASSWORD_TYPE_MD5:
+		case PASSWORD_TYPE_SCRAM:
 
 			/*
 			 * Unfortunately we cannot perform exhaustive checks on encrypted
@@ -74,12 +76,23 @@ check_password(const char *username,
 			 *
 			 * We only check for username = password.
 			 */
-			if (!pg_md5_encrypt(username, username, namelen, encrypted))
-				elog(ERROR, "password encryption failed");
+			if (password_type == PASSWORD_TYPE_MD5)
+			{
+				encrypted = palloc(MD5_PASSWD_LEN + 1);
+				if (pg_md5_encrypt(username, username, namelen, encrypted))
+					elog(ERROR, "password encryption failed");
+			}
+			else if (password_type == PASSWORD_TYPE_SCRAM)
+			{
+				encrypted = scram_build_verifier(username, password, 0);
+			}
+			else
+				Assert(0); /* should not happen */
 			if (strcmp(password, encrypted) == 0)
 				ereport(ERROR,
 						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
 						 errmsg("password must not contain user name")));
+			pfree(encrypted);
 			break;
 
 		case PASSWORD_TYPE_PLAINTEXT:
diff --git a/doc/src/sgml/catalogs.sgml b/doc/src/sgml/catalogs.sgml
index 29738b0..6b28bef 100644
--- a/doc/src/sgml/catalogs.sgml
+++ b/doc/src/sgml/catalogs.sgml
@@ -1310,13 +1310,18 @@
       <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.
+       is encrypted with MD5, 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</>.  If the password is encrypted with
+       SCRAM-SHA-256, it is built with 4 fields separated by a colon. The
+       first field is a salt encoded in base-64.  The second field is the
+       number of iterations used to generate the password.  The third field
+       is a stored key, encoded in hexadecimal.  The fourth field is a
+       server key encoded in hexadecimal.  A password that does not follow
+       any of those formats is assumed to be unencrypted.
       </entry>
      </row>
 
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 02e9bb4..c9f7a80 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1181,7 +1181,8 @@ include_dir 'conf.d'
        <para>
         A value set to <literal>on</> or <literal>md5</> corresponds to a 
         MD5-encrypted password, <literal>off</> or <literal>plain</>
-        corresponds to an unencrypted password.
+        corresponds to an unencrypted password.  Setting this parameter to
+        <literal>scram</> will encrypt the password with SCRAM-SHA-256.
        </para>
        
        <para>
@@ -1259,8 +1260,10 @@ include_dir 'conf.d'
         Authentication checks are always done with the server's user name
         so authentication methods must be configured for the
         server's user name, not the client's.  Because
-        <literal>md5</> uses the user name as salt on both the
-        client and server, <literal>md5</> cannot be used with
+        <literal>md5</>uses the user name as salt on both the
+        client and server, and <literal>scram</> uses the user name as
+        a portion of the salt used on both the client and server,
+        <literal>md5</> and <literal>scram</> cannot be used with
         <varname>db_user_namespace</>.
        </para>
 
diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 68b0941..8af888d 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>
 
@@ -2578,6 +2607,114 @@ 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>
@@ -4340,7 +4477,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/doc/src/sgml/ref/create_role.sgml b/doc/src/sgml/ref/create_role.sgml
index 38cd4c8..93f0763 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -228,16 +228,16 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
         encrypted in the system catalogs.  (If neither is specified,
         the default behavior is determined by the configuration
         parameter <xref linkend="guc-password-encryption">.)  If the
-        presented password string is already in MD5-encrypted format,
-        then it is stored encrypted as-is, regardless of whether
-        <literal>ENCRYPTED</> or <literal>UNENCRYPTED</> is specified
-        (since the system cannot decrypt the specified encrypted
-        password string).  This allows reloading of encrypted
-        passwords during dump/restore.
+        presented password string is already in MD5-encrypted or
+        SCRAM-encrypted format, then it is stored encrypted as-is,
+        regardless of whether <literal>ENCRYPTED</> or <literal>UNENCRYPTED</>
+        is specified (since the system cannot decrypt the specified encrypted
+        password string).  This allows reloading of encrypted passwords
+        during dump/restore.
        </para>
 
        <para>
-        Note that older clients might lack support for the MD5
+        Note that older clients might lack support for the MD5 or SCRAM
         authentication mechanism that is needed to work with passwords
         that are stored encrypted.
        </para>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 2e89f1f..c76d273 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 "common/md5.h"
+#include "libpq/scram.h"
 #include "miscadmin.h"
 #include "storage/lmgr.h"
 #include "utils/acl.h"
@@ -69,9 +70,9 @@ have_createrole_privilege(void)
 /*
  * Encrypt a password if necessary for insertion in pg_authid.
  *
- * If a password is found as already MD5-encrypted, no error is raised
- * to ease the dump and reload of such data. Returns a palloc'ed string
- * holding the encrypted password.
+ * If a password is found as already MD5-encrypted or SCRAM-encrypted, no
+ * error is raised to ease the dump and reload of such data. Returns a
+ * palloc'ed string holding the encrypted password.
  */
 static char *
 encrypt_password(char *password, char *rolname, int passwd_type)
@@ -81,11 +82,11 @@ encrypt_password(char *password, char *rolname, int passwd_type)
 	Assert(password != NULL);
 
 	/*
-	 * If a password is already identified as MD5-encrypted, it is used
-	 * as such.  If the password given is not encrypted, adapt it depending
-	 * on the type wanted by the caller of this routine.
+	 * A password already identified as a SCRAM-encrypted or MD5-encrypted
+	 * those are used as such.  If the password given is not encrypted,
+	 * adapt it depending on the type wanted by the caller of this routine.
 	 */
-	if (isMD5(password))
+	if (isMD5(password) || is_scram_verifier(password))
 		res = pstrdup(password);
 	else
 	{
@@ -101,6 +102,9 @@ encrypt_password(char *password, char *rolname, int passwd_type)
 									res))
 					elog(ERROR, "password encryption failed");
 				break;
+			case PASSWORD_TYPE_SCRAM:
+				res = scram_build_verifier(rolname, password, 0);
+				break;
 			default:
 				Assert(0);	/* should not come here */
 		}
diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile
index 1bdd8ad..7fa2b02 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 ifaddr.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..1513b21
--- /dev/null
+++ b/src/backend/libpq/auth-scram.c
@@ -0,0 +1,764 @@
+/*-------------------------------------------------------------------------
+ *
+ * auth-scram.c
+ *	  Server-side implementation of the SASL SCRAM mechanism.
+ *
+ * See the following RFCs 5802 and RFC 7666 for more details:
+ * - RFC 5802: https://tools.ietf.org/html/rfc5802
+ * - RFC 7677: https://tools.ietf.org/html/rfc7677
+ *
+ * Here are 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 yet.
+ *
+ * Portions Copyright (c) 1996-2016, 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_utils.h"
+#include "common/scram-common.h"
+#include "common/sha.h"
+#include "libpq/auth.h"
+#include "libpq/crypt.h"
+#include "libpq/scram.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+
+typedef enum
+{
+	INIT,			/* receive client-first-message */
+	SALT_SENT,		/* receive client-final-message */
+	FINISHED		/* all done */
+} scram_exchange_state;
+
+typedef struct
+{
+	scram_exchange_state state;
+
+	/* Username from startup packet */
+	const char *username;
+
+	/* Fields from the password verifier, stored in pg_authid. */
+	char	   *salt;			/* base64-encoded */
+	int			iterations;
+	uint8		StoredKey[SCRAM_KEY_LEN];
+	uint8		ServerKey[SCRAM_KEY_LEN];
+
+	/* Fields from the first message from client */
+	char	   *client_first_message_bare;
+	char	   *client_username;
+	char	   *client_authzid;
+	char	   *client_nonce;
+
+	/* Fields from the last message from client */
+	char	   *client_final_message_without_proof;
+	char	   *client_final_nonce;
+	char		ClientProof[SCRAM_KEY_LEN];
+
+	/* Server-side status fields */
+	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);
+
+/*
+ * 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");
+
+	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;
+
+	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;
+}
+
+/*
+ * Read the value in a given SASL exchange message for given attribute.
+ */
+static char *
+read_attr_value(char **input, char attr)
+{
+	char	   *begin = *input;
+	char	   *end;
+
+	if (*begin != attr)
+		ereport(COMMERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("malformed SCRAM message (attr %c expected)", attr)));
+	begin++;
+
+	if (*begin != '=')
+		ereport(COMMERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("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;
+}
+
+/*
+ * Read the next attribute and value in a SASL exchange message.
+ */
+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')))
+		ereport(COMMERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("malformed SCRAM message (invalid attribute char)")));
+	if (attr_p)
+		*attr_p = attr;
+	begin++;
+
+	if (*begin != '=')
+		ereport(COMMERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("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;
+}
+
+/*
+ * Read and parse the first message from client in the context of a SASL
+ * authentication exchange message.
+ */
+static void
+read_client_first_message(scram_state *state, char *input)
+{
+	input = pstrdup(input);
+
+	/*----------
+	 * According to RFC 5820, the syntax for the client's first
+	 * message is:
+	 *
+	 * saslname        = 1*(value-safe-char / "=2C" / "=3D")
+	 *                   ;; Conforms to <value>.
+	 *
+	 * authzid         = "a=" saslname
+	 *                   ;; Protocol specific.
+	 *
+	 * username        = "n=" saslname
+	 *                   ;; Usernames are prepared using SASLprep.
+	 *
+	 * reserved-mext   = "m=" 1*(value-char)
+	 *                   ;; Reserved for signaling mandatory extensions.
+	 *                   ;; The exact syntax will be defined in
+	 *                   ;; the future.
+	 *
+	 * 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
+	 *
+	 * Note: Contrary to the spec, the username in the SASL exchange
+	 * is always sent as an empty string, because we get the username
+	 * from the startup packet.
+	 *
+	 * 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(FATAL,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("channel binding not supported")));
+		default:
+			ereport(COMMERROR,
+					(errcode(ERRCODE_PROTOCOL_VIOLATION),
+					 errmsg("malformed SCRAM request")));
+	}
+	if (*input != ',')
+		ereport(FATAL,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("malformed SCRAM request (',' expected after gs2-cbind-flag)")));
+	input++;
+
+	/* read optional authzid (authorization identity) */
+	if (*input != ',')
+	{
+		state->client_authzid = read_attr_value(&input, 'a');
+		ereport(FATAL,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("SCRAM authorization identity not supported")));
+	}
+	else
+		input++;
+
+	/*
+	 * We're now at the beginning of 'client_first_message_bare'. Save it,
+	 * because it's needed later in the verification of client-proof.
+	 */
+	state->client_first_message_bare = pstrdup(input);
+
+	/*
+	 * Any mandatory extensions would go here. We don't support any, so
+	 * throw an error if we see one.
+	 */
+	if (*input == 'm')
+		ereport(FATAL,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("SCRAM mandatory extension not supported")));
+
+	/* read username and nonce */
+	/* FIXME: Should we check that the username is empty? */
+	state->client_username = read_attr_value(&input, 'n');
+	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! */
+}
+
+/*
+ * Verify the final nonce contained in the last message received from
+ * client in an exchange.
+ */
+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;
+}
+
+/*
+ * Verify the client proof contained in the last message received from
+ * client in an exchange.
+ */
+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, SCRAM_KEY_LEN);
+	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);
+
+	/* 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);
+
+	if (memcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0)
+		return false;
+
+	return true;
+}
+
+/*
+ * Build the first server-side message sent to the client in a SASL
+ * communication exchange.
+ */
+static char *
+build_server_first_message(scram_state *state)
+{
+	char		nonce[SCRAM_NONCE_LEN];
+	int			encoded_len;
+
+	/*----------
+	 * According to RFC 5820, the syntax of the server-first-message is:
+	 *
+	 * 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
+	 *----------
+	 */
+
+	/* Use the nonce that was pre-generated before forking */
+	/* FIXME: use RAND_bytes() here if built with OpenSSL? */
+	memcpy(nonce, MyProcPort->scramNonce, 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;
+}
+
+/*
+ * Read and parse the final message received from client.
+ */
+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);
+
+	/*----------
+	 * According to RFC 5820, the syntax of the client-final-message is:
+	 *
+	 * 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
+	 *----------
+	 */
+
+	/*
+	 * Since we don't support channel binding, nor authorization identity,
+	 * cbind-input is always "n,,". That's "biws" when base64-encoded.
+	 */
+	channel_binding = read_attr_value(&p, 'c');
+	if (strcmp(channel_binding, "biws") != 0)
+		ereport(COMMERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("invalid channel binding input in SCRAM message")));
+	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)
+		ereport(COMMERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("malformed SCRAM message (invalid ClientProof)")));
+	memcpy(state->ClientProof, client_proof, SCRAM_KEY_LEN);
+	pfree(client_proof);
+
+	if (*p != '\0')
+		ereport(COMMERROR,
+				(errcode(ERRCODE_PROTOCOL_VIOLATION),
+				 errmsg("malformed SCRAM message (unexpected attribute %c at end of message)", 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 */
+}
+
+/*
+ * Build the final server-side message of an exchange.
+ */
+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, SCRAM_KEY_LEN);
+	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';
+
+	/*----------
+	 * According to RFC 5820, the syntax for the server-final message is:
+	 *
+	 * 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);
+}
+
+/*
+ * Functions for serializing/deserializing SCRAM password verifiers in
+ * pg_authid.
+ *
+ * The password stored in pg_authid consists of the salt, iteration count,
+ * StoredKey and ServerKey.
+ */
+
+/*
+ * Construct a verifier string for SCRAM, stored in pg_authid.rolpassword.
+ *
+ * If iterations is 0, default number of iterations is used. The result is
+ * palloc'd, so caller is responsible for freeing it.
+ */
+char *
+scram_build_verifier(const char *username, const 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;
+	int			i;
+
+	if (iterations <= 0)
+		iterations = SCRAM_ITERATIONS_DEFAULT;
+
+	/* FIXME: use RAND_bytes() here if built with OpenSSL? */
+	for (i = 0; i < SCRAM_SALT_LEN; i++)
+		salt[i] = random() % 255 + 1;
+
+	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;
+}
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index d907e6b..d0bfadb 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -27,9 +27,11 @@
 #include "libpq/crypt.h"
 #include "libpq/libpq.h"
 #include "libpq/pqformat.h"
+#include "libpq/scram.h"
 #include "miscadmin.h"
 #include "replication/walsender.h"
 #include "storage/ipc.h"
+#include "utils/timestamp.h"
 
 
 /*----------------------------------------------------------------
@@ -201,6 +203,12 @@ static int	CheckRADIUSAuth(Port *port);
 
 
 /*----------------------------------------------------------------
+ * SASL authentication
+ *----------------------------------------------------------------
+ */
+static int CheckSASLAuth(Port *port, char **logdetail);
+
+/*----------------------------------------------------------------
  * Global authentication functions
  *----------------------------------------------------------------
  */
@@ -262,6 +270,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;
@@ -542,6 +551,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, "");
@@ -716,6 +729,125 @@ recv_and_check_password_packet(Port *port, char **logdetail)
 	return result;
 }
 
+/*----------------------------------------------------------------
+ * SASL authentication system
+ *----------------------------------------------------------------
+ */
+static int
+CheckSASLAuth(Port *port, char **logdetail)
+{
+	int			retval = STATUS_ERROR;
+	int			mtype;
+	StringInfoData buf;
+	void	   *scram_opaq;
+	TimestampTz vuntil = 0;
+	char	   *output = NULL;
+	int			outputlen = 0;
+	int			result;
+	char	   *passwd;
+	bool		vuntil_null;
+
+	/*
+	 * 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.)
+	 */
+	if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+		ereport(FATAL,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("SASL authentication is not supported in protocol version 2")));
+
+	/* fetch details about role needed for password checks */
+	if (get_role_details(port->user_name, &passwd, &vuntil, &vuntil_null,
+						 logdetail) != STATUS_OK)
+		return STATUS_ERROR;
+
+	if (!is_scram_verifier(passwd))
+	{
+		*logdetail = psprintf(_("User \"%s\" does not have a SCRAM password."),
+							  port->user_name);
+		return STATUS_ERROR;
+	}
+
+	sendAuthRequest(port, AUTH_REQ_SASL, SCRAM_SHA256_NAME,
+					strlen(SCRAM_SHA256_NAME) + 1);
+
+	scram_opaq = scram_init(port->user_name, passwd);
+
+	/*
+	 * Loop through SASL message exchange. This exchange can consist of
+	 * multiple messages sent in both directions. First message is always from
+	 * the client. All messages from client to server are password packets
+	 * (type 'p').
+	 */
+	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);
+
+
+	if (result != SASL_EXCHANGE_SUCCESS)
+	{
+		*logdetail = psprintf(_("SASL exchange failed for user \"%s\"."),
+							  port->user_name);
+		return STATUS_ERROR;
+	}
+
+	/* exchange is completed, check if this is past validuntil */
+	if (vuntil_null)
+		retval = STATUS_OK;
+	else if (vuntil < GetCurrentTimestamp())
+	{
+		*logdetail = psprintf(_("User \"%s\" has an expired password."),
+							  port->user_name);
+		retval = STATUS_ERROR;
+	}
+	else
+		retval = STATUS_OK;
+
+	return retval;
+}
 
 
 /*----------------------------------------------------------------
diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c
index f1e9a38..6fe79d7 100644
--- a/src/backend/libpq/hba.c
+++ b/src/backend/libpq/hba.c
@@ -1183,6 +1183,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..d7ff9bc 100644
--- a/src/backend/libpq/pg_hba.conf.sample
+++ b/src/backend/libpq/pg_hba.conf.sample
@@ -42,10 +42,10 @@
 # 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",
-# "ident", "peer", "pam", "ldap", "radius" or "cert".  Note that
-# "password" sends passwords in clear text; "md5" is preferred since
-# it sends encrypted passwords.
+# 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" or "scram" are preferred
+# since they send encrypted passwords.
 #
 # OPTIONS are a set of options for the authentication in the format
 # NAME=VALUE.  The available options depend on the different
diff --git a/src/backend/postmaster/postmaster.c b/src/backend/postmaster/postmaster.c
index 0c0a609..5e8812f 100644
--- a/src/backend/postmaster/postmaster.c
+++ b/src/backend/postmaster/postmaster.c
@@ -2351,6 +2351,7 @@ ConnCreate(int serverFd)
 	 * all backends would end up using the same salt...
 	 */
 	RandomSalt(port->md5Salt, sizeof(port->md5Salt));
+	RandomSalt(port->scramNonce, sizeof(port->scramNonce));
 
 	/*
 	 * Allocate GSSAPI specific state struct
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 40600ab..abb14f3 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -398,6 +398,7 @@ static const struct config_enum_entry password_encryption_options[] = {
 	{"off", PASSWORD_TYPE_PLAINTEXT, false},
 	{"on", PASSWORD_TYPE_MD5, false},
 	{"md5", PASSWORD_TYPE_MD5, false},
+	{"scram", PASSWORD_TYPE_SCRAM, false},
 	{"plain", PASSWORD_TYPE_PLAINTEXT, false},
 	{"true", PASSWORD_TYPE_MD5, true},
 	{"false", PASSWORD_TYPE_PLAINTEXT, true},
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 1fdbc06..ae8e849 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -85,7 +85,7 @@
 #ssl_key_file = 'server.key'		# (change requires restart)
 #ssl_ca_file = ''			# (change requires restart)
 #ssl_crl_file = ''			# (change requires restart)
-#password_encryption = md5		# on, off, md5 or plain
+#password_encryption = md5		# on, off, md5, plain or scram
 #db_user_namespace = off
 #row_security = on
 
diff --git a/src/common/Makefile b/src/common/Makefile
index 3b36c0c..1dbbbdd 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -42,7 +42,7 @@ override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
 
 OBJS_COMMON = config_info.o controldata_utils.o exec.o encode_utils.o ip.o \
 	keywords.o md5.o pg_lzcompress.o pgfnames.o psprintf.o relpath.o \
-	rmtree.o string.o username.o wait_error.o
+	rmtree.o scram-common.o string.o username.o wait_error.o
 
 ifeq ($(with_openssl),yes)
 OBJS_COMMON += sha_openssl.o
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
new file mode 100644
index 0000000..6fb4719
--- /dev/null
+++ b/src/common/scram-common.c
@@ -0,0 +1,196 @@
+/*-------------------------------------------------------------------------
+ * 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) 2016, 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"
+
+#define HMAC_IPAD 0x36
+#define HMAC_OPAD 0x5C
+
+/*
+ * Calculate HMAC per RFC2104.
+ *
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_init(scram_HMAC_ctx *ctx, const uint8 *key, int keylen)
+{
+	uint8		k_ipad[SHA256_HMAC_B];
+	int			i;
+	uint8		keybuf[SCRAM_KEY_LEN];
+
+	/*
+	 * If the key is longer than the block size (64 bytes for SHA-256), pass
+	 * it through SHA-256 once to shrink it down
+	 */
+	if (keylen > SHA256_HMAC_B)
+	{
+		pg_sha256_ctx sha256_ctx;
+
+		pg_sha256_init(&sha256_ctx);
+		pg_sha256_update(&sha256_ctx, key, keylen);
+		pg_sha256_final(&sha256_ctx, keybuf);
+		key = keybuf;
+		keylen = SCRAM_KEY_LEN;
+	}
+
+	memset(k_ipad, HMAC_IPAD, SHA256_HMAC_B);
+	memset(ctx->k_opad, HMAC_OPAD, SHA256_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) */
+	pg_sha256_init(&ctx->sha256ctx);
+	pg_sha256_update(&ctx->sha256ctx, k_ipad, SHA256_HMAC_B);
+}
+
+/*
+ * Update HMAC calculation
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_update(scram_HMAC_ctx *ctx, const char *str, int slen)
+{
+	pg_sha256_update(&ctx->sha256ctx, (const uint8 *) str, slen);
+}
+
+/*
+ * Finalize HMAC calculation.
+ * The hash function used is SHA-256.
+ */
+void
+scram_HMAC_final(uint8 *result, scram_HMAC_ctx *ctx)
+{
+	uint8		h[SCRAM_KEY_LEN];
+
+	pg_sha256_final(&ctx->sha256ctx, h);
+
+	/* H(K XOR opad, tmp) */
+	pg_sha256_init(&ctx->sha256ctx);
+	pg_sha256_update(&ctx->sha256ctx, ctx->k_opad, SHA256_HMAC_B);
+	pg_sha256_update(&ctx->sha256ctx, h, SCRAM_KEY_LEN);
+	pg_sha256_final(&ctx->sha256ctx, result);
+}
+
+/*
+ * Iterate hash calculation of HMAC entry using given salt.
+ * scram_Hi() is essentially PBKDF2 (see RFC2898) with HMAC() as the
+ * pseudorandom function.
+ */
+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-256 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)
+{
+	pg_sha256_ctx ctx;
+
+	pg_sha256_init(&ctx);
+	pg_sha256_update(&ctx, input, len);
+	pg_sha256_final(&ctx, result);
+}
+
+/*
+ * Normalize a password for SCRAM authentication.
+ */
+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);
+}
+
+/*
+ * Encrypt password for SCRAM authentication. This basically applies the
+ * normalization of the password and a hash calculation using the salt
+ * value given by caller.
+ */
+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, SCRAM_KEY_LEN);
+	scram_HMAC_update(&ctx, keystr, strlen(keystr));
+	scram_HMAC_final(result, &ctx);
+}
diff --git a/src/include/commands/user.h b/src/include/commands/user.h
index 7a63841..a5ff5f3 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -20,7 +20,8 @@
 typedef enum PasswordType
 {
 	PASSWORD_TYPE_PLAINTEXT = 0,
-	PASSWORD_TYPE_MD5
+	PASSWORD_TYPE_MD5,
+	PASSWORD_TYPE_SCRAM
 } PasswordType;
 
 extern int Password_encryption;
diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h
new file mode 100644
index 0000000..60460e8
--- /dev/null
+++ b/src/include/common/scram-common.h
@@ -0,0 +1,55 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram-common.h
+ *		Declarations for helper functions used for SCRAM authentication
+ *
+ * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
+ * Portions Copyright (c) 1994, Regents of the University of California
+ *
+ * src/include/common/scram-common.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SCRAM_COMMON_H
+#define SCRAM_COMMON_H
+
+#include "common/sha.h"
+
+/* Length of SCRAM keys (client and server) */
+#define SCRAM_KEY_LEN	PG_SHA256_DIGEST_LENGTH
+
+/* length of HMAC */
+#define SHA256_HMAC_B	PG_SHA256_BLOCK_LENGTH
+
+/* length of random nonce generated in the authentication exchange */
+/*
+ * FIXME: Where does '10' come from? I don't see a length specified in
+ * RFC 5802. What length do other implementations use?
+ */
+#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"
+
+/*
+ * Context data for HMAC used in SCRAM authentication.
+ */
+typedef struct
+{
+	pg_sha256_ctx sha256ctx;
+	uint8		k_opad[SHA256_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 3cd06b7..5a02534 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/hba.h b/src/include/libpq/hba.h
index dc7d257..9c93a6b 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 b91eca5..210111f 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		scramNonce[10];	/* SCRAM server-nonce, size of SCRAM_NONCE_LEN */
 
 	/*
 	 * 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 c6bbfc2..7db809b 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..f08750f
--- /dev/null
+++ b/src/include/libpq/scram.h
@@ -0,0 +1,27 @@
+/*-------------------------------------------------------------------------
+ *
+ * scram.h
+ *	  Interface to libpq/scram.c
+ *
+ * Portions Copyright (c) 1996-2016, 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-SHA-256 per IANA */
+#define SCRAM_SHA256_NAME "SCRAM-SHA-256"
+
+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(const char *username,
+					 const char *password,
+					 int iterations);
+extern bool is_scram_verifier(const char *verifier);
+
+#endif
diff --git a/src/interfaces/libpq/.gitignore b/src/interfaces/libpq/.gitignore
index cb96af7..8bbc75f 100644
--- a/src/interfaces/libpq/.gitignore
+++ b/src/interfaces/libpq/.gitignore
@@ -1,6 +1,7 @@
 /exports.list
 /chklocale.c
 /crypt.c
+/encode_utils.c
 /getaddrinfo.c
 /getpeereid.c
 /inet_aton.c
@@ -9,6 +10,9 @@
 /open.c
 /pgstrcasecmp.c
 /pqsignal.c
+/scram-common.c
+/sha.c
+/sha_openssl.c
 /snprintf.c
 /strerror.c
 /strlcpy.c
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index b1789eb..3289823 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
@@ -42,10 +42,12 @@ OBJS += $(filter crypt.o getaddrinfo.o getpeereid.o inet_aton.o open.o system.o
 # src/backend/utils/mb
 OBJS += encnames.o wchar.o
 # src/common
-OBJS += ip.o md5.o
+OBJS += ip.o md5.o encode_utils.o scram-common.o
 
 ifeq ($(with_openssl),yes)
-OBJS += fe-secure-openssl.o
+OBJS += fe-secure-openssl.o sha_openssl.o
+else
+OBJS += sha.o
 endif
 
 ifeq ($(PORTNAME), cygwin)
@@ -102,6 +104,9 @@ ip.c md5.c: % : $(top_srcdir)/src/common/%
 encnames.c wchar.c: % : $(backend_src)/utils/mb/%
 	rm -f $@ && $(LN_S) $< .
 
+encode_utils.c scram-common.c sha.c sha_openssl.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..2fd3a5c
--- /dev/null
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -0,0 +1,605 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-auth-scram.c
+ *	   The front-end (client) implementation of SCRAM authentication.
+ *
+ * See the following RFCs 5802 and RFC 7666 for more details:
+ * - RFC 5802: https://tools.ietf.org/html/rfc5802
+ * - RFC 7677: https://tools.ietf.org/html/rfc7677
+ *
+ * Here are 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 yet.
+ *
+ * Portions Copyright (c) 1996-2016, 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_utils.h"
+#include "common/scram-common.h"
+#include "fe-auth.h"
+
+/*
+ * Status of exchange messages used for SCRAM authentication via the
+ * SASL protocol.
+ */
+typedef enum
+{
+	INIT,						/* Send client-first-message */
+	NONCE_SENT,					/* Receive server-first-message, respond with
+								 * client-final-message */
+	PROOF_SENT,					/* Receive server-final-message, verify proof */
+	FINISHED
+} fe_scram_exchange_state;
+
+typedef struct
+{
+	fe_scram_exchange_state state;
+
+	/* Supplied by caller */
+	const char *username;
+	const char *password;
+
+	char		client_nonce[SCRAM_NONCE_LEN + 1];
+	char	   *client_first_message_bare;
+	char	   *client_final_message_without_proof;
+
+	/* Fields from the server-first message */
+	char	   *server_first_message;
+	char	   *salt;
+	int			saltlen;
+	int			iterations;
+	char	   *nonce;			/* client+server nonces */
+
+	/* Fields 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);
+
+/*
+ * Initialize a SCRAM authentication exchange.
+ */
+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;
+}
+
+/*
+ * Free SCRAM exchange 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->nonce)
+		free(state->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_first_message */
+			*output = build_client_first_message(state);
+			if (*output == NULL)
+			{
+				printfPQExpBuffer(errorMessage,
+								  libpq_gettext("out of memory"));
+				*done = true;
+				state->state = FINISHED;
+				return;
+			}
+			*outputlen = strlen(*output);
+			*done = false;
+			state->state = NONCE_SENT;
+			break;
+
+		case NONCE_SENT:
+			/* receive salt and server nonce, send response */
+			if (!read_server_first_message(state, input, errorMessage))
+			{
+				*done = true;
+				state->state = FINISHED;
+				return;
+			}
+			*output = build_client_final_message(state);
+			if (*output == NULL)
+			{
+				printfPQExpBuffer(errorMessage,
+								  libpq_gettext("out of memory"));
+				*done = true;
+				state->state = FINISHED;
+				return;
+			}
+			*outputlen = strlen(*output);
+			*done = false;
+			state->state = PROOF_SENT;
+			break;
+
+		case PROOF_SENT:
+			/* receive server proof, and verify it */
+			if (!read_server_final_message(state, input, errorMessage))
+			{
+				*done = true;
+				state->state = FINISHED;
+				return;
+			}
+			*success = verify_server_proof(state);
+			*done = true;
+			state->state = FINISHED;
+			break;
+
+		default:
+			/* shouldn't happen */
+			*done = true;
+			*success = false;
+			printfPQExpBuffer(errorMessage,
+							  libpq_gettext("invalid SCRAM exchange state"));
+	}
+}
+
+/*
+ * Read value for an attribute part of a SASL message.
+ */
+static char *
+read_attr_value(char **input, char attr, PQExpBuffer errorMessage)
+{
+	char	   *begin = *input;
+	char	   *end;
+
+	if (*begin != attr)
+	{
+		printfPQExpBuffer(errorMessage,
+			   libpq_gettext("malformed SCRAM message (%c expected)"), attr);
+		return NULL;
+	}
+	begin++;
+
+	if (*begin != '=')
+	{
+		printfPQExpBuffer(errorMessage,
+		  libpq_gettext("malformed SCRAM message (expected '=' in attr %c)"),
+						  attr);
+		return NULL;
+	}
+	begin++;
+
+	end = begin;
+	while (*end && *end != ',')
+		end++;
+
+	if (*end)
+	{
+		*end = '\0';
+		*input = end + 1;
+	}
+	else
+		*input = end;
+
+	return begin;
+}
+
+/*
+ * Build the first exchange message sent by the client.
+ *
+ * Returns NULL on out-of-memory.
+ */
+static char *
+build_client_first_message(fe_scram_state *state)
+{
+	char	   *buf;
+	char		msglen;
+
+	generate_nonce(state->client_nonce, SCRAM_NONCE_LEN);
+
+	/* Construct message */
+	msglen = 5 + strlen(state->username) + 3 + strlen(state->client_nonce);
+	buf = malloc(msglen + 1);
+	if (!buf)
+		return NULL;
+	snprintf(buf, msglen + 1, "n,,n=%s,r=%s",
+			 state->username, state->client_nonce);
+
+	/* Save the client_first_message_bare part for later */
+	state->client_first_message_bare = strdup(buf + 3);
+	if (!state->client_first_message_bare)
+	{
+		free(buf);
+		return NULL;
+	}
+
+	return buf;
+}
+
+/*
+ * Build the final exchange message sent by the client.
+ *
+ * Returns NULL on out-of-memory.
+ */
+static char *
+build_client_final_message(fe_scram_state *state)
+{
+	int			len;
+	char	   *buf;
+	uint8		client_proof[SCRAM_KEY_LEN];
+	char		client_proof_base64[SCRAM_KEY_LEN * 2 + 1];
+	int			client_proof_len;
+
+	/* Build client_final_message_without_proof */
+	len = 9 + strlen(state->nonce);
+	buf = malloc(len + 1);
+	if (!buf)
+		return NULL;
+	snprintf(buf, len + 1,
+			 "c=biws,r=%s", state->nonce);
+	state->client_final_message_without_proof = buf;
+
+	/* Calculate client-proof */
+	calculate_client_proof(state,
+						   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';
+
+	/* Build client_final_message (with the proof) */
+	len = strlen(state->client_final_message_without_proof) + 3 + client_proof_len;
+	buf = malloc(len + 1);
+	if (!buf)
+		return NULL;
+	snprintf(buf, len + 1, "%s,p=%s",
+			 state->client_final_message_without_proof,
+			 client_proof_base64);
+
+	return buf;
+}
+
+/*
+ * Read the first exchange message coming from the server.
+ *
+ * Returns true on success. On failure, returns false,
+ * and adds details to 'errormessage'.
+ */
+static bool
+read_server_first_message(fe_scram_state *state,
+						  char *input,
+						  PQExpBuffer errorMessage)
+{
+	char	   *iterations_str;
+	char	   *endptr;
+	char	   *encoded_salt;
+	char	   *s;
+
+	state->server_first_message = strdup(input);
+	if (!state->server_first_message)
+		return false;
+
+	/*----------
+	 * Parse the message.
+	 *
+	 * According to RFC 5820, the syntax of the server-first-message is:
+	 *
+	 * server-first-message =
+	 *					 [reserved-mext ","] nonce "," salt ","
+	 *					 iteration-count ["," extensions]
+	 *
+	 * reserved-mext   = "m=" 1*(value-char)
+	 *					 ;; Reserved for signaling mandatory extensions.
+	 *					 ;; The exact syntax will be defined in
+	 *					 ;; the future.
+	 *
+	 * 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
+	 *----------
+	 */
+
+	/*
+	 * Check for possible mandatory extensions first. We don't support any, so
+	 * throw an error, if there are any.
+	 */
+	if (*input == 'm')
+	{
+		printfPQExpBuffer(errorMessage,
+						  libpq_gettext("server requires a SCRAM extension that is not supported"));
+		return false;
+	}
+
+	/*
+	 * Read nonce. It consists of the client-nonce that we sent earlier, and
+	 * the server-generated part. Check that the client-nonce matches what we
+	 * sent.
+	 */
+	s = read_attr_value(&input, 'r', errorMessage);
+	if (!s)
+		return false;
+	state->nonce = strdup(s);
+	if (state->nonce == NULL)
+	{
+		printfPQExpBuffer(errorMessage,
+						  libpq_gettext("out of memory"));
+		return false;
+	}
+
+	if (strncmp(state->nonce, state->client_nonce, strlen(state->client_nonce)) != 0)
+	{
+		printfPQExpBuffer(errorMessage,
+					 libpq_gettext("invalid client-nonce in SCRAM message"));
+		return false;
+	}
+
+	/* Read the salt */
+	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)
+	{
+		printfPQExpBuffer(errorMessage,
+						  libpq_gettext("out of memory"));
+		return false;
+	}
+	state->saltlen = b64_decode(encoded_salt, strlen(encoded_salt), state->salt);
+
+	/* FIXME: shouldn't we allow any salt length? */
+	if (state->saltlen != SCRAM_SALT_LEN)
+	{
+		printfPQExpBuffer(errorMessage,
+			 libpq_gettext("malformed SCRAM message (invalid salt length)"));
+		return false;
+	}
+
+	/* Read iteration count */
+	iterations_str = read_attr_value(&input, 'i', errorMessage);
+	if (iterations_str == NULL)
+		return false;
+	state->iterations = strtol(iterations_str, &endptr, 10);
+	if (*endptr != '\0' || state->iterations < 1)
+	{
+		printfPQExpBuffer(errorMessage,
+		 libpq_gettext("malformed SCRAM message (invalid # of iterations)"));
+		return false;
+	}
+
+	if (*input != '\0')
+	{
+		printfPQExpBuffer(errorMessage,
+						  libpq_gettext("malformed SCRAM message (garbage after end of message)"));
+		return false;
+	}
+
+	return true;
+}
+
+/*
+ * Read the final exchange message coming from the server.
+ */
+static bool
+read_server_final_message(fe_scram_state *state,
+						  char *input,
+						  PQExpBuffer errorMessage)
+{
+	char	   *encoded_server_proof;
+	int			server_proof_len;
+	char		buf[SCRAM_KEY_LEN * 2];
+
+	state->server_final_message = strdup(input);
+	if (!state->server_final_message)
+		return false;
+
+	/*
+	 * Parse the message. It consists of a single "v=..." attribute,
+	 * containing the base64-encoded server-proof.
+	 */
+
+	/*
+	 * FIXME: also parse a possible server-error attribute. The server
+	 * currently never sends that, but still.
+	 */
+	encoded_server_proof = read_attr_value(&input, 'v', errorMessage);
+	if (encoded_server_proof == NULL)
+		return false;
+
+	if (b64_dec_len(encoded_server_proof,
+					strlen(encoded_server_proof)) > sizeof(buf))
+	{
+		printfPQExpBuffer(errorMessage,
+						  libpq_gettext("malformed SCRAM message (invalid ServerProof length)"));
+		return false;
+	}
+
+	server_proof_len = b64_decode(encoded_server_proof,
+								  strlen(encoded_server_proof),
+								  buf);
+	if (server_proof_len != SCRAM_KEY_LEN)
+	{
+		printfPQExpBuffer(errorMessage,
+						  libpq_gettext("malformed SCRAM message (invalid ServerProof length)"));
+		return false;
+	}
+	memcpy(state->ServerProof, buf, SCRAM_KEY_LEN);
+
+	if (*input != '\0')
+	{
+		printfPQExpBuffer(errorMessage,
+						  libpq_gettext("malformed SCRAM message (garbage after end of message)"));
+		return false;
+	}
+
+	return true;
+}
+
+/*
+ * Calculate the client proof, part of the final exchange message sent
+ * by the client.
+ */
+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, SCRAM_KEY_LEN);
+	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];
+}
+
+/*
+ * Validate the server proof, received as part of the final exchange message
+ * received from the server.
+ *
+ * It's important to verify the server-proof, even if the server subsequently
+ * accepts the login, to detect that the server is genuine! (XXX: An impersonating
+ * server could choose to present us an MD5 challenge instead, or no challenge
+ * at all, but that's a different problem.)
+ */
+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, SCRAM_KEY_LEN);
+	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;
+
+	/* FIXME: use RAND_bytes() if available? */
+	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 404bc93..97861a7 100644
--- a/src/interfaces/libpq/fe-auth.c
+++ b/src/interfaces/libpq/fe-auth.c
@@ -41,6 +41,7 @@
 #include "common/md5.h"
 #include "libpq-fe.h"
 #include "fe-auth.h"
+#include "libpq/scram.h"
 
 
 #ifdef ENABLE_GSS
@@ -431,6 +432,84 @@ pg_SSPI_startup(PGconn *conn, int use_negotiate)
 #endif   /* ENABLE_SSPI */
 
 /*
+ * Initialize SASL status.
+ * This will be used afterwards for the exchange message protocol used by
+ * SASL for SCRAM.
+ */
+static bool
+pg_SASL_init(PGconn *conn, const char *auth_mechanism)
+{
+	/*
+	 * Check the authentication mechanism (only SCRAM-SHA-256 is supported at
+	 * the moment.)
+	 */
+	if (strcmp(auth_mechanism, SCRAM_SHA256_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;
+	}
+}
+
+/*
+ * Exchange a message for SASL communication protocol with the backend.
+ * This should be used after calling pg_SASL_init to set up the status of
+ * the protocol.
+ */
+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.
  *
  * Note: this is dead code as of Postgres 9.1, because current backends will
@@ -698,6 +777,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 9d11654..f779fb2 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 f3a9e5a..6e1ccd6 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2485,6 +2485,49 @@ keep_going:						/* We will come back to here until there is
 					}
 				}
 #endif
+				/* Get additional payload for SASL, if any */
+				if ((areq == AUTH_REQ_SASL ||
+					 areq == AUTH_REQ_SASL_CONT) &&
+					msgLength > 4)
+				{
+					int			llen = msgLength - 4;
+
+					/*
+					 * We can be called repeatedly for the same buffer. Avoid
+					 * re-allocating the buffer in this case - just re-use the
+					 * old buffer.
+					 */
+					if (llen != conn->auth_req_inlen)
+					{
+						if (conn->auth_req_inbuf)
+						{
+							free(conn->auth_req_inbuf);
+							conn->auth_req_inbuf = NULL;
+						}
+
+						conn->auth_req_inlen = llen;
+						conn->auth_req_inbuf = malloc(llen + 1);
+						if (!conn->auth_req_inbuf)
+						{
+							printfPQExpBuffer(&conn->errorMessage,
+											  libpq_gettext("out of memory allocating SASL buffer (%d)"),
+											  llen);
+							goto error_return;
+						}
+					}
+
+					if (pqGetnchar(conn->auth_req_inbuf, llen, conn))
+					{
+						/* We'll come back when there is more data. */
+						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
@@ -3042,6 +3085,15 @@ closePGconn(PGconn *conn)
 		conn->sspictx = NULL;
 	}
 #endif
+	if (conn->sasl_state)
+	{
+		/*
+		 * XXX: if support for more authentication mechanisms is added, 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 be6c370..7f28d12 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -422,7 +422,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.9.3

