Skip to content

Commit d3fb72e

Browse files
committed
Implement channel binding tls-server-end-point for SCRAM
This adds a second standard channel binding type for SCRAM. It is mainly intended for third-party clients that cannot implement tls-unique, for example JDBC. Author: Michael Paquier <michael.paquier@gmail.com>
1 parent 39cfe86 commit d3fb72e

File tree

9 files changed

+189
-12
lines changed

9 files changed

+189
-12
lines changed

doc/src/sgml/protocol.sgml

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1575,9 +1575,13 @@ the password is in.
15751575

15761576
<para>
15771577
<firstterm>Channel binding</firstterm> is supported in PostgreSQL builds with
1578-
SSL support. The SASL mechanism name for SCRAM with channel binding
1579-
is <literal>SCRAM-SHA-256-PLUS</literal>. The only channel binding type
1580-
supported at the moment is <literal>tls-unique</literal>, defined in RFC 5929.
1578+
SSL support. The SASL mechanism name for SCRAM with channel binding is
1579+
<literal>SCRAM-SHA-256-PLUS</literal>. Two channel binding types are
1580+
supported: <literal>tls-unique</literal> and
1581+
<literal>tls-server-end-point</literal>, both defined in RFC 5929. Clients
1582+
should use <literal>tls-unique</literal> if they can support it.
1583+
<literal>tls-server-end-point</literal> is intended for third-party clients
1584+
that cannot support <literal>tls-unique</literal> for some reason.
15811585
</para>
15821586

15831587
<procedure>
@@ -1597,9 +1601,10 @@ supported at the moment is <literal>tls-unique</literal>, defined in RFC 5929.
15971601
indicates the chosen mechanism, <literal>SCRAM-SHA-256</literal> or
15981602
<literal>SCRAM-SHA-256-PLUS</literal>. (A client is free to choose either
15991603
mechanism, but for better security it should choose the channel-binding
1600-
variant if it can support it.) In the Initial Client response field,
1601-
the message contains the SCRAM
1602-
<structname>client-first-message</structname>.
1604+
variant if it can support it.) In the Initial Client response field, the
1605+
message contains the SCRAM <structname>client-first-message</structname>.
1606+
The <structname>client-first-message</structname> also contains the channel
1607+
binding type chosen by the client.
16031608
</para>
16041609
</step>
16051610
<step id="scram-server-first">

src/backend/libpq/auth-scram.c

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -849,13 +849,14 @@ read_client_first_message(scram_state *state, char *input)
849849
}
850850

851851
/*
852-
* Read value provided by client; only tls-unique is supported
853-
* for now. (It is not safe to print the name of an
854-
* unsupported binding type in the error message. Pranksters
855-
* could print arbitrary strings into the log that way.)
852+
* Read value provided by client. (It is not safe to print
853+
* the name of an unsupported binding type in the error
854+
* message. Pranksters could print arbitrary strings into the
855+
* log that way.)
856856
*/
857857
channel_binding_type = read_attr_value(&input, 'p');
858-
if (strcmp(channel_binding_type, SCRAM_CHANNEL_BINDING_TLS_UNIQUE) != 0)
858+
if (strcmp(channel_binding_type, SCRAM_CHANNEL_BINDING_TLS_UNIQUE) != 0 &&
859+
strcmp(channel_binding_type, SCRAM_CHANNEL_BINDING_TLS_END_POINT) != 0)
859860
ereport(ERROR,
860861
(errcode(ERRCODE_PROTOCOL_VIOLATION),
861862
(errmsg("unsupported SCRAM channel-binding type"))));
@@ -1114,6 +1115,15 @@ read_client_final_message(scram_state *state, char *input)
11141115
{
11151116
#ifdef USE_SSL
11161117
cbind_data = be_tls_get_peer_finished(state->port, &cbind_data_len);
1118+
#endif
1119+
}
1120+
else if (strcmp(state->channel_binding_type,
1121+
SCRAM_CHANNEL_BINDING_TLS_END_POINT) == 0)
1122+
{
1123+
/* Fetch hash data of server's SSL certificate */
1124+
#ifdef USE_SSL
1125+
cbind_data = be_tls_get_certificate_hash(state->port,
1126+
&cbind_data_len);
11171127
#endif
11181128
}
11191129
else

