From 0c73872ebe781bee30598344339afc5b400bdda8 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 14 Nov 2016 19:39:32 +0900
Subject: [PATCH 7/9] Support for SCRAM-SHA-256 authentication (RFC 5802 and
 7677)

SHA-256 is used as hashing function. This commit introduces the basic
SASL communication protocol plugged in on top of the existing
infrastructure.

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/client-auth.sgml                 |  37 +-
 doc/src/sgml/config.sgml                      |  13 +-
 doc/src/sgml/protocol.sgml                    | 147 +++-
 doc/src/sgml/ref/create_role.sgml             |  23 +-
 src/backend/commands/user.c                   |  52 +-
 src/backend/libpq/Makefile                    |   2 +-
 src/backend/libpq/auth-scram.c                | 972 ++++++++++++++++++++++++++
 src/backend/libpq/auth.c                      | 111 +++
 src/backend/libpq/crypt.c                     |  69 +-
 src/backend/libpq/hba.c                       |  13 +
 src/backend/libpq/pg_hba.conf.sample          |   8 +-
 src/backend/utils/misc/guc.c                  |   1 +
 src/backend/utils/misc/postgresql.conf.sample |   2 +-
 src/common/Makefile                           |   2 +-
 src/common/scram-common.c                     | 195 ++++++
 src/include/commands/user.h                   |   3 +-
 src/include/common/scram-common.h             |  56 ++
 src/include/libpq/crypt.h                     |   8 +
 src/include/libpq/hba.h                       |   1 +
 src/include/libpq/libpq-be.h                  |   4 +-
 src/include/libpq/pqcomm.h                    |   2 +
 src/include/libpq/scram.h                     |  35 +
 src/interfaces/libpq/.gitignore               |   5 +
 src/interfaces/libpq/Makefile                 |  17 +-
 src/interfaces/libpq/fe-auth-scram.c          | 562 +++++++++++++++
 src/interfaces/libpq/fe-auth.c                | 108 +++
 src/interfaces/libpq/fe-auth.h                |   8 +
 src/interfaces/libpq/fe-connect.c             |  52 ++
 src/interfaces/libpq/libpq-int.h              |   5 +
 src/tools/msvc/Mkvcbuild.pm                   |  10 +-
 32 files changed, 2479 insertions(+), 82 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 bac169a..7cec087 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/client-auth.sgml b/doc/src/sgml/client-auth.sgml
index 960f5b5..817ed57 100644
--- a/doc/src/sgml/client-auth.sgml
+++ b/doc/src/sgml/client-auth.sgml
@@ -421,6 +421,17 @@ hostnossl  <replaceable>database</replaceable>  <replaceable>user</replaceable>
        </varlistentry>
 
        <varlistentry>
+        <term><literal>scram</></term>
+        <listitem>
+         <para>
+          Require the client to supply a password encrypted with
+          SCRAM-SHA-256 for authentication.
+          See <xref linkend="auth-password"> for details.
+         </para>
+        </listitem>
+       </varlistentry>
+
+       <varlistentry>
         <term><literal>password</></term>
         <listitem>
          <para>
@@ -650,17 +661,24 @@ host    all             all             localhost               trust
 host    postgres        all             192.168.93.0/24         ident
 
 # Allow any user from host 192.168.12.10 to connect to database
-# "postgres" if the user's password is correctly supplied.
+# "postgres" if the user's password is correctly supplied and is
+# using the correct password method.
 #
 # TYPE  DATABASE        USER            ADDRESS                 METHOD
 host    postgres        all             192.168.12.10/32        md5
+host    postgres        all             192.168.12.10/32        scram
 
 # Allow any user from hosts in the example.com domain to connect to
-# any database if the user's password is correctly supplied.
+# any database if the user's password is correctly supplied and is using
+# a MD5-encrypted password.
 #
 # TYPE  DATABASE        USER            ADDRESS                 METHOD
 host    all             all             .example.com            md5
 
+# Same as previous entry, except that the supplied password must be
+# encrypted with SCRAM-SHA-256.
+host    all             all             .example.com            scram
+
 # In the absence of preceding "host" lines, these two lines will
 # reject all connections from 192.168.54.1 (since that entry will be
 # matched first), but allow GSSAPI connections from anywhere else
@@ -888,9 +906,9 @@ omicron         bryanh                  guest1
 
    <para>
     The password-based authentication methods are <literal>md5</>
-    and <literal>password</>. These methods operate
+    <literal>scram</> and <literal>password</>. These methods operate
     similarly except for the way that the password is sent across the
-    connection, namely MD5-hashed and clear-text respectively.
+    connection, namely MD5-hashed, SCRAM-SHA-256 and clear-text respectively.
    </para>
 
    <para>
@@ -905,6 +923,17 @@ omicron         bryanh                  guest1
    </para>
 
    <para>
+    <literal>scram</> has more advantages than <literal>md5</> as it
+    protects from cases where the hashed password is taken directly from
+    <structname>pg_authid</structname> in which case a connection using
+    only the stolen hash is possible without knowing the password behind
+    it.  It protects as well from password interception and data sniffing
+    where the password data could be directly obtained from the network
+    as well as man-in-the-middle (MITM) attacks.  So it is strongly
+    encouraged to use it over <literal>md5</> for password-based deployments.
+   </para>
+
+   <para>
     <productname>PostgreSQL</productname> database passwords are
     separate from operating system user passwords. The password for
     each database user is stored in the <literal>pg_authid</> system
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index adab2f8..e1b7c5b 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1176,9 +1176,10 @@ include_dir 'conf.d'
         password is to be encrypted. The default value is <literal>md5</>, which
         stores the password as an MD5 hash. Setting this to <literal>plain</> stores
         it in plaintext. <literal>on</> and <literal>off</> are also accepted, as
-        aliases for <literal>md5</> and <literal>plain</>, respectively.
-       </para>
-       
+        aliases for <literal>md5</> and <literal>plain</>, respectively.  Setting
+        this parameter to <literal>scram</> will encrypt the password with
+        SCRAM-SHA-256.
+       </para>       
       </listitem>
      </varlistentry>
 
