mirror of
https://github.com/postgres/postgres.git
synced 2025-06-13 07:41:39 +03:00
postgres_fdw: SCRAM authentication pass-through
This enables SCRAM authentication for postgres_fdw when connecting to a foreign server without having to store a plain-text password on user mapping options. This is done by saving the SCRAM ClientKey and ServeryKey from the client authentication and using those instead of the plain-text password for the server-side SCRAM exchange. The new foreign-server or user-mapping option "use_scram_passthrough" enables this. Co-authored-by: Matheus Alcantara <mths.dev@pm.me> Co-authored-by: Peter Eisentraut <peter@eisentraut.org> Discussion: https://www.postgresql.org/message-id/flat/27b29a35-9b96-46a9-bc1a-914140869dac@gmail.com
This commit is contained in:
@ -17,6 +17,7 @@ EXTENSION = postgres_fdw
|
||||
DATA = postgres_fdw--1.0.sql postgres_fdw--1.0--1.1.sql postgres_fdw--1.1--1.2.sql
|
||||
|
||||
REGRESS = postgres_fdw query_cancel
|
||||
TAP_TESTS = 1
|
||||
|
||||
ifdef USE_PGXS
|
||||
PG_CONFIG = pg_config
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "access/xact.h"
|
||||
#include "catalog/pg_user_mapping.h"
|
||||
#include "commands/defrem.h"
|
||||
#include "common/base64.h"
|
||||
#include "funcapi.h"
|
||||
#include "libpq/libpq-be.h"
|
||||
#include "libpq/libpq-be-fe-helpers.h"
|
||||
@ -177,6 +178,7 @@ static void pgfdw_finish_abort_cleanup(List *pending_entries,
|
||||
static void pgfdw_security_check(const char **keywords, const char **values,
|
||||
UserMapping *user, PGconn *conn);
|
||||
static bool UserMappingPasswordRequired(UserMapping *user);
|
||||
static bool UseScramPassthrough(ForeignServer *server, UserMapping *user);
|
||||
static bool disconnect_cached_connections(Oid serverid);
|
||||
static void postgres_fdw_get_connections_internal(FunctionCallInfo fcinfo,
|
||||
enum pgfdwVersion api_version);
|
||||
@ -485,7 +487,7 @@ connect_pg_server(ForeignServer *server, UserMapping *user)
|
||||
* for application_name, fallback_application_name, client_encoding,
|
||||
* end marker.
|
||||
*/
|
||||
n = list_length(server->options) + list_length(user->options) + 4;
|
||||
n = list_length(server->options) + list_length(user->options) + 4 + 2;
|
||||
keywords = (const char **) palloc(n * sizeof(char *));
|
||||
values = (const char **) palloc(n * sizeof(char *));
|
||||
|
||||
@ -554,9 +556,36 @@ connect_pg_server(ForeignServer *server, UserMapping *user)
|
||||
values[n] = GetDatabaseEncodingName();
|
||||
n++;
|
||||
|
||||
if (MyProcPort->has_scram_keys && UseScramPassthrough(server, user))
|
||||
{
|
||||
int len;
|
||||
|
||||
keywords[n] = "scram_client_key";
|
||||
len = pg_b64_enc_len(sizeof(MyProcPort->scram_ClientKey));
|
||||
/* don't forget the zero-terminator */
|
||||
values[n] = palloc0(len + 1);
|
||||
pg_b64_encode((const char *) MyProcPort->scram_ClientKey,
|
||||
sizeof(MyProcPort->scram_ClientKey),
|
||||
(char *) values[n], len);
|
||||
n++;
|
||||
|
||||
keywords[n] = "scram_server_key";
|
||||
len = pg_b64_enc_len(sizeof(MyProcPort->scram_ServerKey));
|
||||
/* don't forget the zero-terminator */
|
||||
values[n] = palloc0(len + 1);
|
||||
pg_b64_encode((const char *) MyProcPort->scram_ServerKey,
|
||||
sizeof(MyProcPort->scram_ServerKey),
|
||||
(char *) values[n], len);
|
||||
n++;
|
||||
}
|
||||
|
||||
keywords[n] = values[n] = NULL;
|
||||
|
||||
/* verify the set of connection parameters */
|
||||
/*
|
||||
* Verify the set of connection parameters only if scram pass-through
|
||||
* is not being used because the password is not necessary.
|
||||
*/
|
||||
if (!(MyProcPort->has_scram_keys && UseScramPassthrough(server, user)))
|
||||
check_conn_params(keywords, values, user);
|
||||
|
||||
/* first time, allocate or get the custom wait event */
|
||||
@ -575,7 +604,11 @@ connect_pg_server(ForeignServer *server, UserMapping *user)
|
||||
server->servername),
|
||||
errdetail_internal("%s", pchomp(PQerrorMessage(conn)))));
|
||||
|
||||
/* Perform post-connection security checks */
|
||||
/*
|
||||
* Perform post-connection security checks only if scram pass-through
|
||||
* is not being used because the password is not necessary.
|
||||
*/
|
||||
if (!(MyProcPort->has_scram_keys && UseScramPassthrough(server, user)))
|
||||
pgfdw_security_check(keywords, values, user, conn);
|
||||
|
||||
/* Prepare new session for use */
|
||||
@ -629,6 +662,30 @@ UserMappingPasswordRequired(UserMapping *user)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
UseScramPassthrough(ForeignServer *server, UserMapping *user)
|
||||
{
|
||||
ListCell *cell;
|
||||
|
||||
foreach(cell, server->options)
|
||||
{
|
||||
DefElem *def = (DefElem *) lfirst(cell);
|
||||
|
||||
if (strcmp(def->defname, "use_scram_passthrough") == 0)
|
||||
return defGetBoolean(def);
|
||||
}
|
||||
|
||||
foreach(cell, user->options)
|
||||
{
|
||||
DefElem *def = (DefElem *) lfirst(cell);
|
||||
|
||||
if (strcmp(def->defname, "use_scram_passthrough") == 0)
|
||||
return defGetBoolean(def);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* For non-superusers, insist that the connstr specify a password or that the
|
||||
* user provided their own GSSAPI delegated credentials. This
|
||||
@ -666,7 +723,7 @@ check_conn_params(const char **keywords, const char **values, UserMapping *user)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_S_R_E_PROHIBITED_SQL_STATEMENT_ATTEMPTED),
|
||||
errmsg("password or GSSAPI delegated credentials required"),
|
||||
errdetail("Non-superusers must delegate GSSAPI credentials or provide a password in the user mapping.")));
|
||||
errdetail("Non-superusers must delegate GSSAPI credentials, provide a password, or enable SCRAM pass-through in user mapping.")));
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -10301,7 +10301,7 @@ CREATE FOREIGN TABLE pg_temp.ft1_nopw (
|
||||
) SERVER loopback_nopw OPTIONS (schema_name 'public', table_name 'ft1');
|
||||
SELECT 1 FROM ft1_nopw LIMIT 1;
|
||||
ERROR: password or GSSAPI delegated credentials required
|
||||
DETAIL: Non-superusers must delegate GSSAPI credentials or provide a password in the user mapping.
|
||||
DETAIL: Non-superusers must delegate GSSAPI credentials, provide a password, or enable SCRAM pass-through in user mapping.
|
||||
-- If we add a password to the connstr it'll fail, because we don't allow passwords
|
||||
-- in connstrs only in user mappings.
|
||||
ALTER SERVER loopback_nopw OPTIONS (ADD password 'dummypw');
|
||||
@ -10351,7 +10351,7 @@ DROP USER MAPPING FOR CURRENT_USER SERVER loopback_nopw;
|
||||
-- lacks password_required=false
|
||||
SELECT 1 FROM ft1_nopw LIMIT 1;
|
||||
ERROR: password or GSSAPI delegated credentials required
|
||||
DETAIL: Non-superusers must delegate GSSAPI credentials or provide a password in the user mapping.
|
||||
DETAIL: Non-superusers must delegate GSSAPI credentials, provide a password, or enable SCRAM pass-through in user mapping.
|
||||
RESET ROLE;
|
||||
-- The user mapping for public is passwordless and lacks the password_required=false
|
||||
-- mapping option, but will work because the current user is a superuser.
|
||||
|
@ -41,4 +41,9 @@ tests += {
|
||||
],
|
||||
'regress_args': ['--dlpath', meson.build_root() / 'src/test/regress'],
|
||||
},
|
||||
'tap': {
|
||||
'tests': [
|
||||
't/001_auth_scram.pl',
|
||||
],
|
||||
},
|
||||
}
|
||||
|
@ -279,6 +279,9 @@ InitPgFdwOptions(void)
|
||||
{"analyze_sampling", ForeignServerRelationId, false},
|
||||
{"analyze_sampling", ForeignTableRelationId, false},
|
||||
|
||||
{"use_scram_passthrough", ForeignServerRelationId, false},
|
||||
{"use_scram_passthrough", UserMappingRelationId, false},
|
||||
|
||||
/*
|
||||
* sslcert and sslkey are in fact libpq options, but we repeat them
|
||||
* here to allow them to appear in both foreign server context (when
|
||||
|
151
contrib/postgres_fdw/t/001_auth_scram.pl
Normal file
151
contrib/postgres_fdw/t/001_auth_scram.pl
Normal file
@ -0,0 +1,151 @@
|
||||
# Copyright (c) 2024-2025, PostgreSQL Global Development Group
|
||||
|
||||
# Test SCRAM authentication when opening a new connection with a foreign
|
||||
# server.
|
||||
#
|
||||
# The test is executed by testing the SCRAM authentifcation on a looplback
|
||||
# connection on the same server and with different servers.
|
||||
|
||||
use strict;
|
||||
use warnings FATAL => 'all';
|
||||
use PostgreSQL::Test::Utils;
|
||||
use PostgreSQL::Test::Cluster;
|
||||
use Test::More;
|
||||
|
||||
my $hostaddr = '127.0.0.1';
|
||||
my $user = "user01";
|
||||
|
||||
my $db0 = "db0"; # For node1
|
||||
my $db1 = "db1"; # For node1
|
||||
my $db2 = "db2"; # For node2
|
||||
my $fdw_server = "db1_fdw";
|
||||
my $fdw_server2 = "db2_fdw";
|
||||
|
||||
my $node1 = PostgreSQL::Test::Cluster->new('node1');
|
||||
my $node2 = PostgreSQL::Test::Cluster->new('node2');
|
||||
|
||||
$node1->init;
|
||||
$node2->init;
|
||||
|
||||
$node1->start;
|
||||
$node2->start;
|
||||
|
||||
# Test setup
|
||||
|
||||
$node1->safe_psql('postgres', qq'CREATE USER $user WITH password \'pass\'');
|
||||
$node2->safe_psql('postgres', qq'CREATE USER $user WITH password \'pass\'');
|
||||
$ENV{PGPASSWORD} = "pass";
|
||||
|
||||
$node1->safe_psql('postgres', qq'CREATE DATABASE $db0');
|
||||
$node1->safe_psql('postgres', qq'CREATE DATABASE $db1');
|
||||
$node2->safe_psql('postgres', qq'CREATE DATABASE $db2');
|
||||
|
||||
setup_table($node1, $db1, "t");
|
||||
setup_table($node2, $db2, "t2");
|
||||
|
||||
$node1->safe_psql($db0, 'CREATE EXTENSION IF NOT EXISTS postgres_fdw');
|
||||
setup_fdw_server($node1, $db0, $fdw_server, $node1, $db1);
|
||||
setup_fdw_server($node1, $db0, $fdw_server2, $node2, $db2);
|
||||
|
||||
setup_user_mapping($node1, $db0, $fdw_server);
|
||||
setup_user_mapping($node1, $db0, $fdw_server2);
|
||||
|
||||
# Make the user have the same SCRAM key on both servers. Forcing to have the
|
||||
# same iteration and salt.
|
||||
my $rolpassword = $node1->safe_psql('postgres',
|
||||
qq"SELECT rolpassword FROM pg_authid WHERE rolname = '$user';");
|
||||
$node2->safe_psql('postgres', qq"ALTER ROLE $user PASSWORD '$rolpassword'");
|
||||
|
||||
setup_pghba($node1);
|
||||
setup_pghba($node2);
|
||||
|
||||
# End of test setup
|
||||
|
||||
test_fdw_auth($node1, $db0, "t", $fdw_server,
|
||||
"SCRAM auth on the same database cluster must succeed");
|
||||
test_fdw_auth($node1, $db0, "t2", $fdw_server2,
|
||||
"SCRAM auth on a different database cluster must succeed");
|
||||
test_auth($node2, $db2, "t2",
|
||||
"SCRAM auth directly on foreign server should still succeed");
|
||||
|
||||
# Helper functions
|
||||
|
||||
sub test_auth
|
||||
{
|
||||
local $Test::Builder::Level = $Test::Builder::Level + 1;
|
||||
|
||||
my ($node, $db, $tbl, $testname) = @_;
|
||||
my $connstr = $node->connstr($db) . qq' user=$user';
|
||||
|
||||
my $ret = $node->safe_psql(
|
||||
$db,
|
||||
qq'SELECT count(1) FROM $tbl',
|
||||
connstr => $connstr);
|
||||
|
||||
is($ret, '10', $testname);
|
||||
}
|
||||
|
||||
sub test_fdw_auth
|
||||
{
|
||||
local $Test::Builder::Level = $Test::Builder::Level + 1;
|
||||
|
||||
my ($node, $db, $tbl, $fdw, $testname) = @_;
|
||||
my $connstr = $node->connstr($db) . qq' user=$user';
|
||||
|
||||
$node->safe_psql(
|
||||
$db,
|
||||
qq'IMPORT FOREIGN SCHEMA public LIMIT TO ($tbl) FROM SERVER $fdw INTO public;',
|
||||
connstr => $connstr);
|
||||
|
||||
test_auth($node, $db, $tbl, $testname);
|
||||
}
|
||||
|
||||
sub setup_pghba
|
||||
{
|
||||
my ($node) = @_;
|
||||
|
||||
unlink($node->data_dir . '/pg_hba.conf');
|
||||
$node->append_conf(
|
||||
'pg_hba.conf', qq{
|
||||
local all all scram-sha-256
|
||||
host all all $hostaddr/32 scram-sha-256
|
||||
});
|
||||
|
||||
$node->restart;
|
||||
}
|
||||
|
||||
sub setup_user_mapping
|
||||
{
|
||||
my ($node, $db, $fdw) = @_;
|
||||
|
||||
$node->safe_psql($db,
|
||||
qq'CREATE USER MAPPING FOR $user SERVER $fdw OPTIONS (user \'$user\');'
|
||||
);
|
||||
$node->safe_psql($db, qq'GRANT USAGE ON FOREIGN SERVER $fdw TO $user;');
|
||||
$node->safe_psql($db, qq'GRANT ALL ON SCHEMA public TO $user');
|
||||
}
|
||||
|
||||
sub setup_fdw_server
|
||||
{
|
||||
my ($node, $db, $fdw, $fdw_node, $dbname) = @_;
|
||||
my $host = $fdw_node->host;
|
||||
my $port = $fdw_node->port;
|
||||
|
||||
$node->safe_psql(
|
||||
$db, qq'CREATE SERVER $fdw FOREIGN DATA WRAPPER postgres_fdw options (
|
||||
host \'$host\', port \'$port\', dbname \'$dbname\', use_scram_passthrough \'true\') '
|
||||
);
|
||||
}
|
||||
|
||||
sub setup_table
|
||||
{
|
||||
my ($node, $db, $tbl) = @_;
|
||||
|
||||
$node->safe_psql($db,
|
||||
qq'CREATE TABLE $tbl AS SELECT g, g + 1 FROM generate_series(1,10) g(g)'
|
||||
);
|
||||
$node->safe_psql($db, qq'GRANT USAGE ON SCHEMA public TO $user');
|
||||
$node->safe_psql($db, qq'GRANT SELECT ON $tbl TO $user');
|
||||
}
|
||||
|
||||
done_testing();
|
@ -2199,6 +2199,34 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry id="libpq-connect-scram-client-key" xreflabel="scram_client_key">
|
||||
<term><literal>scram_client_key</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
The base64-encoded SCRAM client key. This can be used by foreign-data
|
||||
wrappers or similar middleware to enable pass-through SCRAM
|
||||
authentication. See <xref
|
||||
linkend="postgres-fdw-options-connection-management"/> for one such
|
||||
implementation. It is not meant to be specified directly by users or
|
||||
client applications.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry id="libpq-connect-scram-server-key" xreflabel="scram_server_key">
|
||||
<term><literal>scram_server_key</literal></term>
|
||||
<listitem>
|
||||
<para>
|
||||
The base64-encoded SCRAM server key. This can be used by foreign-data
|
||||
wrappers or similar middleware to enable pass-through SCRAM
|
||||
authentication. See <xref
|
||||
linkend="postgres-fdw-options-connection-management"/> for one such
|
||||
implementation. It is not meant to be specified directly by users or
|
||||
client applications.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry id="libpq-connect-service" xreflabel="service">
|
||||
<term><literal>service</literal></term>
|
||||
<listitem>
|
||||
|
@ -770,6 +770,78 @@ OPTIONS (ADD password_required 'false');
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
<varlistentry>
|
||||
<term><literal>use_scram_passthrough</literal> (<type>boolean</type>)</term>
|
||||
<listitem>
|
||||
<para>
|
||||
This option controls whether <filename>postgres_fdw</filename> will
|
||||
use the SCRAM pass-through authentication to connect to the foreign
|
||||
server. With SCRAM pass-through authentication,
|
||||
<filename>postgres_fdw</filename> 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.
|
||||
</para>
|
||||
|
||||
<para>
|
||||
To use SCRAM pass-through authentication:
|
||||
<itemizedlist>
|
||||
<listitem>
|
||||
<para>
|
||||
The remote server must request SCRAM authentication. (If desired,
|
||||
enforce this on the client side (FDW side) with the option
|
||||
<literal>require_auth</literal>.) If another authentication method
|
||||
is requested by the server, then that one will be used normally.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
The remote server can be of any PostgreSQL version that supports
|
||||
SCRAM. Support for <literal>use_scram_passthrough</literal> is
|
||||
only required on the client side (FDW side).
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
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.)
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
The server running <filename>postgres_fdw</filename> and the remote
|
||||
server must have identical SCRAM secrets (encrypted passwords) for
|
||||
the user being used on <filename>postgres_fdw</filename> to
|
||||
authenticate on the foreign server (same salt and iterations, not
|
||||
merely the same password).
|
||||
</para>
|
||||
|
||||
<para>
|
||||
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.
|
||||
</para>
|
||||
</listitem>
|
||||
|
||||
<listitem>
|
||||
<para>
|
||||
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
|
||||
<quote>pass-through</quote>: SCRAM must be used going in and out.)
|
||||
This is a technical requirement of the SCRAM protocol.
|
||||
</para>
|
||||
</listitem>
|
||||
</itemizedlist>
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
</variablelist>
|
||||
</sect3>
|
||||
</sect2>
|
||||
|
@ -101,6 +101,7 @@
|
||||
#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,
|
||||
@ -144,6 +145,7 @@ typedef struct
|
||||
|
||||
int iterations;
|
||||
char *salt; /* base64-encoded */
|
||||
uint8 ClientKey[SCRAM_MAX_KEY_LEN];
|
||||
uint8 StoredKey[SCRAM_MAX_KEY_LEN];
|
||||
uint8 ServerKey[SCRAM_MAX_KEY_LEN];
|
||||
|
||||
@ -462,6 +464,13 @@ scram_exchange(void *opaq, const char *input, int inputlen,
|
||||
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;
|
||||
}
|
||||
|
||||
@ -1140,7 +1149,6 @@ static bool
|
||||
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;
|
||||
@ -1173,10 +1181,10 @@ verify_client_proof(scram_state *state)
|
||||
|
||||
/* 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);
|
||||
|
||||
|
@ -18,6 +18,8 @@
|
||||
#ifndef LIBPQ_BE_H
|
||||
#define LIBPQ_BE_H
|
||||
|
||||
#include "common/scram-common.h"
|
||||
|
||||
#include <sys/time.h>
|
||||
#ifdef USE_OPENSSL
|
||||
#include <openssl/ssl.h>
|
||||
@ -181,6 +183,13 @@ typedef struct Port
|
||||
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.
|
||||
*/
|
||||
|
@ -119,6 +119,8 @@ scram_init(PGconn *conn,
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (password)
|
||||
{
|
||||
/* Normalize the password with SASLprep, if possible */
|
||||
rc = pg_saslprep(password, &prep_password);
|
||||
if (rc == SASLPREP_OOM)
|
||||
@ -138,6 +140,7 @@ scram_init(PGconn *conn,
|
||||
}
|
||||
}
|
||||
state->password = prep_password;
|
||||
}
|
||||
|
||||
return state;
|
||||
}
|
||||
@ -775,23 +778,34 @@ calculate_client_proof(fe_scram_state *state,
|
||||
return false;
|
||||
}
|
||||
|
||||
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.
|
||||
* 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)
|
||||
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;
|
||||
}
|
||||
|
||||
if (pg_hmac_init(ctx, StoredKey, state->key_length) < 0 ||
|
||||
pg_hmac_update(ctx,
|
||||
@ -841,6 +855,12 @@ verify_server_signature(fe_scram_state *state, bool *match,
|
||||
return false;
|
||||
}
|
||||
|
||||
if (state->conn->scram_server_key_binary)
|
||||
{
|
||||
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)
|
||||
{
|
||||
@ -848,6 +868,7 @@ verify_server_signature(fe_scram_state *state, bool *match,
|
||||
pg_hmac_free(ctx);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* calculate ServerSignature */
|
||||
if (pg_hmac_init(ctx, ServerKey, state->key_length) < 0 ||
|
||||
|
@ -559,7 +559,7 @@ pg_SASL_init(PGconn *conn, int payloadlen)
|
||||
* 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)
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "common/base64.h"
|
||||
#include "common/ip.h"
|
||||
#include "common/link-canary.h"
|
||||
#include "common/scram-common.h"
|
||||
@ -366,6 +367,12 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
|
||||
"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}
|
||||
@ -1793,6 +1800,44 @@ pqConnectOptions2(PGconn *conn)
|
||||
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
|
||||
*/
|
||||
@ -4704,6 +4749,8 @@ freePGconn(PGconn *conn)
|
||||
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);
|
||||
|
||||
|
@ -428,6 +428,8 @@ struct pg_conn
|
||||
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
|
||||
@ -518,6 +520,10 @@ struct pg_conn
|
||||
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 */
|
||||
|
Reference in New Issue
Block a user