mirror of
https://github.com/postgres/postgres.git
synced 2025-04-24 10:47:04 +03:00
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>
This commit is contained in:
parent
39cfe86195
commit
d3fb72ea6d
@ -1575,9 +1575,13 @@ the password is in.
|
|||||||
|
|
||||||
<para>
|
<para>
|
||||||
<firstterm>Channel binding</firstterm> is supported in PostgreSQL builds with
|
<firstterm>Channel binding</firstterm> is supported in PostgreSQL builds with
|
||||||
SSL support. The SASL mechanism name for SCRAM with channel binding
|
SSL support. The SASL mechanism name for SCRAM with channel binding is
|
||||||
is <literal>SCRAM-SHA-256-PLUS</literal>. The only channel binding type
|
<literal>SCRAM-SHA-256-PLUS</literal>. Two channel binding types are
|
||||||
supported at the moment is <literal>tls-unique</literal>, defined in RFC 5929.
|
supported: <literal>tls-unique</literal> and
|
||||||
|
<literal>tls-server-end-point</literal>, both defined in RFC 5929. Clients
|
||||||
|
should use <literal>tls-unique</literal> if they can support it.
|
||||||
|
<literal>tls-server-end-point</literal> is intended for third-party clients
|
||||||
|
that cannot support <literal>tls-unique</literal> for some reason.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<procedure>
|
<procedure>
|
||||||
@ -1597,9 +1601,10 @@ supported at the moment is <literal>tls-unique</literal>, defined in RFC 5929.
|
|||||||
indicates the chosen mechanism, <literal>SCRAM-SHA-256</literal> or
|
indicates the chosen mechanism, <literal>SCRAM-SHA-256</literal> or
|
||||||
<literal>SCRAM-SHA-256-PLUS</literal>. (A client is free to choose either
|
<literal>SCRAM-SHA-256-PLUS</literal>. (A client is free to choose either
|
||||||
mechanism, but for better security it should choose the channel-binding
|
mechanism, but for better security it should choose the channel-binding
|
||||||
variant if it can support it.) In the Initial Client response field,
|
variant if it can support it.) In the Initial Client response field, the
|
||||||
the message contains the SCRAM
|
message contains the SCRAM <structname>client-first-message</structname>.
|
||||||
<structname>client-first-message</structname>.
|
The <structname>client-first-message</structname> also contains the channel
|
||||||
|
binding type chosen by the client.
|
||||||
</para>
|
</para>
|
||||||
</step>
|
</step>
|
||||||
<step id="scram-server-first">
|
<step id="scram-server-first">
|
||||||
|
@ -849,13 +849,14 @@ read_client_first_message(scram_state *state, char *input)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Read value provided by client; only tls-unique is supported
|
* Read value provided by client. (It is not safe to print
|
||||||
* for now. (It is not safe to print the name of an
|
* the name of an unsupported binding type in the error
|
||||||
* unsupported binding type in the error message. Pranksters
|
* message. Pranksters could print arbitrary strings into the
|
||||||
* could print arbitrary strings into the log that way.)
|
* log that way.)
|
||||||
*/
|
*/
|
||||||
channel_binding_type = read_attr_value(&input, 'p');
|
channel_binding_type = read_attr_value(&input, 'p');
|
||||||
if (strcmp(channel_binding_type, SCRAM_CHANNEL_BINDING_TLS_UNIQUE) != 0)
|
if (strcmp(channel_binding_type, SCRAM_CHANNEL_BINDING_TLS_UNIQUE) != 0 &&
|
||||||
|
strcmp(channel_binding_type, SCRAM_CHANNEL_BINDING_TLS_END_POINT) != 0)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_PROTOCOL_VIOLATION),
|
(errcode(ERRCODE_PROTOCOL_VIOLATION),
|
||||||
(errmsg("unsupported SCRAM channel-binding type"))));
|
(errmsg("unsupported SCRAM channel-binding type"))));
|
||||||
@ -1114,6 +1115,15 @@ read_client_final_message(scram_state *state, char *input)
|
|||||||
{
|
{
|
||||||
#ifdef USE_SSL
|
#ifdef USE_SSL
|
||||||
cbind_data = be_tls_get_peer_finished(state->port, &cbind_data_len);
|
cbind_data = be_tls_get_peer_finished(state->port, &cbind_data_len);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else if (strcmp(state->channel_binding_type,
|
||||||
|
SCRAM_CHANNEL_BINDING_TLS_END_POINT) == 0)
|
||||||
|
{
|
||||||
|
/* Fetch hash data of server's SSL certificate */
|
||||||
|
#ifdef USE_SSL
|
||||||
|
cbind_data = be_tls_get_certificate_hash(state->port,
|
||||||
|
&cbind_data_len);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -1239,6 +1239,67 @@ be_tls_get_peer_finished(Port *port, size_t *len)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the server certificate hash for SCRAM channel binding type
|
||||||
|
* tls-server-end-point.
|
||||||
|
*
|
||||||
|
* The result is a palloc'd hash of the server certificate with its
|
||||||
|
* size, and NULL if there is no certificate available.
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
be_tls_get_certificate_hash(Port *port, size_t *len)
|
||||||
|
{
|
||||||
|
X509 *server_cert;
|
||||||
|
char *cert_hash;
|
||||||
|
const EVP_MD *algo_type = NULL;
|
||||||
|
unsigned char hash[EVP_MAX_MD_SIZE]; /* size for SHA-512 */
|
||||||
|
unsigned int hash_size;
|
||||||
|
int algo_nid;
|
||||||
|
|
||||||
|
*len = 0;
|
||||||
|
server_cert = SSL_get_certificate(port->ssl);
|
||||||
|
if (server_cert == NULL)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the signature algorithm of the certificate to determine the
|
||||||
|
* hash algorithm to use for the result.
|
||||||
|
*/
|
||||||
|
if (!OBJ_find_sigid_algs(X509_get_signature_nid(server_cert),
|
||||||
|
&algo_nid, NULL))
|
||||||
|
elog(ERROR, "could not determine server certificate signature algorithm");
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The TLS server's certificate bytes need to be hashed with SHA-256 if
|
||||||
|
* its signature algorithm is MD5 or SHA-1 as per RFC 5929
|
||||||
|
* (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
|
||||||
|
* is used, the same hash as the signature algorithm is used.
|
||||||
|
*/
|
||||||
|
switch (algo_nid)
|
||||||
|
{
|
||||||
|
case NID_md5:
|
||||||
|
case NID_sha1:
|
||||||
|
algo_type = EVP_sha256();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
algo_type = EVP_get_digestbynid(algo_nid);
|
||||||
|
if (algo_type == NULL)
|
||||||
|
elog(ERROR, "could not find digest for NID %s",
|
||||||
|
OBJ_nid2sn(algo_nid));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* generate and save the certificate hash */
|
||||||
|
if (!X509_digest(server_cert, algo_type, hash, &hash_size))
|
||||||
|
elog(ERROR, "could not generate server certificate hash");
|
||||||
|
|
||||||
|
cert_hash = palloc(hash_size);
|
||||||
|
memcpy(cert_hash, hash, hash_size);
|
||||||
|
*len = hash_size;
|
||||||
|
|
||||||
|
return cert_hash;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Convert an X509 subject name to a cstring.
|
* Convert an X509 subject name to a cstring.
|
||||||
*
|
*
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
|
|
||||||
/* Channel binding types */
|
/* Channel binding types */
|
||||||
#define SCRAM_CHANNEL_BINDING_TLS_UNIQUE "tls-unique"
|
#define SCRAM_CHANNEL_BINDING_TLS_UNIQUE "tls-unique"
|
||||||
|
#define SCRAM_CHANNEL_BINDING_TLS_END_POINT "tls-server-end-point"
|
||||||
|
|
||||||
/* Length of SCRAM keys (client and server) */
|
/* Length of SCRAM keys (client and server) */
|
||||||
#define SCRAM_KEY_LEN PG_SHA256_DIGEST_LENGTH
|
#define SCRAM_KEY_LEN PG_SHA256_DIGEST_LENGTH
|
||||||
|
@ -210,6 +210,7 @@ extern void be_tls_get_version(Port *port, char *ptr, size_t len);
|
|||||||
extern void be_tls_get_cipher(Port *port, char *ptr, size_t len);
|
extern void be_tls_get_cipher(Port *port, char *ptr, size_t len);
|
||||||
extern void be_tls_get_peerdn_name(Port *port, char *ptr, size_t len);
|
extern void be_tls_get_peerdn_name(Port *port, char *ptr, size_t len);
|
||||||
extern char *be_tls_get_peer_finished(Port *port, size_t *len);
|
extern char *be_tls_get_peer_finished(Port *port, size_t *len);
|
||||||
|
extern char *be_tls_get_certificate_hash(Port *port, size_t *len);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
extern ProtocolVersion FrontendProtocol;
|
extern ProtocolVersion FrontendProtocol;
|
||||||
|
@ -444,6 +444,21 @@ build_client_final_message(fe_scram_state *state)
|
|||||||
cbind_data = pgtls_get_finished(state->conn, &cbind_data_len);
|
cbind_data = pgtls_get_finished(state->conn, &cbind_data_len);
|
||||||
if (cbind_data == NULL)
|
if (cbind_data == NULL)
|
||||||
goto oom_error;
|
goto oom_error;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
else if (strcmp(conn->scram_channel_binding,
|
||||||
|
SCRAM_CHANNEL_BINDING_TLS_END_POINT) == 0)
|
||||||
|
{
|
||||||
|
/* Fetch hash data of server's SSL certificate */
|
||||||
|
#ifdef USE_SSL
|
||||||
|
cbind_data =
|
||||||
|
pgtls_get_peer_certificate_hash(state->conn,
|
||||||
|
&cbind_data_len);
|
||||||
|
if (cbind_data == NULL)
|
||||||
|
{
|
||||||
|
/* error message is already set on error */
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -419,6 +419,86 @@ pgtls_get_finished(PGconn *conn, size_t *len)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the hash of the server certificate, for SCRAM channel binding type
|
||||||
|
* tls-server-end-point.
|
||||||
|
*
|
||||||
|
* NULL is sent back to the caller in the event of an error, with an
|
||||||
|
* error message for the caller to consume.
|
||||||
|
*/
|
||||||
|
char *
|
||||||
|
pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len)
|
||||||
|
{
|
||||||
|
X509 *peer_cert;
|
||||||
|
const EVP_MD *algo_type;
|
||||||
|
unsigned char hash[EVP_MAX_MD_SIZE]; /* size for SHA-512 */
|
||||||
|
unsigned int hash_size;
|
||||||
|
int algo_nid;
|
||||||
|
char *cert_hash;
|
||||||
|
|
||||||
|
*len = 0;
|
||||||
|
|
||||||
|
if (!conn->peer)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
peer_cert = conn->peer;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get the signature algorithm of the certificate to determine the hash
|
||||||
|
* algorithm to use for the result.
|
||||||
|
*/
|
||||||
|
if (!OBJ_find_sigid_algs(X509_get_signature_nid(peer_cert),
|
||||||
|
&algo_nid, NULL))
|
||||||
|
{
|
||||||
|
printfPQExpBuffer(&conn->errorMessage,
|
||||||
|
libpq_gettext("could not determine server certificate signature algorithm\n"));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The TLS server's certificate bytes need to be hashed with SHA-256 if
|
||||||
|
* its signature algorithm is MD5 or SHA-1 as per RFC 5929
|
||||||
|
* (https://tools.ietf.org/html/rfc5929#section-4.1). If something else
|
||||||
|
* is used, the same hash as the signature algorithm is used.
|
||||||
|
*/
|
||||||
|
switch (algo_nid)
|
||||||
|
{
|
||||||
|
case NID_md5:
|
||||||
|
case NID_sha1:
|
||||||
|
algo_type = EVP_sha256();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
algo_type = EVP_get_digestbynid(algo_nid);
|
||||||
|
if (algo_type == NULL)
|
||||||
|
{
|
||||||
|
printfPQExpBuffer(&conn->errorMessage,
|
||||||
|
libpq_gettext("could not find digest for NID %s\n"),
|
||||||
|
OBJ_nid2sn(algo_nid));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!X509_digest(peer_cert, algo_type, hash, &hash_size))
|
||||||
|
{
|
||||||
|
printfPQExpBuffer(&conn->errorMessage,
|
||||||
|
libpq_gettext("could not generate peer certificate hash\n"));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* save result */
|
||||||
|
cert_hash = malloc(hash_size);
|
||||||
|
if (cert_hash == NULL)
|
||||||
|
{
|
||||||
|
printfPQExpBuffer(&conn->errorMessage,
|
||||||
|
libpq_gettext("out of memory\n"));
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
memcpy(cert_hash, hash, hash_size);
|
||||||
|
*len = hash_size;
|
||||||
|
|
||||||
|
return cert_hash;
|
||||||
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------ */
|
/* ------------------------------------------------------------ */
|
||||||
/* OpenSSL specific code */
|
/* OpenSSL specific code */
|
||||||
|
@ -672,6 +672,7 @@ extern ssize_t pgtls_read(PGconn *conn, void *ptr, size_t len);
|
|||||||
extern bool pgtls_read_pending(PGconn *conn);
|
extern bool pgtls_read_pending(PGconn *conn);
|
||||||
extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
|
extern ssize_t pgtls_write(PGconn *conn, const void *ptr, size_t len);
|
||||||
extern char *pgtls_get_finished(PGconn *conn, size_t *len);
|
extern char *pgtls_get_finished(PGconn *conn, size_t *len);
|
||||||
|
extern char *pgtls_get_peer_certificate_hash(PGconn *conn, size_t *len);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* this is so that we can check if a connection is non-blocking internally
|
* this is so that we can check if a connection is non-blocking internally
|
||||||
|
@ -4,7 +4,7 @@ use strict;
|
|||||||
use warnings;
|
use warnings;
|
||||||
use PostgresNode;
|
use PostgresNode;
|
||||||
use TestLib;
|
use TestLib;
|
||||||
use Test::More tests => 4;
|
use Test::More tests => 5;
|
||||||
use ServerSetup;
|
use ServerSetup;
|
||||||
use File::Copy;
|
use File::Copy;
|
||||||
|
|
||||||
@ -45,6 +45,9 @@ test_connect_ok($common_connstr,
|
|||||||
test_connect_ok($common_connstr,
|
test_connect_ok($common_connstr,
|
||||||
"scram_channel_binding=''",
|
"scram_channel_binding=''",
|
||||||
"SCRAM authentication without channel binding");
|
"SCRAM authentication without channel binding");
|
||||||
|
test_connect_ok($common_connstr,
|
||||||
|
"scram_channel_binding=tls-server-end-point",
|
||||||
|
"SCRAM authentication with tls-server-end-point as channel binding");
|
||||||
test_connect_fails($common_connstr,
|
test_connect_fails($common_connstr,
|
||||||
"scram_channel_binding=not-exists",
|
"scram_channel_binding=not-exists",
|
||||||
"SCRAM authentication with invalid channel binding");
|
"SCRAM authentication with invalid channel binding");
|
||||||
|
Loading…
x
Reference in New Issue
Block a user