@@ -1251,8 +1252,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 50cf527..1c23c1d 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>
 
@@ -2585,6 +2614,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>
@@ -4347,7 +4484,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 25911ec..7750a88 100644
--- a/doc/src/sgml/ref/create_role.sgml
+++ b/doc/src/sgml/ref/create_role.sgml
@@ -229,16 +229,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>
@@ -254,10 +254,11 @@ CREATE ROLE <replaceable class="PARAMETER">name</replaceable> [ [ WITH ] <replac
         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. The methods supported are <literal>md5</> to enforce
-        a password to be MD5-encrypted, and <literal>plain</> to use an
-        unencrypted password.  If the password string is already in
-        MD5-encrypted format, then it is stored encrypted even if
-        <literal>plain</> is specified.
+        a password to be MD5-encrypted, <literal>scram</> to enforce a password
+        to be encrypted with SCRAM-SHA-256, and <literal>plain</> to use
+        an unencrypted password.  If the password string is already in
+        MD5-encrypted or SCRAM-SHA-256 format, then it is stored encrypted
+        even if another protocol is specified.
        </para>
       </listitem>
      </varlistentry>
diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c
index 19eeb04..21fe6c1 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,10 +70,10 @@ 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 any transformation on the input
- * string has been done.
+ * 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 if any transformation on
+ * the input string has been done.
  */
 static char *
 encrypt_password(char *password, char *rolname, int passwd_type)
@@ -82,11 +83,12 @@ 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.
+	 * If a password is already identified as MD5-encrypted or
+	 * SCRAM-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.
 	 */
-	if (isMD5(password))
+	if (isMD5(password) || is_scram_verifier(password))
 		res = password;
 	else
 	{
@@ -102,6 +104,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:
 				elog(ERROR, "incorrect password type");
 		}
@@ -222,6 +227,8 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 						password_type = PASSWORD_TYPE_MD5;
 					else if (strcmp(method, "plain") == 0)
 						password_type = PASSWORD_TYPE_PLAINTEXT;
+					else if (strcmp(method, "scram") == 0)
+						password_type = PASSWORD_TYPE_SCRAM;
 					else
 						ereport(ERROR,
 							(errcode(ERRCODE_SYNTAX_ERROR),
@@ -451,11 +458,22 @@ CreateRole(ParseState *pstate, CreateRoleStmt *stmt)
 	 * Call the password checking hook if there is one defined
 	 */
 	if (check_password_hook && password)
+	{
+		int type;
+
+		if (isMD5(password))
+			type = PASSWORD_TYPE_MD5;
+		else if (is_scram_verifier(password))
+			type = PASSWORD_TYPE_SCRAM;
+		else
+			type = PASSWORD_TYPE_PLAINTEXT;
+
 		(*check_password_hook) (stmt->role,
 								password,
-			   isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+								type,
 								validUntil_datum,
 								validUntil_null);
+	}
 
 	/*
 	 * Build a tuple to insert
@@ -663,6 +681,8 @@ AlterRole(AlterRoleStmt *stmt)
 						password_type = PASSWORD_TYPE_MD5;
 					else if (strcmp(method, "plain") == 0)
 						password_type = PASSWORD_TYPE_PLAINTEXT;
+					else if (strcmp(method, "scram") == 0)
+						password_type = PASSWORD_TYPE_SCRAM;
 					else
 						ereport(ERROR,
 							(errcode(ERRCODE_SYNTAX_ERROR),
@@ -859,11 +879,23 @@ AlterRole(AlterRoleStmt *stmt)
 	 * Call the password checking hook if there is one defined
 	 */
 	if (check_password_hook && password)
+	{
+		int type;
+
+		if (isMD5(password))
+			type = PASSWORD_TYPE_MD5;
+		else if (is_scram_verifier(password))
+			type = PASSWORD_TYPE_SCRAM;
+		else
+			type = PASSWORD_TYPE_PLAINTEXT;
+
 		(*check_password_hook) (rolename,
 								password,
-			   isMD5(password) ? PASSWORD_TYPE_MD5 : PASSWORD_TYPE_PLAINTEXT,
+								type,
 								validUntil_datum,
 								validUntil_null);
+	}
+
 
 	/*
 	 * Build an updated tuple, perusing the information just obtained
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..7d41ce8
--- /dev/null
+++ b/src/backend/libpq/auth-scram.c
@@ -0,0 +1,972 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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.
+ *
+ * The password stored in pg_authid consists of the salt, iteration count,
+ * StoredKey and ServerKey.
+ *
+ * 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/base64.h"
+#include "common/scram-common.h"
+#include "common/sha2.h"
+#include "libpq/auth.h"
+#include "libpq/crypt.h"
+#include "libpq/scram.h"
+#include "miscadmin.h"
+#include "utils/builtins.h"
+#include "utils/timestamp.h"
+
+/*
+ * Status data for SCRAM.  This should be kept internal to this file.
+ */
+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];
+
+	/* Fields of 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;
+
+/*
+ * Internal error codes for exchange functions.
+ * Callers of the exchange routines do not need to be aware of any of
+ * that and should just send messages generated here.
+ */
+typedef enum
+{
+	SASL_NO_ERROR = 0,
+	/* error codes */
+	SASL_INVALID_ENCODING,
+	SASL_EXTENSIONS_NOT_SUPPORTED,
+	SASL_CHANNEL_BINDING_UNMATCH,
+	SASL_CHANNEL_BINDING_NO_SUPPORT,
+	SASL_CHANNEL_BINDING_TYPE_NOT_SUPPORTED,
+	SASL_UNKNOWN_USER,
+	SASL_INVALID_PROOF,
+	SASL_INVALID_USERNAME_ENCODING,
+	SASL_NO_RESOURCES,
+	/*
+	 * Unrecognized errors should be treated as "other-error". In order to
+	 * prevent information disclosure, the server may substitute the real
+	 * reason with "other-error".
+	 */
+	SASL_OTHER_ERROR
+} SASLStatus;
+
+
+static SASLStatus read_client_first_message(scram_state *state,
+											char *input, char **logdetail);
+static SASLStatus read_client_final_message(scram_state *state,
+											char *input, char **logdetail);
+static char *build_server_first_message(scram_state *state);
+static char *build_server_final_message(scram_state *state);
+static char *build_error_message(SASLStatus status);
+static SASLStatus check_client_data(void *opaque, char **logdetail);
+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);
+
+/*
+ * build_error_message
+ *
+ * Build an error message for a problem that happened during the SASL
+ * message exchange.  Those messages are formatted with e= as prefix
+ * and need to be sent back to the client.
+ */
+static char *
+build_error_message(SASLStatus status)
+{
+	char *res = NULL;
+
+	/*
+	 * The following error format is respected here:
+	 *
+	 * 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.
+	 */
+
+	switch (status)
+	{
+		case SASL_INVALID_ENCODING:
+			res = psprintf("e=invalid-encoding");
+			break;
+		case SASL_EXTENSIONS_NOT_SUPPORTED:
+			res = psprintf("e=extensions-not-supported");
+			break;
+		case SASL_INVALID_PROOF:
+			res = psprintf("e=invalid-proof");
+			break;
+		case SASL_CHANNEL_BINDING_UNMATCH:
+			res = psprintf("e=channel-bindings-dont-match");
+			break;
+		case SASL_CHANNEL_BINDING_NO_SUPPORT:
+			res = psprintf("e=server-does-support-channel-binding");
+			break;
+		case SASL_CHANNEL_BINDING_TYPE_NOT_SUPPORTED:
+			res = psprintf("e=unsupported-channel-binding-type");
+			break;
+		case SASL_UNKNOWN_USER:
+			res = psprintf("e=unknown-user");
+			break;
+		case SASL_INVALID_USERNAME_ENCODING:
+			res = psprintf("e=invalid-username-encoding");
+			break;
+		case SASL_NO_RESOURCES:
+			res = psprintf("e=no-resources");
+			break;
+		case SASL_OTHER_ERROR:
+			res = psprintf("e=other-error");
+			break;
+		case SASL_NO_ERROR:
+		default:
+			Assert(0);	/* should not happen */
+	}
+
+	Assert(res != NULL);
+	return res;
+}
+
+/*
+ * pg_be_scram_init
+ *
+ * Initialize a new SCRAM authentication exchange status tracker. This
+ * needs to be called before doing any exchange. It will be filled later
+ * after the beginning of the exchange with verifier data.
+ */
+void *
+pg_be_scram_init(char *username)
+{
+	scram_state *state;
+
+	state = (scram_state *) palloc0(sizeof(scram_state));
+	state->state = INIT;
+	state->username = username;
+	state->salt = NULL;
+
+	return state;
+}
+
+/*
+ * check_client_data
+ *
+ * Fill into the SASL exchange status all the information related to user and
+ * perform sanity checks.
+ */
+static SASLStatus
+check_client_data(void *opaque, char **logdetail)
+{
+	scram_state *state = (scram_state *) opaque;
+	char   *server_key;
+	char   *stored_key;
+	char   *salt;
+	int		iterations;
+	int		res;
+	char   *passwd;
+	TimestampTz vuntil = 0;
+	bool	vuntil_null;
+	int		count = 0;
+
+	/* compute the salt to use for computing responses */
+	while (count < sizeof(MyProcPort->SASLSalt))
+	{
+		char	byte;
+
+		if (!pg_strong_random(&byte, 1))
+		{
+			*logdetail = psprintf(_("Could not generate random salt"));
+			return SASL_OTHER_ERROR;
+		}
+
+		/*
+		 * Only ASCII printable characters, except commas are accepted in
+		 * the nonce.
+		 */
+		if (byte < '!' || byte > '~' || byte == ',')
+			continue;
+
+		MyProcPort->SASLSalt[count] = byte;
+		count++;
+	}
+
+	/*
+	 * Fetch details about role needed for password checks.
+	 */
+	res = get_role_details(state->username, &passwd, &vuntil,
+						   &vuntil_null,
+						   logdetail);
+
+	/*
+	 * Check if an error has happened.  "logdetail" is already filled,
+	 * here we just need to find what is the error mapping with the SASL
+	 * exchange and let the client know what happened.
+	 */
+	if (res == PG_ROLE_NOT_DEFINED)
+		return SASL_UNKNOWN_USER;
+	else if (res == PG_ROLE_NO_PASSWORD ||
+			 res == PG_ROLE_EMPTY_PASSWORD)
+		return SASL_OTHER_ERROR;
+
+	/*
+	 * Check password validity, there is nothing to do if the password
+	 * validity field is null.
+	 */
+	if (!vuntil_null)
+	{
+		if (vuntil < GetCurrentTimestamp())
+		{
+			*logdetail = psprintf(_("User \"%s\" has an expired password."),
+								  state->username);
+			pfree(passwd);
+			return SASL_OTHER_ERROR;
+		}
+	}
+
+	/* The SCRAM verifier needs to be in correct shape as well. */
+	if (!parse_scram_verifier(passwd, &salt, &iterations,
+							  &stored_key, &server_key))
+	{
+		*logdetail = psprintf(_("User \"%s\" does not have a valid SCRAM verifier."),
+							  state->username);
+		pfree(passwd);
+		return SASL_OTHER_ERROR;
+	}
+
+	/* OK to fill in everything */
+	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);
+	pfree(passwd);
+	return SASL_NO_ERROR;
+}
+
+/*
+ * Continue a SCRAM authentication exchange.
+ *
+ * The next message to send to client is saved in "output", for a length
+ * of "outputlen".  In the case of an error, optionally store a palloc'd
+ * string at *logdetail that will be sent to the postmaster log (but not
+ * the client).
+ */
+int
+pg_be_scram_exchange(void *opaq, char *input, int inputlen,
+					 char **output, int *outputlen, char **logdetail)
+{
+	scram_state	   *state = (scram_state *) opaq;
+	SASLStatus		status;
+	int				result;
+
+	*output = NULL;
+	*outputlen = 0;
+
+	if (inputlen > 0)
+		elog(DEBUG4, "got SCRAM message: %s", input);
+
+	switch (state->state)
+	{
+		case INIT:
+			/*
+			 * Initialization phase.  Things happen in the following order:
+			 * 1) Receive the first message from client and be sure that it
+			 *    is parsed correctly.
+			 * 2) Validate the user information. A couple of things are done
+			 *    here, mainly validity checks on the password and the user.
+			 * 3) Send the challenge to the client.
+			 */
+			status = read_client_first_message(state, input, logdetail);
+			if (status != SASL_NO_ERROR)
+			{
+				*output = build_error_message(status);
+				*outputlen = strlen(*output);
+				result = SASL_EXCHANGE_FAILURE;
+				break;
+			}
+
+			/* check validity of user data */
+			status = check_client_data(state, logdetail);
+			if (status != SASL_NO_ERROR)
+			{
+				*output = build_error_message(status);
+				*outputlen = strlen(*output);
+				result = SASL_EXCHANGE_FAILURE;
+				break;
+			}
+
+			/* prepare message to send challenge */
+			*output = build_server_first_message(state);
+			if (*output == NULL)
+			{
+				*output = build_error_message(SASL_OTHER_ERROR);
+				*outputlen = strlen(*output);
+				result = SASL_EXCHANGE_FAILURE;
+				break;
+			}
+			*outputlen = strlen(*output);
+			state->state = SALT_SENT;
+			result = SASL_EXCHANGE_CONTINUE;
+			break;
+
+		case SALT_SENT:
+			/*
+			 * Final phase for the server. First receive the response to
+			 * the challenge previously sent and then let the client know
+			 * that everything went well.
+			 */
+			status = read_client_final_message(state, input, logdetail);
+			if (status != SASL_NO_ERROR)
+			{
+				*output = build_error_message(status);
+				*outputlen = strlen(*output);
+				result = SASL_EXCHANGE_FAILURE;
+				break;
+			}
+
+			/* Now check the final nonce and the client proof */
+			if (!verify_final_nonce(state) ||
+				!verify_client_proof(state))
+			{
+				*output = build_error_message(SASL_INVALID_PROOF);
+				*outputlen = strlen(*output);
+				result = SASL_EXCHANGE_FAILURE;
+				break;
+			}
+
+			/* Build final message for client */
+			*output = build_server_final_message(state);
+			if (*output == NULL)
+			{
+				*output = build_error_message(SASL_OTHER_ERROR);
+				*outputlen = strlen(*output);
+				result = SASL_EXCHANGE_FAILURE;
+				break;
+			}
+
+			/* Success! */
+			*outputlen = strlen(*output);
+			result = SASL_EXCHANGE_SUCCESS;
+			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.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;
+
+	if (iterations <= 0)
+		iterations = SCRAM_ITERATIONS_DEFAULT;
+
+	generate_nonce(salt, SCRAM_SALT_LEN);
+
+	encoded_salt = palloc(pg_b64_enc_len(SCRAM_SALT_LEN) + 1);
+	encoded_len = pg_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.
+ *
+ * Returns true if the SCRAM verifier has been parsed, and false otherwise.
+ */
+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, SCRAM_ITERATION_LEN);
+	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;
+}
+
+/*
+ * 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)
+		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;
+}
+
+/*
+ * 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')))
+		return NULL;
+	if (attr_p)
+		*attr_p = attr;
+	begin++;
+
+	if (*begin != '=')
+		return NULL;
+	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.  In the event of an error, returns
+ * to caller a e= message to be used for the rest of the exchange, or
+ * NULL in case of success.
+ */
+static SASLStatus
+read_client_first_message(scram_state *state, char *input, char **logdetail)
+{
+	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 */
+			*logdetail = psprintf(_("channel binding not supported."));
+			return SASL_CHANNEL_BINDING_NO_SUPPORT;
+	}
+
+	/* any mandatory extensions would go here. */
+	if (*input != ',')
+	{
+		*logdetail = psprintf(_("mandatory extension %c not supported."), *input);
+		return SASL_OTHER_ERROR;
+	}
+	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! */
+	return SASL_EXCHANGE_CONTINUE;
+}
+
+/*
+ * 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);
+	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;
+}
+
+/*
+ * 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;
+
+	/*
+	 * 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(pg_b64_enc_len(SCRAM_NONCE_LEN) + 1);
+	encoded_len = pg_b64_encode(nonce, SCRAM_NONCE_LEN, state->server_nonce);
+
+	if (encoded_len < 0)
+		return NULL;
+
+	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 SASLStatus
+read_client_final_message(scram_state *state, char *input, char **logdetail)
+{
+	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)
+	{
+		*logdetail = psprintf(_("invalid channel binding input."));
+		return SASL_CHANNEL_BINDING_TYPE_NOT_SUPPORTED;
+	}
+	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(pg_b64_dec_len(strlen(value)));
+	if (pg_b64_decode(value, strlen(value), client_proof) != SCRAM_KEY_LEN)
+	{
+		*logdetail = psprintf(_("invalid client proof."));
+		return SASL_INVALID_PROOF;
+	}
+	memcpy(state->ClientProof, client_proof, SCRAM_KEY_LEN);
+	pfree(client_proof);
+
+	if (*p != '\0')
+	{
+		*logdetail = psprintf(_("malformed SCRAM message (garbage at end of message %c)."),
+							  attr);
+		return SASL_OTHER_ERROR;
+	}
+
+	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 */
+	return SASL_NO_ERROR;
+}
+
+/*
+ * 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(pg_b64_enc_len(SCRAM_KEY_LEN) + 1);
+	siglen = pg_b64_encode((const char *) ServerSignature,
+						   SCRAM_KEY_LEN, server_signature_base64);
+	if (siglen < 0)
+		return NULL;
+	server_signature_base64[siglen] = '\0';
+
+	/*
+	 * The following error is generated:
+	 *
+	 * 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 44b2212..6f774d9 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -30,6 +30,7 @@
 #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"
@@ -210,6 +211,12 @@ static int	CheckRADIUSAuth(Port *port);
 
 
 /*----------------------------------------------------------------
+ * SASL authentication
+ *----------------------------------------------------------------
+ */
+static int CheckSASLAuth(Port *port, char **logdetail);
+
+/*----------------------------------------------------------------
  * Global authentication functions
  *----------------------------------------------------------------
  */
@@ -271,6 +278,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;
@@ -549,6 +557,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, "");
@@ -738,6 +750,105 @@ 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	   *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.)
+	 */
+	if (PG_PROTOCOL_MAJOR(FrontendProtocol) < 3)
+		ereport(FATAL,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("SASL authentication is not supported in protocol version 2")));
+
+	/*
+	 * Send first the authentication request to user. As for MD5, we want
+	 * the user to send its password first even if nothing has been done
+	 * yet.  This avoids consistency issues where a user would be able to
+	 * guess that a server is expecting SASL or MD5 depending on the answer
+	 * given by the backend without the user providing a password first.
+	 */
+	sendAuthRequest(port, AUTH_REQ_SASL, SCRAM_SHA256_NAME,
+					strlen(SCRAM_SHA256_NAME) + 1);
+
+	/* Initialize the status tracker for message exchanges */
+	scram_opaq = pg_be_scram_init(port->user_name);
+
+	/*
+	 * 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 = pg_be_scram_exchange(scram_opaq, buf.data, buf.len,
+									  &output, &outputlen, logdetail);
+
+		/* 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);
+
+	/* Oops, Something bad happened */
+	if (result != SASL_EXCHANGE_SUCCESS)
+	{
+		/* an error should have been set during the exchange checks */
+		Assert(*logdetail != NULL);
+		return STATUS_ERROR;
+	}
+
+	return STATUS_OK;
+}
 
 
 /*----------------------------------------------------------------
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index d84a180..1c9db9a 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -1,8 +1,8 @@
 /*-------------------------------------------------------------------------
  *
  * crypt.c
- *	  Look into the password file and check the encrypted password with
- *	  the one passed in from the frontend.
+ *	  Set of routines to look into the password file and check the
+ *	  encrypted password with the one passed in from the frontend.
  *
  * Original coding by Todd A. Brandys
  *
@@ -30,30 +30,31 @@
 
 
 /*
- * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
- * In the error case, optionally store a palloc'd string at *logdetail
- * that will be sent to the postmaster log (but not the client).
+ * Fetch information of a given role necessary to check password data,
+ * and returns status code describing the error. In the case of an error,
+ * store a palloc'd string at *logdetail that will be sent to the postmaster
+ * log (but not the client!).
  */
 int
-md5_crypt_verify(const Port *port, const char *role, char *client_pass,
+get_role_details(const char *role,
+				 char **password,
+				 TimestampTz *vuntil,
+				 bool *vuntil_null,
 				 char **logdetail)
 {
-	int			retval = STATUS_ERROR;
-	char	   *shadow_pass,
-			   *crypt_pwd;
-	TimestampTz vuntil = 0;
-	char	   *crypt_client_pass = client_pass;
 	HeapTuple	roleTup;
 	Datum		datum;
 	bool		isnull;
 
+	*vuntil = 0;
+	*vuntil_null = true;
+
 	/* Get role info from pg_authid */
 	roleTup = SearchSysCache1(AUTHNAME, PointerGetDatum(role));
 	if (!HeapTupleIsValid(roleTup))
 	{
-		*logdetail = psprintf(_("Role \"%s\" does not exist."),
-							  role);
-		return STATUS_ERROR;	/* no such user */
+		*logdetail = psprintf(_("Role \"%s\" does not exist."), role);
+		return PG_ROLE_NOT_DEFINED;	/* no such user */
 	}
 
 	datum = SysCacheGetAttr(AUTHNAME, roleTup,
@@ -63,24 +64,52 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 		ReleaseSysCache(roleTup);
 		*logdetail = psprintf(_("User \"%s\" has no password assigned."),
 							  role);
-		return STATUS_ERROR;	/* user has no password */
+		return PG_ROLE_NO_PASSWORD;	/* user has no password */
 	}
-	shadow_pass = TextDatumGetCString(datum);
+	*password = TextDatumGetCString(datum);
 
 	datum = SysCacheGetAttr(AUTHNAME, roleTup,
 							Anum_pg_authid_rolvaliduntil, &isnull);
 	if (!isnull)
-		vuntil = DatumGetTimestampTz(datum);
+	{
+		*vuntil = DatumGetTimestampTz(datum);
+		*vuntil_null = false;
+	}
 
 	ReleaseSysCache(roleTup);
 
-	if (*shadow_pass == '\0')
+	if (**password == '\0')
 	{
 		*logdetail = psprintf(_("User \"%s\" has an empty password."),
 							  role);
-		return STATUS_ERROR;	/* empty password */
+		pfree(*password);
+		return PG_ROLE_EMPTY_PASSWORD;	/* empty password */
 	}
 
+	return PG_ROLE_OK;
+}
+
+/*
+ * Check given password for given user, and return STATUS_OK or STATUS_ERROR.
+ * In the error case, optionally store a palloc'd string at *logdetail
+ * that will be sent to the postmaster log (but not the client).
+ */
+int
+md5_crypt_verify(const Port *port, const char *role, char *client_pass,
+				 char **logdetail)
+{
+	int			retval = STATUS_ERROR;
+	char	   *shadow_pass,
+			   *crypt_pwd;
+	TimestampTz vuntil;
+	char	   *crypt_client_pass = client_pass;
+	bool		vuntil_null;
+
+	/* fetch details about role needed for password checks */
+	if (get_role_details(role, &shadow_pass, &vuntil, &vuntil_null,
+						 logdetail) != PG_ROLE_OK)
+		return STATUS_ERROR;
+
 	/*
 	 * Compare with the encrypted or plain password depending on the
 	 * authentication method being used for this connection.  (We do not
@@ -152,7 +181,7 @@ md5_crypt_verify(const Port *port, const char *role, char *client_pass,
 		/*
 		 * Password OK, now check to be sure we are not past rolvaliduntil
 		 */
-		if (isnull)
+		if (vuntil_null)
 			retval = STATUS_OK;
 		else if (vuntil < GetCurrentTimestamp())
 		{
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 e0fbfcb..73f7973 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/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 3c695c1..53d585e 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -401,6 +401,7 @@ static const struct config_enum_entry force_parallel_mode_options[] = {
 static const struct config_enum_entry password_encryption_options[] = {
 	{"plain", PASSWORD_TYPE_PLAINTEXT, false},
 	{"md5", PASSWORD_TYPE_MD5, false},
+	{"scram", PASSWORD_TYPE_SCRAM, false},
 	{"off", PASSWORD_TYPE_PLAINTEXT, false},
 	{"on", PASSWORD_TYPE_MD5, false},
 	{"true", PASSWORD_TYPE_MD5, true},
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 7c2daa5..4ce87aa 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		# md5 or plain
+#password_encryption = md5		# md5, scram or plain
 #db_user_namespace = off
 #row_security = on
 
diff --git a/src/common/Makefile b/src/common/Makefile
index 49e41cf..971ddd5 100644
--- a/src/common/Makefile
+++ b/src/common/Makefile
@@ -42,7 +42,7 @@ override CPPFLAGS += -DVAL_LIBS="\"$(LIBS)\""
 
 OBJS_COMMON = base64.o config_info.o controldata_utils.o exec.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 += sha2_openssl.o
diff --git a/src/common/scram-common.c b/src/common/scram-common.c
new file mode 100644
index 0000000..fb9a0b8
--- /dev/null
+++ b/src/common/scram-common.c
@@ -0,0 +1,195 @@
+/*-------------------------------------------------------------------------
+ * 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 102c2a5..1ff441a 100644
--- a/src/include/commands/user.h
+++ b/src/include/commands/user.h
@@ -23,7 +23,8 @@
 typedef enum PasswordType
 {
 	PASSWORD_TYPE_PLAINTEXT = 0,
-	PASSWORD_TYPE_MD5
+	PASSWORD_TYPE_MD5,
+	PASSWORD_TYPE_SCRAM
 } PasswordType;
 
 extern int	Password_encryption;	/* GUC */
diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h
new file mode 100644
index 0000000..e9028fb
--- /dev/null
+++ b/src/include/common/scram-common.h
@@ -0,0 +1,56 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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/relpath.h
+ *
+ *-------------------------------------------------------------------------
+ */
+#ifndef SCRAM_COMMON_H
+#define SCRAM_COMMON_H
+
+#include "common/sha2.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 */
+#define SCRAM_NONCE_LEN				10
+
+/* length of salt when generating new verifiers */
+#define SCRAM_SALT_LEN				SCRAM_NONCE_LEN
+
+/* number of bytes used when sending iteration number during exchange */
+#define SCRAM_ITERATION_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/crypt.h b/src/include/libpq/crypt.h
index 5725bb4..522cfa2 100644
--- a/src/include/libpq/crypt.h
+++ b/src/include/libpq/crypt.h
@@ -15,6 +15,14 @@
 
 #include "libpq/libpq-be.h"
 
+/* Detailed error codes for get_role_details()  */
+#define PG_ROLE_OK				0
+#define PG_ROLE_NOT_DEFINED		1
+#define PG_ROLE_NO_PASSWORD		2
+#define PG_ROLE_EMPTY_PASSWORD	3
+
+extern int get_role_details(const char *role, char **password,
+				 TimestampTz *vuntil, bool *vuntil_null, 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 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..046e200 100644
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -144,7 +144,9 @@ 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, size of
+								 * SCRAM_SALT_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 9648422..bbcb2f7 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..58638eb
--- /dev/null
+++ b/src/include/libpq/scram.h
@@ -0,0 +1,35 @@
+/*-------------------------------------------------------------------------
+ *
+ * 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"
+
+/* Status codes for message exchange */
+#define SASL_EXCHANGE_CONTINUE		0
+#define SASL_EXCHANGE_SUCCESS		1
+#define SASL_EXCHANGE_FAILURE		2
+
+/* Routines dedicated to authentication */
+extern void *pg_be_scram_init(char *username);
+extern int pg_be_scram_exchange(void *opaq, char *input, int inputlen,
+					char **output, int *outputlen, char **logdetail);
+
+/* Routines to handle and check SCRAM-SHA-256 verifier */
+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..2224ada 100644
--- a/src/interfaces/libpq/.gitignore
+++ b/src/interfaces/libpq/.gitignore
@@ -1,4 +1,5 @@
 /exports.list
+/base64.c
 /chklocale.c
 /crypt.c
 /getaddrinfo.c
@@ -7,8 +8,12 @@
 /inet_net_ntop.c
 /noblock.c
 /open.c
+/pg_strong_random.c
 /pgstrcasecmp.c
 /pqsignal.c
+/scram-common.c
+/sha2.c
+/sha2_openssl.c
 /snprintf.c
 /strerror.c
 /strlcpy.c
diff --git a/src/interfaces/libpq/Makefile b/src/interfaces/libpq/Makefile
index b1789eb..460b0a1 100644
--- a/src/interfaces/libpq/Makefile
+++ b/src/interfaces/libpq/Makefile
@@ -31,21 +31,23 @@ 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
-OBJS += chklocale.o inet_net_ntop.o noblock.o pgstrcasecmp.o pqsignal.o \
-	thread.o
+OBJS += chklocale.o inet_net_ntop.o noblock.o pg_strong_random.o \
+	pgstrcasecmp.o pqsignal.o thread.o
 # libpgport C files that are needed if identified by configure
 OBJS += $(filter crypt.o getaddrinfo.o getpeereid.o inet_aton.o open.o system.o snprintf.o strerror.o strlcpy.o win32error.o win32setlocale.o, $(LIBOBJS))
 # src/backend/utils/mb
 OBJS += encnames.o wchar.o
 # src/common
-OBJS += ip.o md5.o
+OBJS += base64.o ip.o md5.o scram-common.o
 
 ifeq ($(with_openssl),yes)
-OBJS += fe-secure-openssl.o
+OBJS += fe-secure-openssl.o sha2_openssl.o
+else
+OBJS += sha2.o
 endif
 
 ifeq ($(PORTNAME), cygwin)
@@ -93,7 +95,7 @@ backend_src = $(top_srcdir)/src/backend
 # For some libpgport modules, this only happens if configure decides
 # the module is needed (see filter hack in OBJS, above).
 
-chklocale.c crypt.c getaddrinfo.c getpeereid.c inet_aton.c inet_net_ntop.c noblock.c open.c system.c pgsleep.c pgstrcasecmp.c pqsignal.c snprintf.c strerror.c strlcpy.c thread.c win32error.c win32setlocale.c: % : $(top_srcdir)/src/port/%
+chklocale.c crypt.c getaddrinfo.c getpeereid.c inet_aton.c inet_net_ntop.c noblock.c open.c system.c pgsleep.c pg_strong_random.c pgstrcasecmp.c pqsignal.c snprintf.c strerror.c strlcpy.c thread.c win32error.c win32setlocale.c: % : $(top_srcdir)/src/port/%
 	rm -f $@ && $(LN_S) $< .
 
 ip.c md5.c: % : $(top_srcdir)/src/common/%
@@ -102,6 +104,9 @@ ip.c md5.c: % : $(top_srcdir)/src/common/%
 encnames.c wchar.c: % : $(backend_src)/utils/mb/%
 	rm -f $@ && $(LN_S) $< .
 
+base64.c scram-common.c sha2.c sha2_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..61b1020
--- /dev/null
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -0,0 +1,562 @@
+/*-------------------------------------------------------------------------
+ *
+ * fe-auth-scram.c
+ *	   The front-end (client) implementation of SCRAM authentication.
+ *
+ * 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/base64.h"
+#include "common/scram-common.h"
+#include "fe-auth.h"
+
+/*
+ * Status of exchange messages used for SCRAM authentication via the
+ * SASL protocol.
+ */
+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,
+										PQExpBuffer errormessage);
+static char *build_client_final_message(fe_scram_state *state,
+										PQExpBuffer errormessage);
+static bool verify_server_proof(fe_scram_state *state);
+static bool 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 SCRAM exchange status.
+ */
+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 status
+ */
+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, errorMessage);
+			if (*output == NULL)
+			{
+				*done = true;
+				*success = false;
+				break;
+			}
+			*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;
+				*success = false;
+				break;
+			}
+			*output = build_client_final_message(state, errorMessage);
+			if (*output == NULL)
+			{
+				*done = true;
+				*success = false;
+				break;
+			}
+			*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;
+				*success = false;
+				break;
+			}
+			*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\n"));
+	}
+}
+
+/*
+ * Read value for an attribute part of a SASL message.
+ *
+ * This routine is able to handle error messages e= sent by the server
+ * during the exchange of SASL messages.  Returns NULL in case of error,
+ * setting errorMessage as well.
+ */
+static char *
+read_attr_value(char **input, char attr, PQExpBuffer errorMessage)
+{
+	char		*begin = *input;
+	char		*end;
+
+	if (*begin == 'e')
+	{
+		/* Received an error */
+		begin++;
+		if (*begin != '=')
+		{
+			printfPQExpBuffer(errorMessage,
+				libpq_gettext("malformed SCRAM message (expected = in attr e)\n"));
+			return NULL;
+		}
+
+		begin++;
+		printfPQExpBuffer(errorMessage,
+			libpq_gettext("error received from server in SASL exchange: %s\n"),
+						  begin);
+		return NULL;
+	}
+
+	if (*begin != attr)
+	{
+		printfPQExpBuffer(errorMessage,
+			libpq_gettext("malformed SCRAM message (%c expected)\n"),
+						  attr);
+		return NULL;
+	}
+	begin++;
+
+	if (*begin != '=')
+	{
+		printfPQExpBuffer(errorMessage,
+			libpq_gettext("malformed SCRAM message (expected = in attr %c)\n"),
+						  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.
+ */
+static char *
+build_client_first_message(fe_scram_state *state, PQExpBuffer errormessage)
+{
+	char		nonce[SCRAM_NONCE_LEN + 1];
+	char	   *buf;
+	char		msglen;
+
+	if (!generate_nonce(nonce, SCRAM_NONCE_LEN))
+	{
+		printfPQExpBuffer(errormessage, libpq_gettext("failed to generate nonce\n"));
+		return NULL;
+	}
+
+	/* Generate message */
+	msglen = 5 + strlen(state->username) + 3 + strlen(nonce);
+	buf = malloc(msglen + 1);
+	if (buf == NULL)
+	{
+		printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
+		return NULL;
+	}
+	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)
+	{
+		printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
+		return NULL;
+	}
+
+	return buf;
+}
+
+/*
+ * Build the final exchange message sent from the client.
+ */
+static char *
+build_client_final_message(fe_scram_state *state, PQExpBuffer errormessage)
+{
+	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 (pg_b64_enc_len(SCRAM_KEY_LEN) > sizeof(client_proof_base64))
+	{
+		printfPQExpBuffer(errormessage,
+			libpq_gettext("malformed client proof (%d found)\n"),
+						  pg_b64_enc_len(SCRAM_KEY_LEN));
+		return NULL;
+	}
+
+	client_proof_len = pg_b64_encode((char *) client_proof,
+									 SCRAM_KEY_LEN,
+									 client_proof_base64);
+	if (client_proof_len < 0)
+	{
+		printfPQExpBuffer(errormessage,
+			  libpq_gettext("failure when encoding client proof\n"));
+		return NULL;
+	}
+	client_proof_base64[client_proof_len] = '\0';
+
+	state->client_final_message_without_proof =
+		strdup(client_final_message_without_proof);
+	if (state->client_final_message_without_proof == NULL)
+	{
+		printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
+		return NULL;
+	}
+
+	snprintf(buf, sizeof(buf), "%s,p=%s",
+			 client_final_message_without_proof,
+			 client_proof_base64);
+
+	return strdup(buf);
+}
+
+/*
+ * Read the first exchange message coming from the server.
+ */
+static bool
+read_server_first_message(fe_scram_state *state,
+						  char *input,
+						  PQExpBuffer errormessage)
+{
+	char	   *iterations_str;
+	char	   *endptr;
+	char	   *encoded_salt;
+	char	   *server_nonce;
+
+	state->server_first_message = strdup(input);
+	if (!state->server_first_message)
+	{
+		printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
+		return false;
+	}
+
+	/* parse the message */
+	server_nonce = read_attr_value(&input, 'r', errormessage);
+	if (server_nonce == NULL)
+	{
+		/* read_attr_value() has generated an error string */
+		return false;
+	}
+
+	state->server_nonce = strdup(server_nonce);
+	if (state->server_nonce == NULL)
+	{
+		printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
+		return false;
+	}
+
+	encoded_salt = read_attr_value(&input, 's', errormessage);
+	if (encoded_salt == NULL)
+	{
+		/* read_attr_value() has generated an error string */
+		return false;
+	}
+	state->salt = malloc(pg_b64_dec_len(strlen(encoded_salt)));
+	if (state->salt == NULL)
+	{
+		printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
+		return false;
+	}
+	state->saltlen = pg_b64_decode(encoded_salt,
+								   strlen(encoded_salt),
+								   state->salt);
+	if (state->saltlen != SCRAM_SALT_LEN)
+	{
+		printfPQExpBuffer(errormessage,
+			  libpq_gettext("invalid salt length: found %d, expected %d\n"),
+			  state->saltlen, SCRAM_SALT_LEN);
+		return false;
+	}
+
+	iterations_str = read_attr_value(&input, 'i', errormessage);
+	if (iterations_str == NULL || *input != '\0')
+	{
+		/* read_attr_value() has generated an error string */
+		return false;
+	}
+	state->iterations = strtol(iterations_str, &endptr, SCRAM_ITERATION_LEN);
+	if (*endptr != '\0')
+	{
+		printfPQExpBuffer(errormessage,
+			  libpq_gettext("malformed SCRAM message for number of iterations\n"));
+		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;
+
+	state->server_final_message = strdup(input);
+	if (!state->server_final_message)
+	{
+		printfPQExpBuffer(errormessage, libpq_gettext("out of memory\n"));
+		return false;
+	}
+
+	/* parse the message */
+	encoded_server_proof = read_attr_value(&input, 'v', errormessage);
+	if (encoded_server_proof == NULL || *input != '\0')
+	{
+		/* read_attr_value() has generated an error message */
+		return false;
+	}
+
+	server_proof_len = pg_b64_decode(encoded_server_proof,
+									 strlen(encoded_server_proof),
+									 state->ServerProof);
+	if (server_proof_len != SCRAM_KEY_LEN)
+	{
+		printfPQExpBuffer(errormessage,
+						  libpq_gettext("invalid server proof\n"));
+		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.
+ */
+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.
+ * Returns true of nonce has been succesfully generated, and false
+ * otherwise.
+ */
+static bool
+generate_nonce(char *buf, int len)
+{
+	int		count = 0;
+
+	/* compute the salt to use for computing responses */
+	while (count < len)
+	{
+		char	byte;
+
+		if (!pg_strong_random(&byte, 1))
+			return false;
+
+		/*
+		 * Only ASCII printable characters, except commas are accepted in
+		 * the nonce.
+		 */
+		if (byte < '!' || byte > '~' || byte == ',')
+			continue;
+
+		buf[count] = byte;
+		count++;
+	}
+
+	buf[len] = '\0';
+	return true;
+}
diff --git a/src/interfaces/libpq/fe-auth.c b/src/interfaces/libpq/fe-auth.c
index 19171fb..991e524 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
@@ -704,6 +783,35 @@ 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)
+			{
+				/* Use error message already if any set */
+				if (conn->errorMessage.len == 0)
+					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 b4f9ad7..523e9bf 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2638,6 +2638,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
@@ -3240,6 +3283,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 854ec89..27cc74c 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -450,7 +450,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/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index c5b737a..0f19d1c 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -112,7 +112,7 @@ sub mkvcbuild
 	our @pgcommonallfiles = qw(
 	  base64.c config_info.c controldata_utils.c exec.c ip.c keywords.c
 	  md5.c pg_lzcompress.c pgfnames.c psprintf.c relpath.c rmtree.c
-	  string.c username.c wait_error.c);
+	  scram-common.c string.c username.c wait_error.c);
 
 	if ($solution->{options}->{openssl})
 	{
@@ -233,10 +233,16 @@ sub mkvcbuild
 	$libpq->AddReference($libpgport);
 
    # The OBJS scraper doesn't know about ifdefs, so remove fe-secure-openssl.c
-   # if building without OpenSSL
+   # and sha2_openssl.c if building without OpenSSL, and remove sha2.c if
+   # building with OpenSSL.
 	if (!$solution->{options}->{openssl})
 	{
 		$libpq->RemoveFile('src/interfaces/libpq/fe-secure-openssl.c');
+		$libpq->RemoveFile('src/common/sha2_openssl.c');
+	}
+	else
+	{
+		$libpq->RemoveFile('src/common/sha2.c');
 	}
 
 	my $libpqwalreceiver =
-- 
2.10.2

