From a7028dde8589a8119389d66e5dc55760b3af0019 Mon Sep 17 00:00:00 2001
From: Vladimir Davydov <vdavydov@tarantool.org>
Date: Thu, 23 Dec 2021 18:52:02 +0300
Subject: [PATCH] Add SSL iostream stub

This commit adds SSL context to iostream_ctx and a new kind of error for
SSL connections.

The SSL context is created if the target URI has transport=ssl
parameter. If transport=plain or absent, it will be set to NULL and
plain streams will be created as before. For other values of the
transport parameter, an error is raised. Note, this is just a stub -
an attempt to create an SSL context always currently fails with
"SSL not available" error. It is supposed to be implemented in
EE build.

The new kind of error is currently only used for decoding errors from
MsgPack and never raised. It will be raised by the actual implementation
of SSL stream.
---
 src/box/CMakeLists.txt              |  1 +
 src/box/mp_error.cc                 |  3 ++
 src/lib/core/CMakeLists.txt         | 16 ++++++
 src/lib/core/iostream.c             | 36 ++++++++++++--
 src/lib/core/iostream.h             | 10 +++-
 src/lib/core/ssl.c                  | 25 ++++++++++
 src/lib/core/ssl.h                  | 50 +++++++++++++++++++
 src/lib/core/ssl_error.cc           | 17 +++++++
 src/lib/core/ssl_error.h            | 36 ++++++++++++++
 src/trivia/config.h.cmake           |  1 +
 test/box-luatest/transport_test.lua | 77 +++++++++++++++++++++++++++++
 test/unit/mp_error.cc               |  1 +
 test/unit/mp_error.result           |  5 +-
 13 files changed, 271 insertions(+), 7 deletions(-)
 create mode 100644 src/lib/core/ssl.c
 create mode 100644 src/lib/core/ssl.h
 create mode 100644 src/lib/core/ssl_error.cc
 create mode 100644 src/lib/core/ssl_error.h
 create mode 100644 test/box-luatest/transport_test.lua

diff --git a/src/box/CMakeLists.txt b/src/box/CMakeLists.txt
index cc1db34c09..8156fd56d6 100644
--- a/src/box/CMakeLists.txt
+++ b/src/box/CMakeLists.txt
@@ -77,6 +77,7 @@ set_property(DIRECTORY PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${lua_sources})
 include_directories(${ZSTD_INCLUDE_DIRS})
 include_directories(${PROJECT_BINARY_DIR}/src/box/sql)
 include_directories(${PROJECT_BINARY_DIR}/src/box)
+include_directories(${EXTRA_CORE_INCLUDE_DIRS})
 include_directories(${EXTRA_BOX_INCLUDE_DIRS})
 
 add_library(box_error STATIC error.cc errcode.c mp_error.cc)
diff --git a/src/box/mp_error.cc b/src/box/mp_error.cc
index fba562a84a..3c4176e591 100644
--- a/src/box/mp_error.cc
+++ b/src/box/mp_error.cc
@@ -41,6 +41,7 @@
 #include "msgpuck.h"
 #include "mp_extension_types.h"
 #include "fiber.h"
