From c6b1674abeddb8dcebe6d34f01a3591440ac350d Mon Sep 17 00:00:00 2001
From: Timm Fitschen <t.fitschen@indiscale.com>
Date: Mon, 28 Jun 2021 01:04:00 +0200
Subject: [PATCH] WIP: TLS

---
 cmake/FetchGRPC.cmake    |  7 +++--
 include/CMakeLists.txt   |  1 +
 include/connection.h     | 37 +++++++++++++++++-----
 include/utils.h          | 66 ++++++++++++++++++++++++++++++++++++++++
 src/CMakeLists.txt       |  1 +
 src/caosdbcli.cpp        | 25 +++++++--------
 src/connection.cpp       | 53 ++++++++++++++++++++++----------
 src/utils.cpp            |  6 ++++
 test/test_connection.cpp | 13 +++++++-
 9 files changed, 169 insertions(+), 40 deletions(-)
 create mode 100644 include/utils.h
 create mode 100644 src/utils.cpp

diff --git a/cmake/FetchGRPC.cmake b/cmake/FetchGRPC.cmake
index 7b4b368..fffda8f 100644
--- a/cmake/FetchGRPC.cmake
+++ b/cmake/FetchGRPC.cmake
@@ -35,7 +35,8 @@ set(gRPC_BUILD_GRPC_PHP_PLUGIN OFF)
 set(gRPC_BUILD_GRPC_OBJECTIVE_C_PLUGIN OFF)
 set(gRPC_BUILD_GRPC_NODE_PLUGIN OFF)
 set(protobuf_BUILD_TESTS OFF)
-set(vGRPC_TAG_VERSION_OF_YOUR_CHOICE "v1.38.1")
+set(gRPC_VERSION "1.38.1")
+set(vGRPC_TAG_VERSION_OF_YOUR_CHOICE "v${gRPC_VERSION}")
 
 if(GRPC_AS_SUBMODULE)
   # One way to build a projects that uses gRPC is to just include the
@@ -209,8 +210,8 @@ else()
 
   # Find gRPC installation
   # Looks for gRPCConfig.cmake file installed by gRPC's cmake installation.
-  find_package(gRPC CONFIG REQUIRED)
-  message(STATUS "Using gRPC ${gRPC_VERSION}")
+  find_package(gRPC ${gRPC_VERSION} EXACT CONFIG REQUIRED)
+  message(STATUS "Using gRPC ${gRPC_VERSION} at ${gRPC}")
 
   set(_GRPC_GRPCPP gRPC::grpc++)
   if(CMAKE_CROSSCOMPILING)
diff --git a/include/CMakeLists.txt b/include/CMakeLists.txt
index 1b6533f..0e4d0da 100644
--- a/include/CMakeLists.txt
+++ b/include/CMakeLists.txt
@@ -22,6 +22,7 @@
 set(libcaosdb_INCL
     ${CMAKE_CURRENT_BINARY_DIR}/constants.h
     ${CMAKE_CURRENT_SOURCE_DIR}/connection.h
+    ${CMAKE_CURRENT_SOURCE_DIR}/utils.h
     )
 
 # pass variable to parent scope