src/backend/libpq/be-secure-openssl.c

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1239,6 +1239,67 @@ be_tls_get_peer_finished(Port *port, size_t *len)
12391239
return result;
12401240
}
12411241

1242+
/*
1243+
* Get the server certificate hash for SCRAM channel binding type
1244+
* tls-server-end-point.
1245+
*
1246+
* The result is a palloc'd hash of the server certificate with its
1247+
* size, and NULL if there is no certificate available.
1248+
*/
1249+
char *
1250+
be_tls_get_certificate_hash(Port *port, size_t *len)
1251+
{
1252+
X509 *server_cert;
1253+
char *cert_hash;
1254+
const EVP_MD *algo_type = NULL;
1255+
unsigned char hash[EVP_MAX_MD_SIZE]; /* size for SHA-512 */
1256+
unsigned int hash_size;
1257+
int algo_nid;
1258+
1259+
*len = 0;
1260+
server_cert = SSL_get_certificate(port->ssl);
1261+
if (server_cert == NULL)
1262+
return NULL;
1263+
1264+
/*
1265+
* Get the signature algorithm of the certificate to determine the
1266+
* hash algorithm to use for the result.
1267+
*/
1268+
if (!OBJ_find_sigid_algs(X509_get_signature_nid(server_cert),
1269+
&algo_nid, NULL))
1270+
elog(ERROR, "could not determine server certificate signature algorithm");
1271+
1272+
/*
1273+
* The TLS server's certificate bytes need to be hashed with SHA-256 if
1274+
* its signature algorithm is MD5 or SHA-1 as per RFC 5929
1275+
* (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
1276+
* is used, the same hash as the signature algorithm is used.
1277+
*/
1278+
switch (algo_nid)
1279+
{
1280+
case NID_md5:
1281+
case NID_sha1:
1282+
algo_type = EVP_sha256();
1283+
break;
1284+
default:
1285+
algo_type = EVP_get_digestbynid(algo_nid);
1286+
if (algo_type == NULL)
1287+
elog(ERROR, "could not find digest for NID %s",
1288+
OBJ_nid2sn(algo_nid));
1289+
break;
1290+
}
1291+
1292+
/* generate and save the certificate hash */
1293+
if (!X509_digest(server_cert, algo_type, hash, &hash_size))
1294+
elog(ERROR, "could not generate server certificate hash");
1295+
1296+
cert_hash = palloc(hash_size);
1297+
memcpy(cert_hash, hash, hash_size);
1298+
*len = hash_size;
1299+
1300+
return cert_hash;
1301+
}
1302+
12421303
/*
12431304
* Convert an X509 subject name to a cstring.
12441305
*

src/include/common/scram-common.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
/* Channel binding types */
2323
#define SCRAM_CHANNEL_BINDING_TLS_UNIQUE "tls-unique"
24+
#define SCRAM_CHANNEL_BINDING_TLS_END_POINT "tls-server-end-point"
2425

2526
/* Length of SCRAM keys (client and server) */
2627
#define SCRAM_KEY_LEN PG_SHA256_DIGEST_LENGTH

src/include/libpq/libpq-be.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ extern void be_tls_get_version(Port *port, char *ptr, size_t len);
210210
extern void be_tls_get_cipher(Port *port, char *ptr, size_t len);
211211
extern void be_tls_get_peerdn_name(Port *port, char *ptr, size_t len);
212212
extern char *be_tls_get_peer_finished(Port *port, size_t *len);
213+
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
213214
#endif
214215

215216
extern ProtocolVersion FrontendProtocol;

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,21 @@ build_client_final_message(fe_scram_state *state)
444444
cbind_data = pgtls_get_finished(state->conn, &cbind_data_len);
445445
if (cbind_data == NULL)
446446
goto oom_error;
447+
#endif
448+
}
449+
else if (strcmp(conn->scram_channel_binding,
450+
SCRAM_CHANNEL_BINDING_TLS_END_POINT) == 0)
451+
{
452+
/* Fetch hash data of server's SSL certificate */
453+
#ifdef USE_SSL
454+
cbind_data =
455+
pgtls_get_peer_certificate_hash(state->conn,
456+
&cbind_data_len);
457+
if (cbind_data == NULL)
458+
{
459+
/* error message is already set on error */
460+
return NULL;
461+
}
447462
#endif
448463
}
449464
else

