Skip to content

Commit d6e612f

Browse files
committed
Add libpq parameter 'channel_binding'.
Allow clients to require channel binding to enhance security against untrusted servers. Author: Jeff Davis Reviewed-by: Michael Paquier Discussion: https://postgr.es/m/227015d8417f2b4fef03f8966dbfa5cbcc4f44da.camel%40j-davis.com
1 parent 13cd97e commit d6e612f

File tree

9 files changed

+233
-20
lines changed

9 files changed

+233
-20
lines changed

doc/src/sgml/libpq.sgml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1122,6 +1122,28 @@ postgresql://%2Fvar%2Flib%2Fpostgresql/dbname
11221122
</listitem>
11231123
</varlistentry>
11241124

1125+
<varlistentry id="libpq-connect-channel-binding" xreflabel="channel_binding">
1126+
<term><literal>channel_binding</literal></term>
1127+
<listitem>
1128+
<para>
1129+
This option controls the client's use of channel binding. A setting
1130+
of <literal>require</literal> means that the connection must employ
1131+
channel binding, <literal>prefer</literal> means that the client will
1132+
choose channel binding if available, and <literal>disable</literal>
1133+
prevents the use of channel binding. The default
1134+
is <literal>prefer</literal> if
1135+
<productname>PostgreSQL</productname> is compiled with SSL support;
1136+
otherwise the default is <literal>disable</literal>.
1137+
</para>
1138+
<para>
1139+
Channel binding is a method for the server to authenticate itself to
1140+
the client. It is only supported over SSL connections
1141+
with <productname>PostgreSQL</productname> 11 or later servers using
1142+
the <literal>SCRAM</literal> authentication method.
1143+
</para>
1144+
</listitem>
1145+
</varlistentry>
1146+
11251147
<varlistentry id="libpq-connect-connect-timeout" xreflabel="connect_timeout">
11261148
<term><literal>connect_timeout</literal></term>
11271149
<listitem>
@@ -6864,6 +6886,16 @@ myEventProc(PGEventId evtId, void *evtInfo, void *passThrough)
68646886
</para>
68656887
</listitem>
68666888

6889+
<listitem>
6890+
<para>
6891+
<indexterm>
6892+
<primary><envar>PGCHANNELBINDING</envar></primary>
6893+
</indexterm>
6894+
<envar>PGCHANNELBINDING</envar> behaves the same as the <xref
6895+
linkend="libpq-connect-channel-binding"/> connection parameter.
6896+
</para>
6897+
</listitem>
6898+
68676899
<listitem>
68686900
<para>
68696901
<indexterm>

src/interfaces/libpq/fe-auth-scram.c

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,35 @@ pg_fe_scram_init(PGconn *conn,
119119
return state;
120120
}
121121

