e3c68b
From e3020e43344ddbc32e62e06bbbf88a4f5d7cdc82 Mon Sep 17 00:00:00 2001
e3c68b
From: Mohit Agrawal <moagrawa@redhat.com>
e3c68b
Date: Fri, 10 May 2019 11:13:45 +0530
e3c68b
Subject: [PATCH 141/141] socket/ssl: fix crl handling
e3c68b
e3c68b
Problem:
e3c68b
Just setting the path to the CRL directory in socket_init() wasn't working.
e3c68b
e3c68b
Solution:
e3c68b
Need to use special API to retrieve and set X509_VERIFY_PARAM and set
e3c68b
the CRL checking flags explicitly.
e3c68b
Also, setting the CRL checking flags is a big pain, since the connection
e3c68b
is declared as failed if any CRL isn't found in the designated file or
e3c68b
directory. A comment has been added to the code appropriately.
e3c68b
e3c68b
> Change-Id: I8a8ed2ddaf4b5eb974387d2f7b1a85c1ca39fe79
e3c68b
> fixes: bz#1687326
e3c68b
> Signed-off-by: Milind Changire <mchangir@redhat.com>
e3c68b
> (Cherry pick from commit 06fa261207f0f0625c52fa977b96e5875e9a91e0)
e3c68b
> (Reviewed on upstream link https://review.gluster.org/#/c/glusterfs/+/22334/)
e3c68b
e3c68b
Change-Id: I0958e9890035fd376f1e1eafc1452caf3edd184b
e3c68b
BUG: 1583585
e3c68b
Signed-off-by: Mohit Agrawal <moagrawa@redhat.com>
e3c68b
Reviewed-on: https://code.engineering.redhat.com/gerrit/166458
e3c68b
Tested-by: RHGS Build Bot <nigelb@redhat.com>
e3c68b
Reviewed-by: Sunil Kumar Heggodu Gopala Acharya <sheggodu@redhat.com>
e3c68b
---
e3c68b
 configure.ac                          |   2 +
e3c68b
 rpc/rpc-transport/socket/src/socket.c | 110 ++++++++++++++++++++++++++++------
e3c68b
 rpc/rpc-transport/socket/src/socket.h |   2 +
e3c68b
 tests/features/ssl-ciphers.t          |  13 +++-
e3c68b
 4 files changed, 107 insertions(+), 20 deletions(-)
e3c68b
e3c68b
diff --git a/configure.ac b/configure.ac
e3c68b
index 3065077..0e11d4c 100644
e3c68b
--- a/configure.ac
e3c68b
+++ b/configure.ac
e3c68b
@@ -491,6 +491,8 @@ AC_CHECK_HEADERS([openssl/dh.h])
e3c68b
 
e3c68b
 AC_CHECK_HEADERS([openssl/ecdh.h])
e3c68b
 
e3c68b
+AC_CHECK_LIB([ssl], [SSL_CTX_get0_param], [AC_DEFINE([HAVE_SSL_CTX_GET0_PARAM], [1], [define if found OpenSSL SSL_CTX_get0_param])])
e3c68b
+
e3c68b
 dnl Math library
e3c68b
 AC_CHECK_LIB([m], [pow], [MATH_LIB='-lm'], [MATH_LIB=''])
e3c68b
 AC_SUBST(MATH_LIB)
e3c68b
diff --git a/rpc/rpc-transport/socket/src/socket.c b/rpc/rpc-transport/socket/src/socket.c
e3c68b
index f6de1d3..bf2fa71 100644
e3c68b
--- a/rpc/rpc-transport/socket/src/socket.c
e3c68b
+++ b/rpc/rpc-transport/socket/src/socket.c
e3c68b
@@ -308,8 +308,65 @@ out:
e3c68b
 #define ssl_write_one(t, b, l)                                                 \
e3c68b
     ssl_do((t), (b), (l), (SSL_trinary_func *)SSL_write)
e3c68b
 
e3c68b
+/* set crl verify flags only for server */
e3c68b
+/* see man X509_VERIFY_PARAM_SET_FLAGS(3)
e3c68b
+ * X509_V_FLAG_CRL_CHECK enables CRL checking for the certificate chain
e3c68b
+ * leaf certificate. An error occurs if a suitable CRL cannot be found.
e3c68b
+ * Since we're never going to revoke a gluster node cert, we better disable
e3c68b
+ * CRL check for server certs to avoid getting error and failed connection
e3c68b
+ * attempts.
e3c68b
+ */
e3c68b
+static void
e3c68b
+ssl_clear_crl_verify_flags(SSL_CTX *ssl_ctx)
e3c68b
+{
e3c68b
+#ifdef X509_V_FLAG_CRL_CHECK_ALL
e3c68b
+#ifdef HAVE_SSL_CTX_GET0_PARAM
e3c68b
+    X509_VERIFY_PARAM *vpm;
e3c68b
+
e3c68b
+    vpm = SSL_CTX_get0_param(ssl_ctx);
e3c68b
+    if (vpm) {
e3c68b
+        X509_VERIFY_PARAM_clear_flags(
e3c68b
+            vpm, (X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL));
e3c68b
+    }
e3c68b
+#else
e3c68b
+    /* CRL verify flag need not be cleared for rhel6 kind of clients */
e3c68b
+#endif
e3c68b
+#else
e3c68b
+    gf_log(this->name, GF_LOG_ERROR, "OpenSSL version does not support CRL");
e3c68b
+#endif
e3c68b
+    return;
e3c68b
+}
e3c68b
+
e3c68b
+/* set crl verify flags only for server */
e3c68b
+static void
e3c68b
+ssl_set_crl_verify_flags(SSL_CTX *ssl_ctx)
e3c68b
+{
e3c68b
+#ifdef X509_V_FLAG_CRL_CHECK_ALL
e3c68b
+#ifdef HAVE_SSL_CTX_GET0_PARAM
e3c68b
+    X509_VERIFY_PARAM *vpm;
e3c68b
+
e3c68b
+    vpm = SSL_CTX_get0_param(ssl_ctx);
e3c68b
+    if (vpm) {
e3c68b
+        unsigned long flags;
e3c68b
+
e3c68b
+        flags = X509_VERIFY_PARAM_get_flags(vpm);
e3c68b
+        flags |= (X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
e3c68b
+        X509_VERIFY_PARAM_set_flags(vpm, flags);
e3c68b
+    }
e3c68b
+#else
e3c68b
+    X509_STORE *x509store;
e3c68b
+
e3c68b
+    x509store = SSL_CTX_get_cert_store(ssl_ctx);
e3c68b
+    X509_STORE_set_flags(x509store,
e3c68b
+                         X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
e3c68b
+#endif
e3c68b
+#else
e3c68b
+    gf_log(this->name, GF_LOG_ERROR, "OpenSSL version does not support CRL");
e3c68b
+#endif
e3c68b
+}
e3c68b
+
e3c68b
 int
e3c68b
-ssl_setup_connection_prefix(rpc_transport_t *this)
e3c68b
+ssl_setup_connection_prefix(rpc_transport_t *this, gf_boolean_t server)
e3c68b
 {
e3c68b
     int ret = -1;
e3c68b
     socket_private_t *priv = NULL;
e3c68b
@@ -332,6 +389,9 @@ ssl_setup_connection_prefix(rpc_transport_t *this)
e3c68b
     priv->ssl_accepted = _gf_false;
e3c68b
     priv->ssl_context_created = _gf_false;
e3c68b
 
e3c68b
+    if (!server && priv->crl_path)
e3c68b
+        ssl_clear_crl_verify_flags(priv->ssl_ctx);
e3c68b
+
e3c68b
     priv->ssl_ssl = SSL_new(priv->ssl_ctx);
e3c68b
     if (!priv->ssl_ssl) {
e3c68b
         gf_log(this->name, GF_LOG_ERROR, "SSL_new failed");
e3c68b
@@ -2664,7 +2724,7 @@ ssl_handle_server_connection_attempt(rpc_transport_t *this)
e3c68b
     fd = priv->sock;
e3c68b
 
e3c68b
     if (!priv->ssl_context_created) {
e3c68b
-        ret = ssl_setup_connection_prefix(this);
e3c68b
+        ret = ssl_setup_connection_prefix(this, _gf_true);
e3c68b
         if (ret < 0) {
e3c68b
             gf_log(this->name, GF_LOG_TRACE,
e3c68b
                    "> ssl_setup_connection_prefix() failed!");
e3c68b
@@ -2718,7 +2778,7 @@ ssl_handle_client_connection_attempt(rpc_transport_t *this)
e3c68b
         ret = -1;
e3c68b
     } else {
e3c68b
         if (!priv->ssl_context_created) {
e3c68b
-            ret = ssl_setup_connection_prefix(this);
e3c68b
+            ret = ssl_setup_connection_prefix(this, _gf_false);
e3c68b
             if (ret < 0) {
e3c68b
                 gf_log(this->name, GF_LOG_TRACE,
e3c68b
                        "> ssl_setup_connection_prefix() "
e3c68b
@@ -3085,7 +3145,30 @@ socket_server_event_handler(int fd, int idx, int gen, void *data, int poll_in,
e3c68b
         gf_log(this->name, GF_LOG_TRACE, "XXX server:%s, client:%s",
e3c68b
                new_trans->myinfo.identifier, new_trans->peerinfo.identifier);
e3c68b
 
e3c68b
+        /* Make options available to local socket_init() to create new
e3c68b
+         * SSL_CTX per transport. A separate SSL_CTX per transport is
e3c68b
+         * required to avoid setting crl checking options for client
e3c68b
+         * connections. The verification options eventually get copied
e3c68b
+         * to the SSL object. Unfortunately, there's no way to identify
e3c68b
+         * whether socket_init() is being called after a client-side
e3c68b
+         * connect() or a server-side accept(). Although, we could pass
e3c68b
+         * a flag from the transport init() to the socket_init() and
e3c68b
+         * from this place, this doesn't identify the case where the
e3c68b
+         * server-side transport loading is done for the first time.
e3c68b
+         * Also, SSL doesn't apply for UNIX sockets.
e3c68b
+         */
e3c68b
+        if (new_sockaddr.ss_family != AF_UNIX)
e3c68b
+            new_trans->options = dict_ref(this->options);
e3c68b
+        new_trans->ctx = this->ctx;
e3c68b
+
e3c68b
         ret = socket_init(new_trans);
e3c68b
+
e3c68b
+        /* reset options to NULL to avoid double free */
e3c68b
+        if (new_sockaddr.ss_family != AF_UNIX) {
e3c68b
+            dict_unref(new_trans->options);
e3c68b
+            new_trans->options = NULL;
e3c68b
+        }
e3c68b
+
e3c68b
         if (ret != 0) {
e3c68b
             gf_log(this->name, GF_LOG_WARNING,
e3c68b
                    "initialization of new_trans "
e3c68b
@@ -4150,7 +4233,6 @@ ssl_setup_connection_params(rpc_transport_t *this)
e3c68b
     char *cipher_list = DEFAULT_CIPHER_LIST;
e3c68b
     char *dh_param = DEFAULT_DH_PARAM;
e3c68b
     char *ec_curve = DEFAULT_EC_CURVE;
e3c68b
-    char *crl_path = NULL;
e3c68b
 
e3c68b
     priv = this->private;
e3c68b
 
e3c68b
@@ -4192,6 +4274,7 @@ ssl_setup_connection_params(rpc_transport_t *this)
e3c68b
     }
e3c68b
     priv->ssl_ca_list = gf_strdup(priv->ssl_ca_list);
e3c68b
 
e3c68b
+    optstr = NULL;
e3c68b
     if (dict_get_str(this->options, SSL_CRL_PATH_OPT, &optstr) == 0) {
e3c68b
         if (!priv->ssl_enabled) {
e3c68b
             gf_log(this->name, GF_LOG_WARNING,
e3c68b
@@ -4199,9 +4282,9 @@ ssl_setup_connection_params(rpc_transport_t *this)
e3c68b
                    SSL_ENABLED_OPT);
e3c68b
         }
e3c68b
         if (strcasecmp(optstr, "NULL") == 0)
e3c68b
-            crl_path = NULL;
e3c68b
+            priv->crl_path = NULL;
e3c68b
         else
e3c68b
-            crl_path = optstr;
e3c68b
+            priv->crl_path = gf_strdup(optstr);
e3c68b
     }
e3c68b
 
e3c68b
     gf_log(this->name, priv->ssl_enabled ? GF_LOG_INFO : GF_LOG_DEBUG,
e3c68b
@@ -4343,24 +4426,15 @@ ssl_setup_connection_params(rpc_transport_t *this)
e3c68b
         }
e3c68b
 
e3c68b
         if (!SSL_CTX_load_verify_locations(priv->ssl_ctx, priv->ssl_ca_list,
e3c68b
-                                           crl_path)) {
e3c68b
+                                           priv->crl_path)) {
e3c68b
             gf_log(this->name, GF_LOG_ERROR, "could not load CA list");
e3c68b
             goto err;
e3c68b
         }
e3c68b
 
e3c68b
         SSL_CTX_set_verify_depth(priv->ssl_ctx, cert_depth);
e3c68b
 
e3c68b
-        if (crl_path) {
e3c68b
-#ifdef X509_V_FLAG_CRL_CHECK_ALL
e3c68b
-            X509_STORE *x509store;
e3c68b
-
e3c68b
-            x509store = SSL_CTX_get_cert_store(priv->ssl_ctx);
e3c68b
-            X509_STORE_set_flags(
e3c68b
-                x509store, X509_V_FLAG_CRL_CHECK | X509_V_FLAG_CRL_CHECK_ALL);
e3c68b
-#else
e3c68b
-            gf_log(this->name, GF_LOG_ERROR,
e3c68b
-                   "OpenSSL version does not support CRL");
e3c68b
-#endif
e3c68b
+        if (priv->crl_path) {
e3c68b
+            ssl_set_crl_verify_flags(priv->ssl_ctx);
e3c68b
         }
e3c68b
 
e3c68b
         priv->ssl_session_id = session_id++;
e3c68b
diff --git a/rpc/rpc-transport/socket/src/socket.h b/rpc/rpc-transport/socket/src/socket.h
e3c68b
index e1ccae2..e7c0090 100644
e3c68b
--- a/rpc/rpc-transport/socket/src/socket.h
e3c68b
+++ b/rpc/rpc-transport/socket/src/socket.h
e3c68b
@@ -14,6 +14,7 @@
e3c68b
 #include <openssl/ssl.h>
e3c68b
 #include <openssl/err.h>
e3c68b
 #include <openssl/x509v3.h>
e3c68b
+#include <openssl/x509_vfy.h>
e3c68b
 #ifdef HAVE_OPENSSL_DH_H
e3c68b
 #include <openssl/dh.h>
e3c68b
 #endif
e3c68b
@@ -246,6 +247,7 @@ typedef struct {
e3c68b
     char *ssl_own_cert;
e3c68b
     char *ssl_private_key;
e3c68b
     char *ssl_ca_list;
e3c68b
+    char *crl_path;
e3c68b
     int pipe[2];
e3c68b
     struct gf_sock_incoming incoming;
e3c68b
     /* -1 = not connected. 0 = in progress. 1 = connected */
e3c68b
diff --git a/tests/features/ssl-ciphers.t b/tests/features/ssl-ciphers.t
e3c68b
index 563d37c..7e1e199 100644
e3c68b
--- a/tests/features/ssl-ciphers.t
e3c68b
+++ b/tests/features/ssl-ciphers.t
e3c68b
@@ -175,8 +175,6 @@ BRICK_PORT=`brick_port $V0`
e3c68b
 EXPECT "Y" openssl_connect -cipher EECDH -connect $H0:$BRICK_PORT
e3c68b
 
e3c68b
 # test revocation
e3c68b
-# no need to restart the volume since the options are used
e3c68b
-# by the client here.
e3c68b
 TEST $CLI volume set $V0 ssl.crl-path $TMPDIR
e3c68b
 EXPECT $TMPDIR volume_option $V0 ssl.crl-path
e3c68b
 $GFS --volfile-id=$V0 --volfile-server=$H0 $M0
e3c68b
@@ -189,14 +187,25 @@ TEST openssl ca -batch -config $SSL_CFG -revoke $SSL_CERT 2>&1
e3c68b
 TEST openssl ca -config $SSL_CFG -gencrl -out $SSL_CRL 2>&1
e3c68b
 
e3c68b
 # Failed once revoked
e3c68b
+# Although client fails to mount without restarting the server after crl-path
e3c68b
+# is set when no actual crl file is found on the client, it would also fail
e3c68b
+# when server is restarted for the same reason. Since the socket initialization
e3c68b
+# code is the same for client and server, the crl verification flags need to
e3c68b
+# be turned off for the client to avoid SSL searching for CRLs in the
e3c68b
+# ssl.crl-path. If no CRL files are found in the ssl.crl-path, SSL fails the
e3c68b
+# connect() attempt on the client.
e3c68b
+TEST $CLI volume stop $V0
e3c68b
+TEST $CLI volume start $V0
e3c68b
 $GFS --volfile-id=$V0 --volfile-server=$H0 $M0
e3c68b
 EXPECT "N" wait_mount $M0
e3c68b
 TEST ! test -f $TEST_FILE
e3c68b
 EXPECT_WITHIN $UMOUNT_TIMEOUT "Y" force_umount $M0
e3c68b
 
e3c68b
 # Succeed with CRL disabled
e3c68b
+TEST $CLI volume stop $V0
e3c68b
 TEST $CLI volume set $V0 ssl.crl-path NULL
e3c68b
 EXPECT NULL volume_option $V0 ssl.crl-path
e3c68b
+TEST $CLI volume start $V0
e3c68b
 $GFS --volfile-id=$V0 --volfile-server=$H0 $M0
e3c68b
 EXPECT "Y" wait_mount $M0
e3c68b
 TEST test -f $TEST_FILE
e3c68b
-- 
e3c68b
1.8.3.1
e3c68b