src/interfaces/libpq/fe-secure-openssl.c

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,86 @@ pgtls_get_finished(PGconn *conn, size_t *len)
419419
return result;
420420
}
421421

422+
/*
423+
* Get the hash of the server certificate, for SCRAM channel binding type
424+
* tls-server-end-point.
425+
*
426+
* NULL is sent back to the caller in the event of an error, with an
427+
* error message for the caller to consume.
428+
*/
429+
char *
430+
pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
431+
{
432+
X509 *peer_cert;
433+
const EVP_MD *algo_type;
434+
unsigned char hash[EVP_MAX_MD_SIZE]; /* size for SHA-512 */
435+
unsigned int hash_size;
436+
int algo_nid;
437+
char *cert_hash;
438+
439+
*len = 0;
440+
441+
if (!conn->peer)
442+
return NULL;
443+
444+
peer_cert = conn->peer;
445+
446+
/*
447+
* Get the signature algorithm of the certificate to determine the hash
448+
* algorithm to use for the result.
449+
*/
450+
if (!OBJ_find_sigid_algs(X509_get_signature_nid(peer_cert),
451+
&algo_nid, NULL))
452+
{
453+
printfPQExpBuffer(&conn->errorMessage,
454+
libpq_gettext("could not determine server certificate signature algorithm\n"));
455+
return NULL;
456+
}
457+
458+
/*
459+
* The TLS server's certificate bytes need to be hashed with SHA-256 if
460+
* its signature algorithm is MD5 or SHA-1 as per RFC 5929
461+
* (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
462+
* is used, the same hash as the signature algorithm is used.
463+
*/
464+
switch (algo_nid)
465+
{
466+
case NID_md5:
467+
case NID_sha1:
468+
algo_type = EVP_sha256();
469+
break;
470+
default:
471+
algo_type = EVP_get_digestbynid(algo_nid);
472+
if (algo_type == NULL)
473+
{
474+
printfPQExpBuffer(&conn->errorMessage,
475+
libpq_gettext("could not find digest for NID %s\n"),
476+
OBJ_nid2sn(algo_nid));
477+
return NULL;
478+
}
479+
break;
480+
}
481+
482+
if (!X509_digest(peer_cert, algo_type, hash, &hash_size))
483+
{
484+
printfPQExpBuffer(&conn->errorMessage,
485+
libpq_gettext("could not generate peer certificate hash\n"));
486+
return NULL;
487+
}
488+
489+
/* save result */
490+
cert_hash = malloc(hash_size);
491+
if (cert_hash == NULL)
492+
{
493+
printfPQExpBuffer(&conn->errorMessage,
494+
libpq_gettext("out of memory\n"));
495+
return NULL;
496+
}
497+
memcpy(cert_hash, hash, hash_size);
498+
*len = hash_size;
499+
500+
return cert_hash;
501+
}
422502

423503
/* ------------------------------------------------------------ */
424504
/* OpenSSL specific code */

src/interfaces/libpq/libpq-int.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -672,6 +672,7 @@ extern ssize_t pgtls_read(PGconn *conn, void *ptr, size_t len);
672672
extern bool pgtls_read_pending(PGconn *conn);
673673
extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
674674
extern char *pgtls_get_finished(PGconn *conn, size_t *len);
675+
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
675676

676677
/*
677678
* this is so that we can check if a connection is non-blocking internally

src/test/ssl/t/002_scram.pl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
use warnings;
55
use PostgresNode;
66
use TestLib;
7-
use Test::More tests => 4;
7+
use Test::More tests => 5;
88
use ServerSetup;
99
use File::Copy;
1010

@@ -45,6 +45,9 @@
4545
test_connect_ok($common_connstr,
4646
"scram_channel_binding=''",
4747
"SCRAM authentication without channel binding");
48+
test_connect_ok($common_connstr,
49+
"scram_channel_binding=tls-server-end-point",
50+
"SCRAM authentication with tls-server-end-point as channel binding");
4851
test_connect_fails($common_connstr,
4952
"scram_channel_binding=not-exists",
5053
"SCRAM authentication with invalid channel binding");

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