+#include "ssl_error.h"
 
 /**
  * MP_ERROR format:
@@ -257,6 +258,8 @@ error_build_xc(struct mp_error *mp_error)
 		err = new SwimError();
 	} else if (strcmp(mp_error->type, "CryptoError") == 0) {
 		err = new CryptoError();
+	} else if (strcmp(mp_error->type, "SSLError") == 0) {
+		err = new SSLError();
 	} else {
 		err = new ClientError();
 	}
diff --git a/src/lib/core/CMakeLists.txt b/src/lib/core/CMakeLists.txt
index 220a39e683..0f76833c4b 100644
--- a/src/lib/core/CMakeLists.txt
+++ b/src/lib/core/CMakeLists.txt
@@ -37,6 +37,18 @@ set(core_sources
     mp_datetime.c
 )
 
+if(ENABLE_SSL)
+    list(APPEND core_sources ${SSL_SOURCES})
+else()
+    list(APPEND core_sources ssl.c ssl_error.cc)
+endif()
+
+include_directories(${EXTRA_CORE_INCLUDE_DIRS})
+
+if(ENABLE_SSL)
+    include_directories(${OPENSSL_INCLUDE_DIR})
+endif()
+
 if (TARGET_OS_NETBSD)
     # A workaround for "undefined reference to `__gcc_personality_v0'"
     # on x86_64-rumprun-netbsd-gcc
@@ -63,3 +75,7 @@ endif()
 if ("${HAVE_CLOCK_GETTIME}" AND NOT "${HAVE_CLOCK_GETTIME_WITHOUT_RT}")
     target_link_libraries(core rt)
 endif()
+
+if(ENABLE_SSL)
+    target_link_libraries(core ${OPENSSL_LIBRARIES})
+endif()
diff --git a/src/lib/core/iostream.c b/src/lib/core/iostream.c
index 17b7c85eb4..474fd7ec0f 100644
--- a/src/lib/core/iostream.c
+++ b/src/lib/core/iostream.c
@@ -12,7 +12,10 @@
 #include <sys/types.h>
 #include <unistd.h>
 
+#include "diag.h"
 #include "sio.h"
+#include "ssl.h"
+#include "uri/uri.h"
 
 static const struct iostream_vtab plain_iostream_vtab;
 
@@ -86,23 +89,48 @@ int
 iostream_ctx_create(struct iostream_ctx *ctx, enum iostream_mode mode,
 		    const struct uri *uri)
 {
-	(void)uri;
 	assert(mode == IOSTREAM_SERVER || mode == IOSTREAM_CLIENT);
 	ctx->mode = mode;
+	const char *transport = uri_param(uri, "transport", 0);
+	if (transport != NULL) {
+		if (strcmp(transport, "ssl") == 0) {
+			ctx->ssl = ssl_iostream_ctx_new(mode, uri);
+			if (ctx->ssl == NULL)
+				goto err;
+		} else if (strcmp(transport, "plain") == 0) {
+			ctx->ssl = NULL;
+		} else {
+			diag_set(IllegalParams, "Invalid transport: %s",
+				 transport);
+			goto err;
+		}
+	}
 	return 0;
+err:
+	iostream_ctx_clear(ctx);
+	return -1;
 }
 
 void
 iostream_ctx_destroy(struct iostream_ctx *ctx)
 {
+	if (ctx->ssl != NULL)
+		ssl_iostream_ctx_delete(ctx->ssl);
 	iostream_ctx_clear(ctx);
 }
 
 int
-iostream_create(struct iostream *io, int fd, struct iostream_ctx *ctx)
+iostream_create(struct iostream *io, int fd, const struct iostream_ctx *ctx)
 {
 	assert(ctx->mode == IOSTREAM_SERVER || ctx->mode == IOSTREAM_CLIENT);
-	(void)ctx;
-	plain_iostream_create(io, fd);
+	if (ctx->ssl != NULL) {
+		if (ssl_iostream_create(io, fd, ctx->mode, ctx->ssl) != 0)
+			goto err;
+	} else {
+		plain_iostream_create(io, fd);
+	}
 	return 0;
+err:
+	iostream_clear(io);
+	return -1;
 }
diff --git a/src/lib/core/iostream.h b/src/lib/core/iostream.h
index 9e25fd1386..ec4780047e 100644
--- a/src/lib/core/iostream.h
+++ b/src/lib/core/iostream.h
@@ -191,12 +191,19 @@ enum iostream_mode {
 	IOSTREAM_CLIENT,
 };
 
+struct ssl_iostream_ctx;
+
 /**
  * Context used for creating IO stream objects of a particular type.
  */
 struct iostream_ctx {
 	/** IO stream mode: server or client. */
 	enum iostream_mode mode;
+	/**
+	 * Context used for creating encrypted streams. If it's NULL, then
+	 * streams created with this context will be unencrypted.
+	 */
+	struct ssl_iostream_ctx *ssl;
 };
 
 /**
@@ -208,6 +215,7 @@ static inline void
 iostream_ctx_clear(struct iostream_ctx *ctx)
 {
 	ctx->mode = IOSTREAM_MODE_UNINITIALIZED;
+	ctx->ssl = NULL;
 }
 
 /**
@@ -232,7 +240,7 @@ iostream_ctx_destroy(struct iostream_ctx *ctx);
  * and clears the iostream struct (see iostream_clear).
  */
 int
-iostream_create(struct iostream *io, int fd, struct iostream_ctx *ctx);
+iostream_create(struct iostream *io, int fd, const struct iostream_ctx *ctx);
 
 #if defined(__cplusplus)
 } /* extern "C" */