diff --git a/include/connection.h b/include/connection.h
index 7bd14f0..d4d5d74 100644
--- a/include/connection.h
+++ b/include/connection.h
@@ -1,5 +1,4 @@
 /*
- *
  * This file is a part of the CaosDB Project.
  *
  * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com>
@@ -37,16 +36,40 @@
 #include <grpcpp/create_channel.h>
 #include <grpcpp/security/credentials.h>
 #include "caosdb/info/v1alpha1/main.grpc.pb.h"
+namespace grpc { class ChannelCredentials; }
+namespace caosdb { namespace info { namespace v1alpha1 { class VersionInfo; } } }
 
 namespace caosdb {
 using caosdb::info::v1alpha1::GeneralInfoService;
 using caosdb::info::v1alpha1::VersionInfo;
 using grpc::ChannelCredentials;
 
+class CACertificateProvider {
+public:
+  [[nodiscard]] auto virtual getCACertPem() const -> std::string = 0;
+};
+
+class PemFileCACertProvider : public CACertificateProvider {
+private:
+  std::string cacert;
+public:
+  explicit PemFileCACertProvider(const std::string &path);
+  [[nodiscard]] auto getCACertPem() const -> std::string override;
+};
+
+class PemCACertProvider : public CACertificateProvider {
+private:
+  std::string cacert;
+public:
+  explicit PemCACertProvider(const std::string &cacert);
+  [[nodiscard]] auto getCACertPem() const -> std::string override;
+};
+
 /**
  * @brief Configuration of the CaosDB connection.
  */
 class CaosDBConnectionConfig {
+private:
   std::string host;
   int port;
 
@@ -59,19 +82,16 @@ public:
   [[nodiscard]] auto virtual toString() const -> std::string = 0;
   [[nodiscard]] auto getHost() const -> std::string;
   [[nodiscard]] auto getPort() const -> int;
-  [[nodiscard]] auto virtual getChannelCredentials() const
-    -> std::shared_ptr<ChannelCredentials> = 0;
+  [[nodiscard]] auto virtual getChannelCredentials() const -> std::shared_ptr<ChannelCredentials> = 0;
 };
 
 class InsecureCaosDBConnectionConfig : public CaosDBConnectionConfig {
 private:
   std::shared_ptr<ChannelCredentials> credentials;
-
 public:
   InsecureCaosDBConnectionConfig(const std::string &host, int port);
+  [[nodiscard]] auto getChannelCredentials() const -> std::shared_ptr<ChannelCredentials> override;
   [[nodiscard]] auto toString() const -> std::string override;
-  [[nodiscard]] auto getChannelCredentials() const
-    -> std::shared_ptr<ChannelCredentials> override;
 };
 
 class SslCaosDBConnectionConfig : public CaosDBConnectionConfig {
@@ -79,9 +99,10 @@ private:
   std::shared_ptr<ChannelCredentials> credentials;
 
 public:
-  SslCaosDBConnectionConfig(const std::string &host, int port);
   SslCaosDBConnectionConfig(const std::string &host, int port,
-                            const std::string &cacert);
+                            const std::shared_ptr<CACertificateProvider> &cacert);
+  [[nodiscard]] auto getChannelCredentials() const -> std::shared_ptr<ChannelCredentials> override;
+  [[nodiscard]] auto toString() const -> std::string override;
 };
 
 /**
diff --git a/include/utils.h b/include/utils.h
new file mode 100644
index 0000000..616d2b9
--- /dev/null
+++ b/include/utils.h
@@ -0,0 +1,66 @@
+/*
+ * This file is a part of the CaosDB Project.
+ *
+ * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com>
+ * Copyright (C) 2021 IndiScale GmbH <info@indiscale.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef UTILS_H
+#define UTILS_H
+#include <string_view>
+#include <fstream>
+#include <string>
+#include <cstdlib>
+
+namespace caosdb::utils {
+
+/**
+ * @brief Read a text file into a string and return the file's content.
+ * @todo use boost-filesystem's "load_string_file"!
+ */
+inline auto load_string_file(const std::string &path) -> std::string {
+    const auto path_view = std::string_view{path};
+    constexpr auto size = std::size_t{4096};
+    auto stream = std::ifstream{path_view.data()};
+    stream.exceptions(std::ios_base::badbit);
+
+    auto result = std::string();
+    auto buffer = std::string(size, '\0');
+    while (stream.read(& buffer[0], size)) {
+        result.append(buffer, 0, stream.gcount());
+    }
+    result.append(buffer, 0, stream.gcount());
+    return result;
+}
+
+/**
+ * @brief Return the value of an environment variable or - if undefined - the
+ * fall_back value.
+ */
+inline auto get_env_var(const std::string & key, const std::string & fall_back ) -> const std::string
+{
+    const char * val = getenv( key.c_str() );
+    if ( val == nullptr ) {
+      return fall_back;
+    } else {
+      const auto result = std::string(val);
+      return result;
+    }
+}
+
+} // namespace caosdb::utils
+#endif
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 21a98c4..49f8500 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -21,6 +21,7 @@
 
 # add all source files to this list
 set(libcaosdb_SRC
+    src/utils.cpp
     src/connection.cpp
     )
 