122+
/*
123+
* Return true if channel binding was employed and the SCRAM exchange
124+
* completed. This should be used after a successful exchange to determine
125+
* whether the server authenticated itself to the client.
126+
*
127+
* Note that the caller must also ensure that the exchange was actually
128+
* successful.
129+
*/
130+
bool
131+
pg_fe_scram_channel_bound(void *opaq)
132+
{
133+
fe_scram_state *state = (fe_scram_state *) opaq;
134+
135+
/* no SCRAM exchange done */
136+
if (state == NULL)
137+
return false;
138+
139+
/* SCRAM exchange not completed */
140+
if (state->state != FE_SCRAM_FINISHED)
141+
return false;
142+
143+
/* channel binding mechanism not used */
144+
if (strcmp(state->sasl_mechanism, SCRAM_SHA_256_PLUS_NAME) != 0)
145+
return false;
146+
147+
/* all clear! */
148+
return true;
149+
}
150+
122151
/*
123152
* Free SCRAM exchange status
124153
*/
@@ -225,9 +254,7 @@ pg_fe_scram_exchange(void *opaq, char *input, int inputlen,
225254

226255
/*
227256
* Verify server signature, to make sure we're talking to the
228-
* genuine server. XXX: A fake server could simply not require
229-
* authentication, though. There is currently no option in libpq
230-
* to reject a connection, if SCRAM authentication did not happen.
257+
* genuine server.
231258
*/
232259
if (verify_server_signature(state))
233260
*success = true;
@@ -358,7 +385,8 @@ build_client_first_message(fe_scram_state *state)
358385
appendPQExpBufferStr(&buf, "p=tls-server-end-point");
359386
}
360387
#ifdef HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
361-
else if (conn->ssl_in_use)
388+
else if (conn->channel_binding[0] != 'd' && /* disable */
389+
conn->ssl_in_use)
362390
{
363391
/*
364392
* Client supports channel binding, but thinks the server does not.
@@ -369,7 +397,7 @@ build_client_first_message(fe_scram_state *state)
369397
else
370398
{
371399
/*
372-
* Client does not support channel binding.
400+
* Client does not support channel binding, or has disabled it.
373401
*/
374402
appendPQExpBufferChar(&buf, 'n');
375403
}
@@ -498,7 +526,8 @@ build_client_final_message(fe_scram_state *state)
498526
#endif /* HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH */
499527
}
500528
#ifdef HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
501-
else if (conn->ssl_in_use)
529+
else if (conn->channel_binding[0] != 'd' && /* disable */
530+
conn->ssl_in_use)
502531
appendPQExpBufferStr(&buf, "c=eSws"); /* base64 of "y,," */
503532
#endif
504533
else

src/interfaces/libpq/fe-auth.c

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,14 @@ pg_SASL_init(PGconn *conn, int payloadlen)
423423

424424
initPQExpBuffer(&mechanism_buf);
425425

