diff mbox series

[ovs-dev,v2] stream-ssl: fix setting key and certificate

Message ID 20221220122106.1893577-1-xsimonar@redhat.com
State Superseded
Headers show
Series [ovs-dev,v2] stream-ssl: fix setting key and certificate | expand

Checks

Context Check Description
ovsrobot/apply-robot success apply and check: success
ovsrobot/github-robot-_Build_and_Test fail github build: failed
ovsrobot/intel-ovs-compilation success test: success

Commit Message

Xavier Simonart Dec. 20, 2022, 12:21 p.m. UTC
stream_ssl_set_key_and_cert is supposed to, whenever either the certificate or
the private key file changes, re-read both of them.
It was re-reading them only when both changed.
So, if, for instance, certificate was changed a few seconds only after changing
the key, the new key and certificate were never applied.

A few patches have been proposed on similar issues.
This patch tries to take into account the inputs/comments from them i.e.
- avoid crash on NULL private key and valid certificate
  (from d5d0c94551b6 ("stream-ssl: Fix crash on NULL private key and valid certificate."))
- avoid breaking setup while the second component is not updated
  (from https://patchwork.ozlabs.org/project/openvswitch/patch/20210513213311.1870647-1-hzhou@ovn.org/
- update key and cert, if they are valid.

Fixes: d5d0c94551b6 ("stream-ssl: Fix crash on NULL private key and valid certificate.")

Signed-off-by: Xavier Simonart <xsimonar@redhat.com>
---
v2: fix  'rl' shadows an earlier one
---
 lib/stream-ssl.c      | 115 +++++++++++++++++++++++++++++++-----------
 tests/ovsdb-server.at |  36 +++++++++++++
 2 files changed, 121 insertions(+), 30 deletions(-)
diff mbox series

Patch

diff --git a/lib/stream-ssl.c b/lib/stream-ssl.c
index 62da9febb..0bfe49b4c 100644
--- a/lib/stream-ssl.c
+++ b/lib/stream-ssl.c
@@ -76,6 +76,12 @@  enum session_type {
     SERVER
 };
 
+enum ssl_update_result {
+    SSL_UPDATE_ERROR,
+    SSL_NOT_UPDATED,
+    SSL_UPDATED
+};
+
 struct ssl_stream
 {
     struct stream stream;
@@ -186,6 +192,7 @@  static unsigned int next_session_nr;
 static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(10, 25);
 
 static int ssl_init(void);
+static SSL_CTX *new_ssl_ctx(void);
 static int do_ssl_init(void);
 static bool ssl_wants_io(int ssl_error);
 static void ssl_close(struct stream *);
@@ -201,7 +208,8 @@  static void stream_ssl_set_ca_cert_file__(const char *file_name,
                                           bool bootstrap, bool force);
 static void ssl_protocol_cb(int write_p, int version, int content_type,
                             const void *, size_t, SSL *, void *sslv_);
-static bool update_ssl_config(struct ssl_config_file *, const char *file_name);
+static enum ssl_update_result update_ssl_config(struct ssl_config_file *,
+                                                const char *file_name);
 static int sock_errno(void);
 
 static short int
@@ -1010,11 +1018,39 @@  ssl_init(void)
     return init_status;
 }
 
-static int
-do_ssl_init(void)
+static SSL_CTX *
+new_ssl_ctx(void)
 {
     SSL_METHOD *method;
 
+    /* OpenSSL has a bunch of "connection methods": SSLv2_method(),
+     * SSLv3_method(), TLSv1_method(), SSLv23_method(), ...  Most of these
+     * support exactly one version of SSL, e.g. TLSv1_method() supports TLSv1
+     * only, not any earlier *or later* version.  The only exception is
+     * SSLv23_method(), which in fact supports *any* version of SSL and TLS.
+     * We don't want SSLv2 or SSLv3 support, so we turn it off below with
+     * SSL_CTX_set_options().
+     *
+     * The cast is needed to avoid a warning with newer versions of OpenSSL in
+     * which SSLv23_method() returns a "const" pointer. */
+    method = CONST_CAST(SSL_METHOD *, SSLv23_method());
+    if (method == NULL) {
+        VLOG_ERR("TLSv1_method: %s", ERR_error_string(ERR_get_error(), NULL));
+        return NULL;
+    }
+
+    SSL_CTX *new_ctx = SSL_CTX_new(method);
+    if (new_ctx == NULL) {
+        VLOG_ERR_RL(&rl, "SSL_new: %s",
+                    ERR_error_string(ERR_get_error(), NULL));
+        return NULL;
+    }
+    return new_ctx;
+}
+
+static int
+do_ssl_init(void)
+{
 #if OPENSSL_VERSION_NUMBER < 0x10100000L || defined (LIBRESSL_VERSION_NUMBER)
 #ifdef _WIN32
     /* The following call is needed if we "#include <openssl/applink.c>". */
@@ -1054,25 +1090,8 @@  do_ssl_init(void)
         RAND_seed(seed, sizeof seed);
     }
 
-    /* OpenSSL has a bunch of "connection methods": SSLv2_method(),
-     * SSLv3_method(), TLSv1_method(), SSLv23_method(), ...  Most of these
-     * support exactly one version of SSL, e.g. TLSv1_method() supports TLSv1
-     * only, not any earlier *or later* version.  The only exception is
-     * SSLv23_method(), which in fact supports *any* version of SSL and TLS.
-     * We don't want SSLv2 or SSLv3 support, so we turn it off below with
-     * SSL_CTX_set_options().
-     *
-     * The cast is needed to avoid a warning with newer versions of OpenSSL in
-     * which SSLv23_method() returns a "const" pointer. */
-    method = CONST_CAST(SSL_METHOD *, SSLv23_method());
-    if (method == NULL) {
-        VLOG_ERR("TLSv1_method: %s", ERR_error_string(ERR_get_error(), NULL));
-        return ENOPROTOOPT;
-    }
-
-    ctx = SSL_CTX_new(method);
+    ctx = new_ssl_ctx();
     if (ctx == NULL) {
-        VLOG_ERR("SSL_CTX_new: %s", ERR_error_string(ERR_get_error(), NULL));
         return ENOPROTOOPT;
     }
     SSL_CTX_set_options(ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
@@ -1132,14 +1151,19 @@  stream_ssl_is_configured(void)
     return private_key.file_name || certificate.file_name || ca_cert.file_name;
 }
 
-static bool
+/* Returns:
+ *   SSL_UPDATED if updated,
+ *   SSL_NOT_UPDATED if unchanged,
+ *   SSL_ERROR in case of error.
+ */
+static enum ssl_update_result
 update_ssl_config(struct ssl_config_file *config, const char *file_name)
 {
     struct timespec mtime;
     int error;
 
     if (ssl_init() || !file_name) {
-        return false;
+        return SSL_UPDATE_ERROR;
     }
 
     /* If the file name hasn't changed and neither has the file contents, stop
@@ -1153,7 +1177,7 @@  update_ssl_config(struct ssl_config_file *config, const char *file_name)
         && !strcmp(config->file_name, file_name)
         && mtime.tv_sec == config->mtime.tv_sec
         && mtime.tv_nsec == config->mtime.tv_nsec) {
-        return false;
+        return SSL_NOT_UPDATED;
     }
 
     /* Update 'config'. */
@@ -1162,7 +1186,7 @@  update_ssl_config(struct ssl_config_file *config, const char *file_name)
         free(config->file_name);
         config->file_name = xstrdup(file_name);
     }
-    return true;
+    return SSL_UPDATED;
 }
 
 static void