diff --git a/src/lib/core/ssl.c b/src/lib/core/ssl.c
new file mode 100644
index 0000000000..ae456ade9f
--- /dev/null
+++ b/src/lib/core/ssl.c
@@ -0,0 +1,25 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2010-2021, Tarantool AUTHORS, please see AUTHORS file.
+ */
+#include "ssl.h"
+
+#include <stddef.h>
+
+#include "diag.h"
+#include "iostream.h"
+#include "trivia/config.h"
+
+#if defined(ENABLE_SSL)
+# error unimplemented
+#endif
+
+struct ssl_iostream_ctx *
+ssl_iostream_ctx_new(enum iostream_mode mode, const struct uri *uri)
+{
+	(void)mode;
+	(void)uri;
+	diag_set(IllegalParams, "SSL is not available in this build");
+	return NULL;
+}
diff --git a/src/lib/core/ssl.h b/src/lib/core/ssl.h
new file mode 100644
index 0000000000..6c0dabba22
--- /dev/null
+++ b/src/lib/core/ssl.h
@@ -0,0 +1,50 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2010-2021, Tarantool AUTHORS, please see AUTHORS file.
+ */
+#pragma once
+
+#include "trivia/config.h"
+
+#if defined(ENABLE_SSL)
+# include "ssl_impl.h"
+#else /* !defined(ENABLE_SSL) */
+
+#include "iostream.h"
+#include "trivia/util.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif /* defined(__cplusplus) */
+
+struct uri;
+struct ssl_iostream_ctx;
+
+struct ssl_iostream_ctx *
+ssl_iostream_ctx_new(enum iostream_mode mode, const struct uri *uri);
+
+static inline void
+ssl_iostream_ctx_delete(struct ssl_iostream_ctx *ctx)
+{
+	(void)ctx;
+	unreachable();
+}
+
+static inline int
+ssl_iostream_create(struct iostream *io, int fd, enum iostream_mode mode,
+		    const struct ssl_iostream_ctx *ctx)
+{
+	(void)io;
+	(void)fd;
+	(void)mode;
+	(void)ctx;
+	unreachable();
+	return 0;
+}
+
+#if defined(__cplusplus)
+} /* extern "C" */
+#endif /* defined(__cplusplus) */
+
+#endif /* !defined(ENABLE_SSL) */
diff --git a/src/lib/core/ssl_error.cc b/src/lib/core/ssl_error.cc
new file mode 100644
index 0000000000..00cdfe9e9e
--- /dev/null
+++ b/src/lib/core/ssl_error.cc
@@ -0,0 +1,17 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2010-2021, Tarantool AUTHORS, please see AUTHORS file.
+ */
+#include "ssl_error.h"
+
+#include <stddef.h>
+
+#include "reflection.h"
+#include "trivia/config.h"
+
+#if defined(ENABLE_SSL)
+# error unimplemented
+#endif
+
+const struct type_info type_SSLError = make_type("SSLError", NULL);
diff --git a/src/lib/core/ssl_error.h b/src/lib/core/ssl_error.h
new file mode 100644
index 0000000000..a684cc066e
--- /dev/null
+++ b/src/lib/core/ssl_error.h
@@ -0,0 +1,36 @@
+/*
+ * SPDX-License-Identifier: BSD-2-Clause
+ *
+ * Copyright 2010-2021, Tarantool AUTHORS, please see AUTHORS file.
+ */
+#pragma once
+
+#include "trivia/config.h"
+
+#if defined(ENABLE_SSL)
+# include "ssl_error_impl.h"
+#else /* !defined(ENABLE_SSL) */
+
+#include <stddef.h>
+
+#include "exception.h"
+#include "reflection.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif /* defined(__cplusplus) */
+
+extern const struct type_info type_SSLError;
+
+#if defined(__cplusplus)
+} /* extern "C" */
+
+class SSLError: public Exception {
+public:
+	SSLError(): Exception(&type_SSLError, NULL, 0) {}
+	virtual void raise() { throw this; }
+};
+
+#endif /* defined(__cplusplus) */
+
+#endif /* !defined(ENABLE_SSL) */
diff --git a/src/trivia/config.h.cmake b/src/trivia/config.h.cmake
index 2fafc46da5..e299f59416 100644
--- a/src/trivia/config.h.cmake
+++ b/src/trivia/config.h.cmake
@@ -256,6 +256,7 @@
 /* Cacheline size to calculate alignments */
 #define CACHELINE_SIZE 64
 
+#cmakedefine ENABLE_SSL 1
 #cmakedefine ENABLE_AUDIT_LOG 1
 #cmakedefine ENABLE_FEEDBACK_DAEMON 1
 
diff --git a/test/box-luatest/transport_test.lua b/test/box-luatest/transport_test.lua
new file mode 100644
index 0000000000..9664644a4c
--- /dev/null
+++ b/test/box-luatest/transport_test.lua
@@ -0,0 +1,77 @@
+local net = require('net.box')
+local server = require('test.luatest_helpers.server')
+local tarantool = require('tarantool')
+local t = require('luatest')
+local g = t.group()
+
+g.before_all = function()
+    g.server = server:new({alias = 'master'})
+    g.server:start()
+end
+
+g.after_all = function()
+    g.server:stop()
+end
+
+g.test_listen = function()
+    g.server:exec(function()
+        local t = require('luatest')
+        local listen = box.cfg.listen
+        box.cfg({listen = {listen, params = {transport = 'plain'}}})
+        t.assert_error_msg_equals(
+            'Invalid transport: foo',
+            box.cfg, {listen = {listen, params = {transport = 'foo'}}})
+        box.cfg({listen = listen})
+    end)
+end
+
+g.test_replication = function()
+    g.server:exec(function()
+        local t = require('luatest')
+        local listen = box.cfg.listen
+        box.cfg({replication = {listen, params = {transport = 'plain'}}})
+        t.assert_error_msg_equals(
+            'Invalid transport: foo',
+            box.cfg, {replication = {listen, params = {transport = 'foo'}}})
+        box.cfg({replication = {}})
+    end)
+end
+
+g.test_net_box = function()
+    local c = net.connect({g.server.net_box_uri,
+                           params = {transport = 'plain'}})
+    t.assert_equals(c.state, 'active')
+    c:close()
+    t.assert_error_msg_equals(
+        'Invalid transport: foo',
+        net.connect, {g.server.net_box_uri, params = {transport = 'foo'}})
+end
+
+-- Check that SSL isn't available in open-source builds.
+if tarantool.package ~= 'Tarantool Enterprise' then
+
+g.test_listen_ssl = function()
+    g.server:exec(function()
+        local t = require('luatest')
+        t.assert_error_msg_equals(
+            'SSL is not available in this build',
+            box.cfg, {listen = 'localhost:0?transport=ssl'})
+    end)
+end
+
+g.test_replication_ssl = function()
+    g.server:exec(function()
+        local t = require('luatest')
+        t.assert_error_msg_equals(
+            'SSL is not available in this build',
+            box.cfg, {replication = 'localhost:0?transport=ssl'})
+    end)
+end
+
+g.test_net_box_ssl = function()
+    t.assert_error_msg_equals(
+        'SSL is not available in this build',
+        net.connect, {g.server.net_box_uri, params = {transport = 'ssl'}})
+end
+
+end -- tarantool.package ~= 'Tarantool Enterprise'
diff --git a/test/unit/mp_error.cc b/test/unit/mp_error.cc
index 5e68b34d29..777c68dff1 100644
--- a/test/unit/mp_error.cc
+++ b/test/unit/mp_error.cc
@@ -86,6 +86,7 @@ const char *standard_errors[] = {
 	"CollationError",
 	"SwimError",
 	"CryptoError",
+	"SSLError",
 };
 
 enum {
diff --git a/test/unit/mp_error.result b/test/unit/mp_error.result
index 356d2dc970..3cb18996f3 100644
--- a/test/unit/mp_error.result
+++ b/test/unit/mp_error.result
@@ -1,7 +1,7 @@
 	*** main ***
 1..7
 	*** test_stack_error_decode ***
-    1..17
+    1..18
     ok 1 - check CustomError
     ok 2 - check AccessDeniedError
     ok 3 - check ClientError
@@ -18,7 +18,8 @@
     ok 14 - check CollationError
     ok 15 - check SwimError
     ok 16 - check CryptoError
-    ok 17 - stack size
+    ok 17 - check SSLError
+    ok 18 - stack size
 ok 1 - subtests
 	*** test_stack_error_decode: done ***
 	*** test_decode_unknown_type ***
-- 
GitLab