diff --git a/src/caosdbcli.cpp b/src/caosdbcli.cpp
index e5c95da..66d1315 100644
--- a/src/caosdbcli.cpp
+++ b/src/caosdbcli.cpp
@@ -27,6 +27,7 @@
 #include "caosdb/info/v1alpha1/main.pb.h"
 #include "constants.h"
 #include "connection.h"
+#include "utils.h"
 
 auto main() -> int {
 
@@ -35,22 +36,22 @@ auto main() -> int {
             << caosdb::LIBCAOSDB_VERSION_PATCH << ")" << std::endl;
   std::cout << "We don't miss the H of caos." << std::endl;
 
-  const std::string host = "localhost";
-  // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers,readability-magic-numbers)
-  const int port = 8080;
-  const std::string &test = host;
-  std::cout << test << std::endl;
+  const auto pem_file_path = caosdb::utils::get_env_var("CAOSDB_SERVER_CA_PEM", std::string());
+  std::string pem = caosdb::utils::load_string_file(pem_file_path);
 
-  std::shared_ptr<caosdb::InsecureCaosDBConnectionConfig> config =
-    std::make_shared<caosdb::InsecureCaosDBConnectionConfig>(host, port);
+  std::cout << "PEM" << pem << "\n";
+  const std::string host = caosdb::utils::get_env_var("CAOSDB_SERVER_HOST", "localhost");
+  const std::string port_str = caosdb::utils::get_env_var("CAOSDB_SERVER_PORT", "8000");
+  const int port = std::stoi(port_str);
+
+  auto cacert = std::make_shared<caosdb::PemCACertProvider>(pem);
+  auto config = std::make_shared<caosdb::SslCaosDBConnectionConfig>(host, port, cacert);
   caosdb::CaosDBConnection connection(config);
   std::cout << std::endl << connection << std::endl;
-  const caosdb::info::v1alpha1::VersionInfo &v_info =
-    connection.getVersionInfo();
-  const std::string &build = v_info.build();
-  std::cout << "BUILD(" << build[0] << ")" << std::endl;
+  const auto &v_info = connection.getVersionInfo();
+  const auto &build = v_info.build();
+  //std::cout << "BUILD(" << build[0] << ")" << std::endl;
   std::cout << "VersionInfo(" << v_info.major() << "." << v_info.minor() << "."
             << v_info.patch() << ")" << std::endl;
-  std::cout << "PRE_RELEASE(" << v_info.pre_release() << ")" << std::endl;
   return 0;
 }
diff --git a/src/connection.cpp b/src/connection.cpp
index 42ef66e..62b71dd 100644
--- a/src/connection.cpp
+++ b/src/connection.cpp
@@ -21,6 +21,7 @@
  */
 
 #include "connection.h"
+#include "utils.h"
 #include <grpcpp/create_channel.h>
 #include <grpcpp/impl/codegen/client_context.h>
 #include <grpcpp/impl/codegen/status.h>
@@ -32,6 +33,7 @@
 #include "caosdb/info/v1alpha1/main.pb.h"
 
 namespace caosdb {
+using caosdb::utils::load_string_file;
 using caosdb::info::v1alpha1::GeneralInfoService;
 using caosdb::info::v1alpha1::GetVersionInfoRequest;
 using caosdb::info::v1alpha1::GetVersionInfoResponse;
@@ -40,6 +42,22 @@ using grpc::InsecureChannelCredentials;
 using grpc::SslCredentials;
 using grpc::SslCredentialsOptions;
 
+PemFileCACertProvider::PemFileCACertProvider(const std::string &path) {
+  this->cacert = load_string_file(path);
+}
+
+auto PemFileCACertProvider::getCACertPem() const -> std::string {
+  return this->cacert;
+}
+
+PemCACertProvider::PemCACertProvider(const std::string &cacert) {
+  this->cacert = cacert;
+}
+
+auto PemCACertProvider::getCACertPem() const -> std::string {
+  return this->cacert;
+}
+
 CaosDBConnectionConfig::CaosDBConnectionConfig(const std::string &host,
                                                int port) {
   this->host = host;
@@ -61,12 +79,7 @@ auto operator<<(std::ostream &out, const CaosDBConnectionConfig &config)
 InsecureCaosDBConnectionConfig::InsecureCaosDBConnectionConfig(
   const std::string &host, int port)
   : CaosDBConnectionConfig(host, port) {
-  this->credentials = grpc::InsecureChannelCredentials();
-}
-
-auto InsecureCaosDBConnectionConfig::toString() const -> std::string {
-  return "InsecureCaosDBConnectionConfig(" + this->getHost() + "," +
-         std::to_string(this->getPort()) + ")";
+    this->credentials = grpc::InsecureChannelCredentials();
 }
 
 auto InsecureCaosDBConnectionConfig::getChannelCredentials() const
@@ -74,26 +87,34 @@ auto InsecureCaosDBConnectionConfig::getChannelCredentials() const
   return this->credentials;
 }
 
-SslCaosDBConnectionConfig::SslCaosDBConnectionConfig(
-  const std::string &host, int port, const std::string &cacert)
-  : CaosDBConnectionConfig(host, port) {
-  auto options = SslCredentialsOptions();
-  options.pem_root_certs = cacert;
-  this->credentials = SslCredentials(options);
+auto InsecureCaosDBConnectionConfig::toString() const -> std::string {
+  return "InsecureCaosDBConnectionConfig(" + this->getHost() + "," +
+         std::to_string(this->getPort()) + ")";
 }
 
 SslCaosDBConnectionConfig::SslCaosDBConnectionConfig(
-  const std::string &host, int port)
+  const std::string &host, int port, const std::shared_ptr<CACertificateProvider> &cacert)
   : CaosDBConnectionConfig(host, port) {
-  auto options = SslCredentialsOptions();
+  SslCredentialsOptions options;
+  options.pem_root_certs = cacert->getCACertPem();
   this->credentials = SslCredentials(options);
 }
 
+auto SslCaosDBConnectionConfig::getChannelCredentials() const
+  -> std::shared_ptr<ChannelCredentials> {
+  return this->credentials;
+}
+
+auto SslCaosDBConnectionConfig::toString() const -> std::string {
+  return "SslCaosDBConnectionConfig(" + this->getHost() + "," +
+         std::to_string(this->getPort()) + ")";
+}
+
+
 CaosDBConnection::CaosDBConnection(
   const std::shared_ptr<CaosDBConnectionConfig> &config) {
   this->config = config;
-  const std::string &target =
-    this->config->getHost() + ":" + std::to_string(this->config->getPort());
+  const std::string target = this->config->getHost() + ":" + std::to_string(this->config->getPort());
   const std::shared_ptr<grpc::Channel> &channel =
     grpc::CreateChannel(target, this->config->getChannelCredentials());
   this->stub_ = GeneralInfoService::NewStub(channel);
diff --git a/src/utils.cpp b/src/utils.cpp
new file mode 100644
index 0000000..bfb4049
--- /dev/null
+++ b/src/utils.cpp
@@ -0,0 +1,6 @@
+#include "utils.h"
+
+namespace caosdb::utils {
+
+
+} // namespace caosdb::utils
diff --git a/test/test_connection.cpp b/test/test_connection.cpp
index b8d4d53..d1a4157 100644
--- a/test/test_connection.cpp
+++ b/test/test_connection.cpp
@@ -27,7 +27,7 @@
 #include <type_traits>
 #include "gtest/gtest_pred_impl.h"
 
-TEST(test_connection, localhost_8080) {
+TEST(test_connection, configure_insecure_localhost_8080) {
   caosdb::InsecureCaosDBConnectionConfig config("localhost", 8000);
 
   ASSERT_EQ("localhost", config.getHost());
@@ -36,3 +36,14 @@ TEST(test_connection, localhost_8080) {
     config.getChannelCredentials();
   ASSERT_TRUE(icc != nullptr);
 }
+
+TEST(test_connection, configure_ssl_localhost_8080) {
+  auto cacert = std::make_shared<caosdb::PemCACertProvider>("ca chain");
+  caosdb::SslCaosDBConnectionConfig config("localhost", 44300, cacert);
+
+  ASSERT_EQ("localhost", config.getHost());
+  ASSERT_EQ(44300, config.getPort());
+  std::shared_ptr<grpc::ChannelCredentials> sslcc =
+    config.getChannelCredentials();
+  ASSERT_TRUE(sslcc != nullptr);
+}
-- 
GitLab