]> granicus.if.org Git - postgresql/commitdiff
Implement channel binding tls-server-end-point for SCRAM
authorPeter Eisentraut <peter_e@gmx.net>
Thu, 4 Jan 2018 20:18:39 +0000 (15:18 -0500)
committerPeter Eisentraut <peter_e@gmx.net>
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>

doc/src/sgml/protocol.sgml
src/backend/libpq/auth-scram.c
src/backend/libpq/be-secure-openssl.c
src/include/common/scram-common.h
src/include/libpq/libpq-be.h
src/interfaces/libpq/fe-auth-scram.c
src/interfaces/libpq/fe-secure-openssl.c
src/interfaces/libpq/libpq-int.h
src/test/ssl/t/002_scram.pl

index 8174e3defa77980b3ceb76bdde200d91eb2a4f90..4c5ed1e6d66fd38856b5ae89861d0b893ff38f3d 100644 (file)
@@ -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">
index 1b07eaebfac0885a331c65760c6c093399c01278..48eb531d0f0a37384040f677c3d5a9f4582ac65c 100644 (file)
@@ -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
index 3a7aa0187673951ece8ce0bbb3486d81668403a4..f75cc2ef18fed699a96d6f8e961708e7782cf74a 100644 (file)
@@ -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.
  *
index 3d81934fdabe02e795f50c044315619985df3c69..e1d742ba89839e4efb070cff70246326179ec292 100644 (file)
@@ -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
index e660e8afa8442d2e96fa249aeed21602c45acec6..49cb2631104492fd1133b538c861780a30627a18 100644 (file)
@@ -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;
index 06c9cb261417b9fd51bfaaf74cf1d5c780efa9d9..23bd5fb2b61c9a8d77cc3e62d0f8c2a39109f5d9 100644 (file)
@@ -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
index 7b7390a1fc63e26528a9461c0b5f806783cbbbc7..52390640bf351ef2cc5943adfa6dd688225f5b84 100644 (file)
@@ -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                                   */
index 516039eea00d3e37dc70336a3c171d58261c6c8f..4e354098b391109e79286fc0ac1536165ac90d49 100644 (file)
@@ -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
index 324b4888d42b7900b8222cedc83666795e1b7787..3f425e00f0a9e71b46fd0e5ec1868317a867dc7c 100644 (file)
@@ -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");