426+
if (conn->channel_binding[0] == 'r' && /* require */
427+
!conn->ssl_in_use)
428+
{
429+
printfPQExpBuffer(&conn->errorMessage,
430+
libpq_gettext("Channel binding required, but SSL not in use\n"));
431+
goto error;
432+
}
433+
426434
if (conn->sasl_state)
427435
{
428436
printfPQExpBuffer(&conn->errorMessage,
@@ -454,10 +462,10 @@ pg_SASL_init(PGconn *conn, int payloadlen)
454462

455463
/*
456464
* Select the mechanism to use. Pick SCRAM-SHA-256-PLUS over anything
457-
* else if a channel binding type is set and if the client supports
458-
* it. Pick SCRAM-SHA-256 if nothing else has already been picked. If
459-
* we add more mechanisms, a more refined priority mechanism might
460-
* become necessary.
465+
* else if a channel binding type is set and if the client supports it
466+
* (and did not set channel_binding=disable). Pick SCRAM-SHA-256 if
467+
* nothing else has already been picked. If we add more mechanisms, a
468+
* more refined priority mechanism might become necessary.
461469
*/
462470
if (strcmp(mechanism_buf.data, SCRAM_SHA_256_PLUS_NAME) == 0)
463471
{
@@ -466,10 +474,11 @@ pg_SASL_init(PGconn *conn, int payloadlen)
466474
/*
467475
* The server has offered SCRAM-SHA-256-PLUS, which is only
468476
* supported by the client if a hash of the peer certificate
469-
* can be created.
477+
* can be created, and if channel_binding is not disabled.
470478
*/
471479
#ifdef HAVE_PGTLS_GET_PEER_CERTIFICATE_HASH
472-
selected_mechanism = SCRAM_SHA_256_PLUS_NAME;
480+
if (conn->channel_binding[0] != 'd') /* disable */
481+
selected_mechanism = SCRAM_SHA_256_PLUS_NAME;
473482
#endif
474483
}
475484
else
@@ -493,6 +502,14 @@ pg_SASL_init(PGconn *conn, int payloadlen)
493502
selected_mechanism = SCRAM_SHA_256_NAME;
494503
}
495504

505+
if (conn->channel_binding[0] == 'r' && /* require */
506+
strcmp(selected_mechanism, SCRAM_SHA_256_PLUS_NAME) != 0)
507+
{
508+
printfPQExpBuffer(&conn->errorMessage,
509+
libpq_gettext("channel binding is required, but server did not offer an authentication method that supports channel binding\n"));
510+
goto error;
511+
}
512+
496513
if (!selected_mechanism)
497514
{
498515
printfPQExpBuffer(&conn->errorMessage,
@@ -774,6 +791,50 @@ pg_password_sendauth(PGconn *conn, const char *password, AuthRequest areq)
774791
return ret;
775792
}
776793

794+
/*
795+
* Verify that the authentication request is expected, given the connection
796+
* parameters. This is especially important when the client wishes to
797+
* authenticate the server before any sensitive information is exchanged.
798+
*/
799+
static bool
800+
check_expected_areq(AuthRequest areq, PGconn *conn)
801+
{
802+
bool result = true;
803+
804+
/*
805+
* When channel_binding=require, we must protect against two cases: (1) we
806+
* must not respond to non-SASL authentication requests, which might leak
807+
* information such as the client's password; and (2) even if we receive
808+
* AUTH_REQ_OK, we still must ensure that channel binding has happened in
809+
* order to authenticate the server.
810+
*/
811+
if (conn->channel_binding[0] == 'r' /* require */ )
812+
{
813+
switch (areq)
814+
{
815+
case AUTH_REQ_SASL:
816+
case AUTH_REQ_SASL_CONT:
817+
case AUTH_REQ_SASL_FIN:
818+
break;
819+
case AUTH_REQ_OK:
820+
if (!pg_fe_scram_channel_bound(conn->sasl_state))
821+
{
822+
printfPQExpBuffer(&conn->errorMessage,
823+
libpq_gettext("Channel binding required, but server authenticated client without channel binding\n"));
824+
result = false;
825+
}
826+
break;
827+
default:
828+
printfPQExpBuffer(&conn->errorMessage,
829+
libpq_gettext("Channel binding required but not supported by server's authentication request\n"));
830+
result = false;
831+
break;
832+
}
833+
}
834+
835+
return result;
836+
}
837+
777838
/*
778839
* pg_fe_sendauth
779840
* client demux routine for processing an authentication request
@@ -788,6 +849,9 @@ pg_password_sendauth(PGconn *conn, const char *password, AuthRequest areq)
788849
int
789850
pg_fe_sendauth(AuthRequest areq, int payloadlen, PGconn *conn)
790851
{
852+
if (!check_expected_areq(areq, conn))
853+
return STATUS_ERROR;
854+
791855
switch (areq)
792856
{
793857
case AUTH_REQ_OK:

src/interfaces/libpq/fe-auth.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ extern char *pg_fe_getauthname(PQExpBuffer errorMessage);
2626
extern void *pg_fe_scram_init(PGconn *conn,
2727
const char *password,
2828
const char *sasl_mechanism);
29+
extern bool pg_fe_scram_channel_bound(void *opaq);
2930
extern void pg_fe_scram_free(void *opaq);
3031
extern void pg_fe_scram_exchange(void *opaq, char *input, int inputlen,
3132
char **output, int *outputlen,

src/interfaces/libpq/fe-connect.c

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,11 @@ static int ldapServiceLookup(const char *purl, PQconninfoOption *options,
124124
#define DefaultTty ""
125125
#define DefaultOption ""
126126
#define DefaultAuthtype ""
127+
#ifdef USE_SSL
128+
#define DefaultChannelBinding "prefer"
129+
#else
130+
#define DefaultChannelBinding "disable"
131+
#endif
127132
#define DefaultTargetSessionAttrs "any"
128133
#ifdef USE_SSL
129134
#define DefaultSSLMode "prefer"
@@ -211,6 +216,10 @@ static const internalPQconninfoOption PQconninfoOptions[] = {
211216
"Database-Password-File", "", 64,
212217
offsetof(struct pg_conn, pgpassfile)},
213218

219+
{"channel_binding", "PGCHANNELBINDING", NULL, NULL,
220+
"Channel-Binding", "", 7, /* sizeof("require") */
221+
offsetof(struct pg_conn, channel_binding)},
222+
214223
{"connect_timeout", "PGCONNECT_TIMEOUT", NULL, NULL,
215224
"Connect-timeout", "", 10, /* strlen(INT32_MAX) == 10 */
216225
offsetof(struct pg_conn, connect_timeout)},
@@ -1197,6 +1206,29 @@ connectOptions2(PGconn *conn)
11971206
}
11981207
}
11991208

