+ linkend="postgres-fdw-options-connection-management"/> for one such
+ implementation. It is not meant to be specified directly by users or
+ client applications.
+
+
+
+
+
+ scram_server_key
+
+ The base64-encoded SCRAM server key. This can be used by foreign-data
+ wrappers or similar middleware to enable pass-through SCRAM
+ authentication. See
+ linkend="postgres-fdw-options-connection-management"/> for one such
+ implementation. It is not meant to be specified directly by users or
+ client applications.
+
+
+
+
service
+
+ use_scram_passthrough (boolean)
+
+ This option controls whether postgres_fdw will
+ use the SCRAM pass-through authentication to connect to the foreign
+ server. With SCRAM pass-through authentication,
+ postgres_fdw uses SCRAM-hashed secrets instead of
+ plain-text user passwords to connect to the remote server. This
+ avoids storing plain-text user passwords in PostgreSQL system
+ catalogs.
+
+
+ To use SCRAM pass-through authentication:
+
+
+ The remote server must request SCRAM authentication. (If desired,
+ enforce this on the client side (FDW side) with the option
+ require_auth.) If another authentication method
+ is requested by the server, then that one will be used normally.
+
+
+
+
+ The remote server can be of any PostgreSQL version that supports
+ SCRAM. Support for use_scram_passthrough is
+ only required on the client side (FDW side).
+
+
+
+
+ The user mapping password is not used. (It could be set to support
+ other authentication methods, but that would arguably violate the
+ point of this feature, which is to avoid storing plain-text
+ passwords.)
+
+
+
+
+ The server running postgres_fdw and the remote
+ server must have identical SCRAM secrets (encrypted passwords) for
+ the user being used on postgres_fdw to
+ authenticate on the foreign server (same salt and iterations, not
+ merely the same password).
+
+
+ As a corollary, if FDW connections to multiple hosts are to be
+ made, for example for partitioned foreign tables/sharding, then all
+ hosts must have identical SCRAM secrets for the users involved.
+
+
+
+
+ The current session on the PostgreSQL instance that makes the
+ outgoing FDW connections also must also use SCRAM authentication
+ for its incoming client connection. (Hence
+ pass-through
: SCRAM must be used going in and out.)
+ This is a technical requirement of the SCRAM protocol.
+
+
+
+
+
+
+
#include "libpq/crypt.h"
#include "libpq/sasl.h"
#include "libpq/scram.h"
+#include "miscadmin.h"
static void scram_get_mechanisms(Port *port, StringInfo buf);
static void *scram_init(Port *port, const char *selected_mech,
int iterations;
char *salt; /* base64-encoded */
+ uint8 ClientKey[SCRAM_MAX_KEY_LEN];
uint8 StoredKey[SCRAM_MAX_KEY_LEN];
uint8 ServerKey[SCRAM_MAX_KEY_LEN];
if (*output)
*outputlen = strlen(*output);
+ if (result == PG_SASL_EXCHANGE_SUCCESS && state->state == SCRAM_AUTH_FINISHED)
+ {
+ memcpy(MyProcPort->scram_ClientKey, state->ClientKey, sizeof(MyProcPort->scram_ClientKey));
+ memcpy(MyProcPort->scram_ServerKey, state->ServerKey, sizeof(MyProcPort->scram_ServerKey));
+ MyProcPort->has_scram_keys = true;
+ }
+
return result;
}
verify_client_proof(scram_state *state)
{
uint8 ClientSignature[SCRAM_MAX_KEY_LEN];
- uint8 ClientKey[SCRAM_MAX_KEY_LEN];
uint8 client_StoredKey[SCRAM_MAX_KEY_LEN];
pg_hmac_ctx *ctx = pg_hmac_create(state->hash_type);
int i;
/* Extract the ClientKey that the client calculated from the proof */
for (i = 0; i < state->key_length; i++)
- ClientKey[i] = state->ClientProof[i] ^ ClientSignature[i];
+ state->ClientKey[i] = state->ClientProof[i] ^ ClientSignature[i];
/* Hash it one more time, and compare with StoredKey */
- if (scram_H(ClientKey, state->hash_type, state->key_length,
+ if (scram_H(state->ClientKey, state->hash_type, state->key_length,
client_StoredKey, &errstr) < 0)
elog(ERROR, "could not hash stored key: %s", errstr);
#ifndef LIBPQ_BE_H
#define LIBPQ_BE_H
+#include "common/scram-common.h"
+
#include
#ifdef USE_OPENSSL
#include
int keepalives_count;
int tcp_user_timeout;
+ /*
+ * SCRAM structures.
+ */
+ uint8 scram_ClientKey[SCRAM_MAX_KEY_LEN];
+ uint8 scram_ServerKey[SCRAM_MAX_KEY_LEN];
+ bool has_scram_keys; /* true if the above two are valid */
+
/*
* GSSAPI structures.
*/
return NULL;
}
- /* Normalize the password with SASLprep, if possible */
- rc = pg_saslprep(password, &prep_password);
- if (rc == SASLPREP_OOM)
- {
- free(state->sasl_mechanism);
- free(state);
- return NULL;
- }
- if (rc != SASLPREP_SUCCESS)
+ if (password)
{
- prep_password = strdup(password);
- if (!prep_password)
+ /* Normalize the password with SASLprep, if possible */
+ rc = pg_saslprep(password, &prep_password);
+ if (rc == SASLPREP_OOM)
{
free(state->sasl_mechanism);
free(state);
return NULL;
}
+ if (rc != SASLPREP_SUCCESS)
+ {
+ prep_password = strdup(password);
+ if (!prep_password)
+ {
+ free(state->sasl_mechanism);
+ free(state);
+ return NULL;
+ }
+ }
+ state->password = prep_password;
}
- state->password = prep_password;
return state;
}
return false;
}
- /*
- * Calculate SaltedPassword, and store it in 'state' so that we can reuse
- * it later in verify_server_signature.
- */
- if (scram_SaltedPassword(state->password, state->hash_type,
- state->key_length, state->salt, state->saltlen,
- state->iterations, state->SaltedPassword,
- errstr) < 0 ||
- scram_ClientKey(state->SaltedPassword, state->hash_type,
- state->key_length, ClientKey, errstr) < 0 ||
- scram_H(ClientKey, state->hash_type, state->key_length,
- StoredKey, errstr) < 0)
- {
- /* errstr is already filled here */
+ if (state->conn->scram_client_key_binary)
+ {
+ memcpy(ClientKey, state->conn->scram_client_key_binary, SCRAM_MAX_KEY_LEN);
+ }
+ else
+ {
+ /*
+ * Calculate SaltedPassword, and store it in 'state' so that we can
+ * reuse it later in verify_server_signature.
+ */
+ if (scram_SaltedPassword(state->password, state->hash_type,
+ state->key_length, state->salt, state->saltlen,
+ state->iterations, state->SaltedPassword,
+ errstr) < 0 ||
+ scram_ClientKey(state->SaltedPassword, state->hash_type,
+ state->key_length, ClientKey, errstr) < 0)
+ {
+ /* errstr is already filled here */
+ pg_hmac_free(ctx);
+ return false;
+ }
+ }
+
+ if (scram_H(ClientKey, state->hash_type, state->key_length, StoredKey, errstr) < 0)
+ {
pg_hmac_free(ctx);
return false;
}
return false;
}
- if (scram_ServerKey(state->SaltedPassword, state->hash_type,
- state->key_length, ServerKey, errstr) < 0)
+ if (state->conn->scram_server_key_binary)
{
- /* errstr is filled already */
- pg_hmac_free(ctx);
- return false;
+ memcpy(ServerKey, state->conn->scram_server_key_binary, SCRAM_MAX_KEY_LEN);
+ }
+ else
+ {
+ if (scram_ServerKey(state->SaltedPassword, state->hash_type,
+ state->key_length, ServerKey, errstr) < 0)
+ {
+ /* errstr is filled already */
+ pg_hmac_free(ctx);
+ return false;
+ }
}
/* calculate ServerSignature */
* First, select the password to use for the exchange, complaining if
* there isn't one and the selected SASL mechanism needs it.
*/
- if (conn->password_needed)
+ if (conn->password_needed && !conn->scram_client_key_binary)
{
password = conn->connhost[conn->whichhost].password;
if (password == NULL)
#include
#include
+#include "common/base64.h"
#include "common/ip.h"
#include "common/link-canary.h"
#include "common/scram-common.h"
"Load-Balance-Hosts", "", 8, /* sizeof("disable") = 8 */
offsetof(struct pg_conn, load_balance_hosts)},
+ {"scram_client_key", NULL, NULL, NULL, "SCRAM-Client-Key", "D", SCRAM_MAX_KEY_LEN * 2,
+ offsetof(struct pg_conn, scram_client_key)},
+
+ {"scram_server_key", NULL, NULL, NULL, "SCRAM-Server-Key", "D", SCRAM_MAX_KEY_LEN * 2,
+ offsetof(struct pg_conn, scram_server_key)},
+
/* Terminating entry --- MUST BE LAST */
{NULL, NULL, NULL, NULL,
NULL, NULL, 0}
else
conn->target_server_type = SERVER_TYPE_ANY;
+ if (conn->scram_client_key)
+ {
+ int len;
+
+ len = pg_b64_dec_len(strlen(conn->scram_client_key));
+ /* Consider the zero-terminator */
+ if (len != SCRAM_MAX_KEY_LEN + 1)
+ {
+ libpq_append_conn_error(conn, "invalid SCRAM client key length: %d", len);
+ return false;
+ }
+ conn->scram_client_key_len = len;
+ conn->scram_client_key_binary = malloc(len);
+ if (!conn->scram_client_key_binary)
+ goto oom_error;
+ pg_b64_decode(conn->scram_client_key, strlen(conn->scram_client_key),
+ conn->scram_client_key_binary, len);
+ }
+
+ if (conn->scram_server_key)
+ {
+ int len;
+
+ len = pg_b64_dec_len(strlen(conn->scram_server_key));
+ /* Consider the zero-terminator */
+ if (len != SCRAM_MAX_KEY_LEN + 1)
+ {
+ libpq_append_conn_error(conn, "invalid SCRAM server key length: %d", len);
+ return false;
+ }
+ conn->scram_server_key_len = len;
+ conn->scram_server_key_binary = malloc(len);
+ if (!conn->scram_server_key_binary)
+ goto oom_error;
+ pg_b64_decode(conn->scram_server_key, strlen(conn->scram_server_key),
+ conn->scram_server_key_binary, len);
+ }
+
/*
* validate load_balance_hosts option, and set load_balance_type
*/
free(conn->rowBuf);
free(conn->target_session_attrs);
free(conn->load_balance_hosts);
+ free(conn->scram_client_key);
+ free(conn->scram_server_key);
termPQExpBuffer(&conn->errorMessage);
termPQExpBuffer(&conn->workBuffer);
char *target_session_attrs; /* desired session properties */
char *require_auth; /* name of the expected auth method */
char *load_balance_hosts; /* load balance over hosts */
+ char *scram_client_key; /* base64-encoded SCRAM client key */
+ char *scram_server_key; /* base64-encoded SCRAM server key */
bool cancelRequest; /* true if this connection is used to send a
* cancel request, instead of being a normal
AddrInfo *addr; /* the array of addresses for the currently
* tried host */
bool send_appname; /* okay to send application_name? */
+ size_t scram_client_key_len;
+ void *scram_client_key_binary; /* binary SCRAM client key */
+ size_t scram_server_key_len;
+ void *scram_server_key_binary; /* binary SCRAM server key */
/* Miscellaneous stuff */
int be_pid; /* PID of backend --- needed for cancels */