@@ -1179,7 +1203,7 @@  stream_ssl_set_private_key_file__(const char *file_name)
 void
 stream_ssl_set_private_key_file(const char *file_name)
 {
-    if (update_ssl_config(&private_key, file_name)) {
+    if (update_ssl_config(&private_key, file_name) == SSL_UPDATED) {
         stream_ssl_set_private_key_file__(file_name);
     }
 }
@@ -1198,7 +1222,7 @@  stream_ssl_set_certificate_file__(const char *file_name)
 void
 stream_ssl_set_certificate_file(const char *file_name)
 {
-    if (update_ssl_config(&certificate, file_name)) {
+    if (update_ssl_config(&certificate, file_name) == SSL_UPDATED) {
         stream_ssl_set_certificate_file__(file_name);
     }
 }
@@ -1224,15 +1248,46 @@  stream_ssl_set_certificate_file(const char *file_name)
  *
  * This function avoids both problems by, whenever either the certificate or
  * the private key file changes, re-reading both of them, in the correct order.
+ *
+ * If either the certificate or the private key file failed to update (e.g.
+ * empty file), then those files are not applied as this used to cause segv.
+ * This function also avoids breaking setup while the second component is not
+ * updated.
  */
 void
 stream_ssl_set_key_and_cert(const char *private_key_file,
                             const char *certificate_file)
 {
-    if (update_ssl_config(&private_key, private_key_file)
-        && update_ssl_config(&certificate, certificate_file)) {
+    int priv_key_updated = update_ssl_config(&private_key, private_key_file);
+    if (((priv_key_updated == SSL_UPDATED) &&
+         (update_ssl_config(&certificate,certificate_file)
+                           != SSL_UPDATE_ERROR)) ||
+        ((priv_key_updated != SSL_UPDATE_ERROR) &&
+         (update_ssl_config(&certificate, certificate_file) == SSL_UPDATED))) {
+        SSL_CTX *tmp_ctx = new_ssl_ctx();
+        if (tmp_ctx == NULL) {
+            return;
+        }
+        if (SSL_CTX_use_certificate_file(tmp_ctx, certificate_file,
+                                         SSL_FILETYPE_PEM) != 1) {
+            VLOG_ERR_RL(&rl, "SSL_use_certificate_file: %s",
+                        ERR_error_string(ERR_get_error(), NULL));
+            return;
+        }
+        if (SSL_CTX_use_PrivateKey_file(tmp_ctx, private_key_file,
+                                        SSL_FILETYPE_PEM) != 1) {
+            VLOG_ERR_RL(&rl, "SSL_use_PrivateKey_file: %s",
+                        ERR_error_string(ERR_get_error(), NULL));
+            return;
+        }
+        if (SSL_CTX_check_private_key(tmp_ctx) != 1) {
+            VLOG_ERR_RL(&rl, "SSL_check_private_key: %s",
+                        ERR_error_string(ERR_get_error(), NULL));
+            return;
+        }
         stream_ssl_set_certificate_file__(certificate_file);
         stream_ssl_set_private_key_file__(private_key_file);
+        SSL_CTX_free(tmp_ctx);
     }
 }
 
@@ -1427,7 +1482,7 @@  stream_ssl_set_ca_cert_file__(const char *file_name,
 {
     struct stat s;
 
-    if (!update_ssl_config(&ca_cert, file_name) && !force) {
+    if ((update_ssl_config(&ca_cert, file_name) != SSL_UPDATED) && !force) {
         return;
     }
 
diff --git a/tests/ovsdb-server.at b/tests/ovsdb-server.at
index c7b2fe3ae..c2b80748b 100644
--- a/tests/ovsdb-server.at
+++ b/tests/ovsdb-server.at
@@ -1311,6 +1311,42 @@  AT_CHECK([test $(get_memory_value atoms) -eq $initial_db_atoms])
 OVS_APP_EXIT_AND_WAIT([ovsdb-server])
 AT_CLEANUP
 
+
+AT_SETUP([ssl certificate update])
+AT_KEYWORDS([ovsdb server ssl])
+AT_SKIP_IF([test "$HAVE_OPENSSL" = no])
+PKIDIR=$abs_top_builddir/tests
+AT_CHECK([ovsdb-tool create db $abs_top_srcdir/vswitchd/vswitch.ovsschema], [0], [stdout], [ignore])
+on_exit 'kill `cat *.pid`'
+
+# ssl certificate update with empty private_key
+AT_CHECK([ovsdb-server --log-file --detach --no-chdir --pidfile --private-key=db:Open_vSwitch,SSL,private_key --certificate=db:Open_vSwitch,SSL,certificate --bootstrap-ca-cert=db:Open_vSwitch,SSL,ca_cert --remote=punix:./db.sock --remote=db:Open_vSwitch,Open_vSwitch,manager_options db], [0], [ignore], [ignore])
+AT_CHECK([ovs-vsctl --no-wait init])
+AT_CHECK([ovs-vsctl --no-wait set-ssl pkey.key cert.cert ca.cert])
+AT_CHECK([ovs-vsctl --no-wait set SSL . private_key='""'])
+AT_CHECK([ovs-vsctl --no-wait set SSL . certificate='cert.new'])
+
+OVS_APP_EXIT_AND_WAIT([ovsdb-server])
+
+# Use wrong key/cert combination
+cp $PKIDIR/testpki-privkey2.pem key.pem
+cp $PKIDIR/testpki-cert.pem cert.pem
+AT_CHECK([ovsdb-server --log-file --detach --no-chdir --pidfile --private-key=key.pem --certificate=cert.pem --ca-cert=$PKIDIR/testpki-cacert.pem --remote=pssl:0:127.0.0.1 db], [0], [ignore], [ignore])
+PARSE_LISTENING_PORT([ovsdb-server.log], [SSL_PORT])
+OVS_WAIT_UNTIL([grep "ERR|SSL_use_PrivateKey_file" ovsdb-server.log | grep mismatch])
+
+# Use good combination and try to connect
+cp $PKIDIR/testpki-cert2.pem cert.pem
+AT_CHECK([ovsdb-client --private-key=$PKIDIR/testpki-privkey.pem --certificate=$PKIDIR/testpki-cert.pem --ca-cert=$PKIDIR/testpki-cacert.pem wait ssl:127.0.0.1:$SSL_PORT _Server connected  > ovsdb-client.stdout 2> ovsdb-client.stderr])
+
+# Set wrong key/cert combination
+cp $PKIDIR/testpki-privkey.pem key.pem
+# Check that we can still connect
+AT_CHECK([ovsdb-client --private-key=$PKIDIR/testpki-privkey.pem --certificate=$PKIDIR/testpki-cert.pem --ca-cert=$PKIDIR/testpki-cacert.pem wait ssl:127.0.0.1:$SSL_PORT _Server connected  > ovsdb-client.stdout 2> ovsdb-client.stderr])
+
+OVSDB_SERVER_SHUTDOWN
+AT_CLEANUP
+
 AT_BANNER([OVSDB -- ovsdb-server transactions (SSL IPv4 sockets)])
 
 # OVSDB_CHECK_EXECUTION(TITLE, SCHEMA, TRANSACTIONS, OUTPUT, [KEYWORDS])