1209+
/*
1210+
* validate channel_binding option
1211+
*/
1212+
if (conn->channel_binding)
1213+
{
1214+
if (strcmp(conn->channel_binding, "disable") != 0
1215+
&& strcmp(conn->channel_binding, "prefer") != 0
1216+
&& strcmp(conn->channel_binding, "require") != 0)
1217+
{
1218+
conn->status = CONNECTION_BAD;
1219+
printfPQExpBuffer(&conn->errorMessage,
1220+
libpq_gettext("invalid channel_binding value: \"%s\"\n"),
1221+
conn->channel_binding);
1222+
return false;
1223+
}
1224+
}
1225+
else
1226+
{
1227+
conn->channel_binding = strdup(DefaultChannelBinding);
1228+
if (!conn->channel_binding)
1229+
goto oom_error;
1230+
}
1231+
12001232
/*
12011233
* validate sslmode option
12021234
*/
@@ -3485,10 +3517,11 @@ PQconnectPoll(PGconn *conn)
34853517
case CONNECTION_SETENV:
34863518
{
34873519
/*
3488-
* Do post-connection housekeeping (only needed in protocol 2.0).
3520+
* Do post-connection housekeeping (only needed in protocol
3521+
* 2.0).
34893522
*
3490-
* We pretend that the connection is OK for the duration of these
3491-
* queries.
3523+
* We pretend that the connection is OK for the duration of
3524+
* these queries.
34923525
*/
34933526
conn->status = CONNECTION_OK;
34943527

@@ -3905,6 +3938,8 @@ freePGconn(PGconn *conn)
39053938
}
39063939
if (conn->pgpassfile)
39073940
free(conn->pgpassfile);
3941+
if (conn->channel_binding)
3942+
free(conn->channel_binding);
39083943
if (conn->keepalives)
39093944
free(conn->keepalives);
39103945
if (conn->keepalives_idle)

src/interfaces/libpq/libpq-int.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,8 @@ struct pg_conn
347347
char *pguser; /* Postgres username and password, if any */
348348
char *pgpass;
349349
char *pgpassfile; /* path to a file containing password(s) */
350+
char *channel_binding; /* channel binding mode
351+
* (require,prefer,disable) */
350352
char *keepalives; /* use TCP keepalives? */
351353
char *keepalives_idle; /* time between TCP keepalives */
352354
char *keepalives_interval; /* time between TCP keepalive

src/test/authentication/t/001_password.pl

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
}
1818
else
1919
{
20-
plan tests => 8;
20+
plan tests => 10;
2121
}
2222

2323

@@ -86,3 +86,13 @@ sub test_role
8686
reset_pg_hba($node, 'md5');
8787
test_role($node, 'scram_role', 'md5', 0);
8888
test_role($node, 'md5_role', 'md5', 0);
89+
90+
# Tests for channel binding without SSL.
91+
# Using the password authentication method; channel binding can't work
92+
reset_pg_hba($node, 'password');
93+
$ENV{"PGCHANNELBINDING"} = 'require';
94+
test_role($node, 'scram_role', 'scram-sha-256', 2);
95+
# SSL not in use; channel binding still can't work
96+
reset_pg_hba($node, 'scram-sha-256');
97+
$ENV{"PGCHANNELBINDING"} = 'require';
98+
test_role($node, 'scram_role', 'scram-sha-256', 2);

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy