Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
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

+32
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

+35-6
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

+70-6
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

+1
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

+38-3
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

+2
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

+11-1
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)