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