git.postgresql.org Git - postgresql.git/commitdiff

git projects / postgresql.git / commitdiff
? search:
summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: 39cfe86)
Implement channel binding tls-server-end-point for SCRAM
Thu, 4 Jan 2018 20:18:39 +0000 (15:18 -0500)
Thu, 4 Jan 2018 20:29:50 +0000 (15:29 -0500)
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>


diff --git a/doc/src/sgml/protocol.sgml b/doc/src/sgml/protocol.sgml
index 8174e3defa77980b3ceb76bdde200d91eb2a4f90..4c5ed1e6d66fd38856b5ae89861d0b893ff38f3d 100644 (file)
--- a/doc/src/sgml/protocol.sgml
+++ b/doc/src/sgml/protocol.sgml
@@ -1575,9 +1575,13 @@ the password is in.
<para>
<firstterm>Channel binding</firstterm> is supported in PostgreSQL builds with
-SSL support. The SASL mechanism name for SCRAM with channel binding
-is <literal>SCRAM-SHA-256-PLUS</literal>. The only channel binding type
-supported at the moment is <literal>tls-unique</literal>, defined in RFC 5929.
+SSL support. The SASL mechanism name for SCRAM with channel binding is
+<literal>SCRAM-SHA-256-PLUS</literal>. Two channel binding types are
+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>
<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
<literal>SCRAM-SHA-256-PLUS</literal>. (A client is free to choose either
mechanism, but for better security it should choose the channel-binding
- variant if it can support it.) In the Initial Client response field,
- the message contains the SCRAM
- <structname>client-first-message</structname>.
+ variant if it can support it.) In the Initial Client response field, the
+ message contains the SCRAM <structname>client-first-message</structname>.
+ The <structname>client-first-message</structname> also contains the channel
+ binding type chosen by the client.
</para>
</step>
<step id="scram-server-first">
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index 1b07eaebfac0885a331c65760c6c093399c01278..48eb531d0f0a37384040f677c3d5a9f4582ac65c 100644 (file)
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -849,13 +849,14 @@ read_client_first_message(scram_state *state, char *input)
}
/*
- * Read value provided by client; only tls-unique is supported
- * for now. (It is not safe to print the name of an
- * unsupported binding type in the error message. Pranksters
- * could print arbitrary strings into the log that way.)
+ * Read value provided by client. (It is not safe to print
+ * the name of an unsupported binding type in the error
+ * message. Pranksters could print arbitrary strings into the
+ * log that way.)
*/
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,
(errcode(ERRCODE_PROTOCOL_VIOLATION),
(errmsg("unsupported SCRAM channel-binding type"))));
@@ -1114,6 +1115,15 @@ read_client_final_message(scram_state *state, char *input)
{
#ifdef USE_SSL
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
}
else
diff --git a/src/backend/libpq/be-secure-openssl.c b/src/backend/libpq/be-secure-openssl.c
index 3a7aa0187673951ece8ce0bbb3486d81668403a4..f75cc2ef18fed699a96d6f8e961708e7782cf74a 100644 (file)
--- a/src/backend/libpq/be-secure-openssl.c
+++ b/src/backend/libpq/be-secure-openssl.c
@@ -1239,6 +1239,67 @@ be_tls_get_peer_finished(Port *port, size_t *len)
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.
*
diff --git a/src/include/common/scram-common.h b/src/include/common/scram-common.h
index 3d81934fdabe02e795f50c044315619985df3c69..e1d742ba89839e4efb070cff70246326179ec292 100644 (file)
--- a/src/include/common/scram-common.h
+++ b/src/include/common/scram-common.h
@@ -21,6 +21,7 @@
/* Channel binding types */
#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) */
#define SCRAM_KEY_LEN PG_SHA256_DIGEST_LENGTH
diff --git a/src/include/libpq/libpq-be.h b/src/include/libpq/libpq-be.h
index e660e8afa8442d2e96fa249aeed21602c45acec6..49cb2631104492fd1133b538c861780a30627a18 100644 (file)
--- a/src/include/libpq/libpq-be.h
+++ b/src/include/libpq/libpq-be.h
@@ -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_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_certificate_hash(Port *port, size_t *len);
#endif
extern ProtocolVersion FrontendProtocol;
diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c
index 06c9cb261417b9fd51bfaaf74cf1d5c780efa9d9..23bd5fb2b61c9a8d77cc3e62d0f8c2a39109f5d9 100644 (file)
--- a/src/interfaces/libpq/fe-auth-scram.c
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -444,6 +444,21 @@ build_client_final_message(fe_scram_state *state)
cbind_data = pgtls_get_finished(state->conn, &cbind_data_len);
if (cbind_data == NULL)
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
}
else
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 7b7390a1fc63e26528a9461c0b5f806783cbbbc7..52390640bf351ef2cc5943adfa6dd688225f5b84 100644 (file)
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -419,6 +419,86 @@ pgtls_get_finished(PGconn *conn, size_t *len)
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 */
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 516039eea00d3e37dc70336a3c171d58261c6c8f..4e354098b391109e79286fc0ac1536165ac90d49 100644 (file)
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -672,6 +672,7 @@ extern ssize_t pgtls_read(PGconn *conn, void *ptr, size_t len);
extern bool pgtls_read_pending(PGconn *conn);
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_peer_certificate_hash(PGconn *conn, size_t *len);
/*
* this is so that we can check if a connection is non-blocking internally
diff --git a/src/test/ssl/t/002_scram.pl b/src/test/ssl/t/002_scram.pl
index 324b4888d42b7900b8222cedc83666795e1b7787..3f425e00f0a9e71b46fd0e5ec1868317a867dc7c 100644 (file)
--- a/src/test/ssl/t/002_scram.pl
+++ b/src/test/ssl/t/002_scram.pl
@@ -4,7 +4,7 @@ use strict;
use warnings;
use PostgresNode;
use TestLib;
-use Test::More tests => 4;
+use Test::More tests => 5;
use ServerSetup;
use File::Copy;
@@ -45,6 +45,9 @@ test_connect_ok($common_connstr,
test_connect_ok($common_connstr,
"scram_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,
"scram_channel_binding=not-exists",
"SCRAM authentication with invalid channel binding");
This is the main PostgreSQL git repository.
RSS Atom

AltStyle によって変換されたページ (->オリジナル) /