Commit d3fb72ea authored by Peter Eisentraut's avatar Peter Eisentraut

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>
parent 39cfe861
...@@ -1575,9 +1575,13 @@ the password is in. ...@@ -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. ...@@ -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) ...@@ -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) ...@@ -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) ...@@ -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 @@ ...@@ -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); ...@@ -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) ...@@ -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) ...@@ -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); ...@@ -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; ...@@ -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, ...@@ -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");
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment