From 167ac6f3c39d4280c442ff9500dc65a08af48714 Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Wed, 14 Feb 2018 12:48:24 +0100 Subject: [PATCH] AWS: move from MbedTLS to OpenSSL for the connection There seems to be a crash in Amazon's MbedTLS code, let's see if openssl behaves better --- debian/control | 1 - libguh-core/MbedTLS/MbedTLSConnection.cpp | 348 ------------- libguh-core/MbedTLS/MbedTLSConnection.hpp | 183 ------- libguh-core/MbedTLS/README.txt | 6 - libguh-core/OpenSSL/OpenSSLConnection.cpp | 572 ++++++++++++++++++++++ libguh-core/OpenSSL/OpenSSLConnection.hpp | 286 +++++++++++ libguh-core/awsconnector.cpp | 3 +- libguh-core/awsconnector.h | 4 +- libguh-core/libguh-core.pro | 8 +- server/server.pro | 2 +- tests/auto/autotests.pri | 2 +- 11 files changed, 866 insertions(+), 549 deletions(-) delete mode 100644 libguh-core/MbedTLS/MbedTLSConnection.cpp delete mode 100644 libguh-core/MbedTLS/MbedTLSConnection.hpp delete mode 100644 libguh-core/MbedTLS/README.txt create mode 100644 libguh-core/OpenSSL/OpenSSLConnection.cpp create mode 100644 libguh-core/OpenSSL/OpenSSLConnection.hpp diff --git a/debian/control b/debian/control index a3203962..fb4a66cc 100644 --- a/debian/control +++ b/debian/control @@ -23,7 +23,6 @@ Build-Depends: debhelper (>= 9.0.0), libavahi-client-dev, libavahi-common-dev, libssl-dev, - libmbedtls-dev, libaws-iot-device-sdk-cpp, dbus-test-runner, diff --git a/libguh-core/MbedTLS/MbedTLSConnection.cpp b/libguh-core/MbedTLS/MbedTLSConnection.cpp deleted file mode 100644 index 9add91b4..00000000 --- a/libguh-core/MbedTLS/MbedTLSConnection.cpp +++ /dev/null @@ -1,348 +0,0 @@ -/* - * Copyright 2010-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/** - * @file MbedTLSConnection.hpp - * @brief - * - */ - -#include -#include - -#include "MbedTLSConnection.hpp" -#include "util/logging/LogMacros.hpp" - -#define MBEDTLS_WRAPPER_LOG_TAG "[MbedTLS Wrapper]" -#define MAX_CHARS_IN_PORT_NUMBER 6 - -namespace awsiotsdk { - namespace network { - MbedTLSConnection::MbedTLSConnection(util::String endpoint, - uint16_t endpoint_port, - util::String root_ca_location, - util::String device_cert_location, - util::String device_private_key_location, - std::chrono::milliseconds tls_handshake_timeout, - std::chrono::milliseconds tls_read_timeout, - std::chrono::milliseconds tls_write_timeout, - bool server_verification_flag) { - endpoint_ = endpoint; - endpoint_port_ = endpoint_port; - root_ca_location_ = root_ca_location; - device_cert_location_ = device_cert_location; - device_private_key_location_ = device_private_key_location; - server_verification_flag_ = server_verification_flag; - tls_handshake_timeout_ = tls_handshake_timeout; - tls_read_timeout_ = tls_read_timeout; - tls_write_timeout_ = tls_write_timeout; - flags_ = 0; - - is_connected_ = false; - requires_free_ = false; - } - - bool MbedTLSConnection::IsPhysicalLayerConnected() { - // Use this to add implementation which can check for physical layer disconnect - return true; - } - - bool MbedTLSConnection::IsConnected() { - return is_connected_; - } - - int MbedTLSConnection::VerifyCertificate(void *data, mbedtls_x509_crt *crt, int depth, uint32_t *flags) { - char buf[1024]; - ((void) data); - - AWS_LOG_INFO(MBEDTLS_WRAPPER_LOG_TAG, "Verify requested for (Depth %d):", depth); - mbedtls_x509_crt_info(buf, sizeof(buf) - 1, "", crt); - AWS_LOG_INFO(MBEDTLS_WRAPPER_LOG_TAG, "%s", buf); - - if ((*flags) == 0) { - AWS_LOG_INFO(MBEDTLS_WRAPPER_LOG_TAG, "This certificate has no flags"); - } else { - AWS_LOG_INFO(MBEDTLS_WRAPPER_LOG_TAG, buf, sizeof(buf), " ! ", *flags); - AWS_LOG_INFO(MBEDTLS_WRAPPER_LOG_TAG, "%s\n", buf); - } - - return 0; - } - - ResponseCode MbedTLSConnection::ConnectInternal() { - ResponseCode rc = ResponseCode::SUCCESS; - - int ret = 0; - const util::String pers = "aws_iot_tls_wrapper"; - char port_buf[6]; - char vrfy_buf[512]; - - mbedtls_net_init(&server_fd_); - mbedtls_ssl_init(&ssl_); - mbedtls_ssl_config_init(&conf_); - mbedtls_ctr_drbg_init(&ctr_drbg_); - mbedtls_x509_crt_init(&cacert_); - mbedtls_x509_crt_init(&clicert_); - mbedtls_pk_init(&pkey_); - - requires_free_ = true; - - AWS_LOG_INFO(MBEDTLS_WRAPPER_LOG_TAG, "...............................%d", MBEDTLS_SSL_MAX_CONTENT_LEN); - - AWS_LOG_INFO(MBEDTLS_WRAPPER_LOG_TAG, "....Seeding the random number generator..."); - mbedtls_entropy_init(&entropy_); - if ((ret = mbedtls_ctr_drbg_seed(&ctr_drbg_, mbedtls_entropy_func, &entropy_, - (const unsigned char *) (pers.c_str()), pers.length())) != 0) { - AWS_LOG_ERROR(MBEDTLS_WRAPPER_LOG_TAG, " Connect Failed!!! mbedtls_ctr_drbg_seed returned -0x%x", -ret); - return ResponseCode::NETWORK_SSL_INIT_ERROR; - } - - AWS_LOG_INFO(MBEDTLS_WRAPPER_LOG_TAG, "....Loading the CA root certificate... %s", root_ca_location_.c_str()); - ret = mbedtls_x509_crt_parse_file(&cacert_, root_ca_location_.c_str()); - if (ret < 0) { - AWS_LOG_ERROR(MBEDTLS_WRAPPER_LOG_TAG, - "Failed!!! mbedtls_x509_crt_parse returned -0x%x while parsing root cert\n\n", - -ret); - return ResponseCode::NETWORK_SSL_ROOT_CRT_PARSE_ERROR; - } - AWS_LOG_INFO(MBEDTLS_WRAPPER_LOG_TAG, "ok (%d skipped)\n", ret); - - AWS_LOG_INFO(MBEDTLS_WRAPPER_LOG_TAG, "....Loading the client cert. and key..."); - ret = mbedtls_x509_crt_parse_file(&clicert_, device_cert_location_.c_str()); - if (ret != 0) { - AWS_LOG_ERROR(MBEDTLS_WRAPPER_LOG_TAG, - "Failed!!! mbedtls_x509_crt_parse returned -0x%x while parsing device cert\n\n", - -ret); - return ResponseCode::NETWORK_SSL_DEVICE_CRT_PARSE_ERROR; - } - - ret = mbedtls_pk_parse_keyfile(&pkey_, device_private_key_location_.c_str(), ""); - if (ret != 0) { - AWS_LOG_ERROR(MBEDTLS_WRAPPER_LOG_TAG, - "Failed!!! mbedtls_pk_parse_key returned -0x%x while parsing private key\n\n", - -ret); - AWS_LOG_INFO(MBEDTLS_WRAPPER_LOG_TAG, " path : %s ", device_private_key_location_.c_str()); - return ResponseCode::NETWORK_SSL_KEY_PARSE_ERROR; - } - AWS_LOG_INFO(MBEDTLS_WRAPPER_LOG_TAG, " ok\n"); - snprintf(port_buf, MAX_CHARS_IN_PORT_NUMBER, "%d", endpoint_port_); - AWS_LOG_INFO(MBEDTLS_WRAPPER_LOG_TAG, "....Connecting to %s/%s...", endpoint_.c_str(), port_buf); - if ((ret = mbedtls_net_connect(&server_fd_, endpoint_.c_str(), port_buf, MBEDTLS_NET_PROTO_TCP)) != 0) { - AWS_LOG_ERROR(MBEDTLS_WRAPPER_LOG_TAG, "Failed!!! mbedtls_net_connect returned -0x%x\n\n", -ret); - switch (ret) { - case MBEDTLS_ERR_NET_SOCKET_FAILED: - return ResponseCode::NETWORK_TCP_SETUP_ERROR; - case MBEDTLS_ERR_NET_UNKNOWN_HOST: - return ResponseCode::NETWORK_TCP_UNKNOWN_HOST; - case MBEDTLS_ERR_NET_CONNECT_FAILED: - default: - return ResponseCode::NETWORK_TCP_CONNECT_ERROR; - }; - } - - ret = mbedtls_net_set_block(&server_fd_); - if (ret != 0) { - AWS_LOG_ERROR(MBEDTLS_WRAPPER_LOG_TAG, "Failed!!! net_set_(non)block() returned -0x%x\n\n", -ret); - return ResponseCode::NETWORK_SSL_UNKNOWN_ERROR; - } - AWS_LOG_INFO(MBEDTLS_WRAPPER_LOG_TAG, "Ok!"); - - AWS_LOG_INFO(MBEDTLS_WRAPPER_LOG_TAG, "....Setting up the SSL/TLS structure..."); - if ((ret = mbedtls_ssl_config_defaults(&conf_, MBEDTLS_SSL_IS_CLIENT, MBEDTLS_SSL_TRANSPORT_STREAM, - MBEDTLS_SSL_PRESET_DEFAULT)) != 0) { - AWS_LOG_ERROR(MBEDTLS_WRAPPER_LOG_TAG, - "Failed!!! mbedtls_ssl_config_defaults returned -0x%x\n\n", - -ret); - return ResponseCode::NETWORK_SSL_UNKNOWN_ERROR; - } - - mbedtls_ssl_conf_verify(&conf_, &MbedTLSConnection::VerifyCertificate, NULL); - if (server_verification_flag_) { - mbedtls_ssl_conf_authmode(&conf_, MBEDTLS_SSL_VERIFY_REQUIRED); - } else { - mbedtls_ssl_conf_authmode(&conf_, MBEDTLS_SSL_VERIFY_OPTIONAL); - } - mbedtls_ssl_conf_rng(&conf_, mbedtls_ctr_drbg_random, &ctr_drbg_); - - mbedtls_ssl_conf_ca_chain(&conf_, &cacert_, NULL); - if ((ret = mbedtls_ssl_conf_own_cert(&conf_, &clicert_, &pkey_)) != - 0) { - AWS_LOG_ERROR(MBEDTLS_WRAPPER_LOG_TAG, "Failed!!! mbedtls_ssl_conf_own_cert returned %d\n\n", ret); - return ResponseCode::NETWORK_SSL_UNKNOWN_ERROR; - } - - mbedtls_ssl_conf_read_timeout(&conf_, static_cast(tls_handshake_timeout_.count())); - if ((ret = mbedtls_ssl_set_hostname(&ssl_, endpoint_.c_str())) != 0) { - AWS_LOG_ERROR(MBEDTLS_WRAPPER_LOG_TAG, "Failed!!! mbedtls_ssl_set_hostname returned %d\n\n", ret); - return ResponseCode::NETWORK_SSL_UNKNOWN_ERROR; - } - AWS_LOG_INFO(MBEDTLS_WRAPPER_LOG_TAG, "\n\nSSL state connect : %d ", ssl_.state); - mbedtls_ssl_set_bio(&ssl_, &server_fd_, mbedtls_net_send, NULL, mbedtls_net_recv_timeout); - AWS_LOG_INFO(MBEDTLS_WRAPPER_LOG_TAG, "Ok!"); - - if ((ret = mbedtls_ssl_setup(&ssl_, &conf_)) != 0) { - AWS_LOG_ERROR(MBEDTLS_WRAPPER_LOG_TAG, "Failed!!! mbedtls_ssl_setup returned -0x%x\n\n", -ret); - return ResponseCode::NETWORK_SSL_UNKNOWN_ERROR; - } - - AWS_LOG_INFO(MBEDTLS_WRAPPER_LOG_TAG, "\n\nSSL state connect : %d ", ssl_.state); - AWS_LOG_INFO(MBEDTLS_WRAPPER_LOG_TAG, "....Performing the SSL/TLS handshake..."); - while ((ret = mbedtls_ssl_handshake(&ssl_)) != 0) { - if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { - AWS_LOG_ERROR(MBEDTLS_WRAPPER_LOG_TAG, "Failed!!! mbedtls_ssl_handshake returned -0x%x\n", -ret); - if (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED) { - AWS_LOG_ERROR(MBEDTLS_WRAPPER_LOG_TAG, " Unable to verify the server's certificate. " - "Either it is invalid,\n" - " or you didn't set ca_file or ca_path " - "to an appropriate value.\n" - " Alternatively, you may want to use " - "auth_mode=optional for testing purposes.\n"); - } - return ResponseCode::NETWORK_SSL_TLS_HANDSHAKE_ERROR; - } - } - - AWS_LOG_INFO(MBEDTLS_WRAPPER_LOG_TAG, - " ok\n [ Protocol is %s ]\n [ Ciphersuite is %s ]\n", - mbedtls_ssl_get_version(&ssl_), - mbedtls_ssl_get_ciphersuite(&ssl_)); - if ((ret = mbedtls_ssl_get_record_expansion(&ssl_)) >= 0) { - AWS_LOG_INFO(MBEDTLS_WRAPPER_LOG_TAG, " [ Record expansion is %d ]\n", ret); - } else { - AWS_LOG_INFO(MBEDTLS_WRAPPER_LOG_TAG, " [ Record expansion is unknown (compression) ]\n"); - } - - AWS_LOG_INFO(MBEDTLS_WRAPPER_LOG_TAG, "....Verifying peer X.509 certificate..."); - - if (server_verification_flag_) { - if ((flags_ = mbedtls_ssl_get_verify_result(&ssl_)) != 0) { - AWS_LOG_ERROR(MBEDTLS_WRAPPER_LOG_TAG, " failed\n"); - mbedtls_x509_crt_verify_info(vrfy_buf, sizeof(vrfy_buf), " ! ", flags_); - AWS_LOG_ERROR(MBEDTLS_WRAPPER_LOG_TAG, "%s\n", vrfy_buf); - rc = ResponseCode::NETWORK_SSL_SERVER_VERIFICATION_ERROR; - } else { - AWS_LOG_INFO(MBEDTLS_WRAPPER_LOG_TAG, " ok\n"); - rc = ResponseCode::SUCCESS; - } - } else { - AWS_LOG_INFO(MBEDTLS_WRAPPER_LOG_TAG, " Server Verification skipped\n"); - rc = ResponseCode::SUCCESS; - } - - mbedtls_ssl_conf_read_timeout(&conf_, static_cast(tls_read_timeout_.count())); - is_connected_ = true; - return rc; - } - - ResponseCode MbedTLSConnection::WriteInternal(const util::String &buf, size_t &size_written_bytes_out) { - size_t total_written_length = 0; - ResponseCode rc = ResponseCode::SUCCESS; - size_t bytes_to_write = buf.length(); - const unsigned char *buf_cstr = (const unsigned char *) (buf.c_str()); - bool isErrorFlag = false; - int ret; - - auto timeout = std::chrono::system_clock::now() + tls_write_timeout_; - auto now = std::chrono::system_clock::now(); - - do { - ret = mbedtls_ssl_write(&ssl_, buf_cstr + total_written_length, bytes_to_write - total_written_length); - if (ret > 0) { - total_written_length += ret; - } else if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE) { - AWS_LOG_ERROR(MBEDTLS_WRAPPER_LOG_TAG, "Failed!!! mbedtls_ssl_write returned -0x%x\n\n", -ret); - /* All other negative return values indicate connection needs to be reset. - * Will be caught in ping request so ignored here */ - isErrorFlag = true; - break; - } - now = std::chrono::system_clock::now(); - } while (now < timeout && total_written_length < bytes_to_write); - - size_written_bytes_out = total_written_length; - - if (isErrorFlag) { - rc = ResponseCode::NETWORK_SSL_WRITE_ERROR; - } else if (now < timeout && total_written_length != bytes_to_write) { - return ResponseCode::NETWORK_SSL_WRITE_TIMEOUT_ERROR; - } - - return rc; - } - - ResponseCode MbedTLSConnection::ReadInternal(util::Vector &buf, size_t buf_read_offset, - size_t size_bytes_to_read, size_t &size_read_bytes_out) { - int ret; - size_t total_read_length = 0; - size_t remaining_bytes_to_read = size_bytes_to_read; - auto timeout = std::chrono::system_clock::now() + tls_read_timeout_; - do { - // This read will timeout after IOT_SSL_READ_TIMEOUT if there's no data to be read - ret = mbedtls_ssl_read(&ssl_, &buf[buf_read_offset], remaining_bytes_to_read); - if (ret > 0) { - buf_read_offset += ret; - total_read_length += ret; - remaining_bytes_to_read -= ret; - size_read_bytes_out = total_read_length; - } else if (ret != MBEDTLS_ERR_SSL_WANT_READ && ret != MBEDTLS_ERR_SSL_WANT_WRITE - && ret != MBEDTLS_ERR_SSL_TIMEOUT) { - return ResponseCode::NETWORK_SSL_READ_ERROR; - } - } while (remaining_bytes_to_read > 0 && timeout < std::chrono::system_clock::now()); - - if (0 == total_read_length) { - return ResponseCode::NETWORK_SSL_NOTHING_TO_READ; - } else if (size_read_bytes_out == size_bytes_to_read) { - return ResponseCode::SUCCESS; - } else if (0 < total_read_length) { - return ResponseCode::NETWORK_SSL_READ_TIMEOUT_ERROR; - } - - return ResponseCode::NETWORK_SSL_READ_ERROR; - } - - ResponseCode MbedTLSConnection::DisconnectInternal() { - if (is_connected_) { - int ret = 0; - do { - ret = mbedtls_ssl_close_notify(&ssl_); - } while (ret == MBEDTLS_ERR_SSL_WANT_WRITE); - } - - if(requires_free_) { - mbedtls_net_free(&server_fd_); - - mbedtls_x509_crt_free(&clicert_); - mbedtls_x509_crt_free(&cacert_); - mbedtls_pk_free(&pkey_); - mbedtls_ssl_free(&ssl_); - mbedtls_ssl_config_free(&conf_); - mbedtls_ctr_drbg_free(&ctr_drbg_); - mbedtls_entropy_free(&entropy_); - requires_free_ = false; - } - - is_connected_ = false; - - /* All other negative return values indicate connection needs to be reset. - * No further action required since this is disconnect call */ - return ResponseCode::SUCCESS; - } - - MbedTLSConnection::~MbedTLSConnection() { - Disconnect(); - } - } -} diff --git a/libguh-core/MbedTLS/MbedTLSConnection.hpp b/libguh-core/MbedTLS/MbedTLSConnection.hpp deleted file mode 100644 index 751181df..00000000 --- a/libguh-core/MbedTLS/MbedTLSConnection.hpp +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright 2010-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -/** - * @file MbedTLSConnection.hpp - * @brief Defines a reference implementation for an MbedTLS library wrapper * - */ - -#pragma once - -#include - -#include "mbedtls/config.h" - -#include "mbedtls/platform.h" -#ifdef MBEDTLS_NEW_HEADERS -#include "mbedtls/net_sockets.h" -#else -#include "mbedtls/net.h" -#endif -#include "mbedtls/ssl.h" -#include "mbedtls/entropy.h" -#include "mbedtls/ctr_drbg.h" -#include "mbedtls/certs.h" -#include "mbedtls/x509.h" -#include "mbedtls/error.h" -#include "mbedtls/debug.h" -#include "mbedtls/timing.h" - -#include "NetworkConnection.hpp" -#include "ResponseCode.hpp" - -namespace awsiotsdk { - namespace network { - /** - * @brief MbedTLS Wrapper Class - * - * Defines a reference wrapper for MbedTLS libraries - */ - class MbedTLSConnection : public NetworkConnection { - protected: - util::String root_ca_location_; ///< Pointer to string containing the filename (including path) of the root CA file. - util::String device_cert_location_; ///< Pointer to string containing the filename (including path) of the device certificate. - util::String device_private_key_location_; ///< Pointer to string containing the filename (including path) of the device private key file. - std::atomic_bool server_verification_flag_; ///< Boolean. True = perform server certificate hostname validation. False = skip validation \b NOT recommended. - std::atomic_bool is_connected_; ///< Boolean indicating connection status - std::chrono::milliseconds tls_handshake_timeout_; ///< Timeout for TLS handshake command - std::chrono::milliseconds tls_read_timeout_; ///< Timeout for the TLS Read command - std::chrono::milliseconds tls_write_timeout_; ///< Timeout for the TLS Write command - - // Endpoint information - uint16_t endpoint_port_; ///< Endpoint port - util::String endpoint_; ///< Endpoint for this connection - - mbedtls_entropy_context entropy_; - mbedtls_ctr_drbg_context ctr_drbg_; - mbedtls_ssl_context ssl_; - mbedtls_ssl_config conf_; - uint32_t flags_; - mbedtls_x509_crt cacert_; - mbedtls_x509_crt clicert_; - mbedtls_pk_context pkey_; - mbedtls_net_context server_fd_; - - // TODO: This is a Hotfix, requires a better approach - std::atomic_bool requires_free_; ///< Boolean indicating whether the mbedtls struct variables have been allocated or not - - /** - * @brief Create a TLS socket and open the connection - * - * Creates an open socket connection including TLS handshake. - * - * @return ResponseCode - successful connection or TLS error - */ - ResponseCode ConnectInternal(); - - /** - * @brief Write bytes to the network socket - * - * @param util::String - const reference to buffer which should be written to socket - * @return size_t - number of bytes written or Network error - * @return ResponseCode - successful write or Network error code - */ - ResponseCode WriteInternal(const util::String &buf, size_t &size_written_bytes_out); - - /** - * @brief Read bytes from the network socket - * - * @param util::String - reference to buffer where read bytes should be copied - * @param size_t - number of bytes to read - * @param size_t - reference to store number of bytes read - * @return ResponseCode - successful read or TLS error code - */ - ResponseCode ReadInternal(util::Vector &buf, size_t buf_read_offset, - size_t size_bytes_to_read, size_t &size_read_bytes_out); - - /** - * @brief Disconnect from network socket - * - * @return ResponseCode - successful read or TLS error code - */ - ResponseCode DisconnectInternal(); - - public: - static int VerifyCertificate(void *data, mbedtls_x509_crt *crt, int depth, uint32_t *flags); - - /** - * @brief Constructor for the MbedTLS TLS implementation - * - * Performs any initialization required by the TLS layer. - * - * @param util::String endpoint - The target endpoint to connect to - * @param uint16_t endpoint_port - The port on the target to connect to - * @param util::String root_ca_location - Path of the location of the Root CA - * @param util::String device_cert_location - Path to the location of the Device Cert - * @param util::String device_private_key_location - Path to the location of the device private key file - * @param std::chrono::milliseconds tls_handshake_timeout - The value to use for timeout of handshake operation - * @param std::chrono::milliseconds tls_read_timeout - The value to use for timeout of read operation - * @param std::chrono::milliseconds tls_write_timeout - The value to use for timeout of write operation - * @param bool server_verification_flag - used to decide whether server verification is needed or not - * - */ - MbedTLSConnection(util::String endpoint, uint16_t endpoint_port, util::String root_ca_location, - util::String device_cert_location, util::String device_private_key_location, - std::chrono::milliseconds tls_handshake_timeout, - std::chrono::milliseconds tls_read_timeout, std::chrono::milliseconds tls_write_timeout, - bool server_verification_flag); - - /** - * @brief Check if TLS layer is still connected - * - * Called to check if the TLS layer is still connected or not. - * - * @return bool - indicating status of network TLS layer connection - */ - bool IsConnected(); - - /** - * @brief Check if Network Physical layer is still connected - * - * Called to check if the Network Physical layer is still connected or not. - * - * @return bool - indicating status of network physical layer connection - */ - bool IsPhysicalLayerConnected(); - - /** - * @brief sets the path to the root CA - * - * Called to change the location of the root CA after the constructor has initialized the OpenSSL object. - * - * @param root_ca_location - */ - void SetRootCAPath(util::String root_ca_location) { root_ca_location_ = root_ca_location; } - - /** - * @brief sets the endpoint and the port - * - * Called to change the endpoint and the port after the constructor has initialized the OpenSSL object. - * @param endpoint - * @param endpoint_port - */ - void SetEndpointAndPort(util::String endpoint, uint16_t endpoint_port) { - endpoint_ = endpoint; - endpoint_port_ = endpoint_port; - } - - virtual ~MbedTLSConnection(); - }; - } -} diff --git a/libguh-core/MbedTLS/README.txt b/libguh-core/MbedTLS/README.txt deleted file mode 100644 index a1bbd537..00000000 --- a/libguh-core/MbedTLS/README.txt +++ /dev/null @@ -1,6 +0,0 @@ -The files in this folder have been taken from - -https://github.com/aws/aws-iot-device-sdk-cpp - -under the term of the Apache License, Version 2.0 - diff --git a/libguh-core/OpenSSL/OpenSSLConnection.cpp b/libguh-core/OpenSSL/OpenSSLConnection.cpp new file mode 100644 index 00000000..3bee7040 --- /dev/null +++ b/libguh-core/OpenSSL/OpenSSLConnection.cpp @@ -0,0 +1,572 @@ +/* + * Copyright 2010-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +/** + * @file OpenSSLConnection.hpp + * @brief + * + */ + +#include +#include + +#include "OpenSSLConnection.hpp" +#include "util/logging/LogMacros.hpp" + +#ifdef WIN32 +#define MAX_PATH_LENGTH_ 260 +#include +#define getcwd _getcwd // avoid MSFT "deprecation" warning +#else +#include +#include +#include +#define MAX_PATH_LENGTH_ PATH_MAX +#endif + +#define OPENSSL_WRAPPER_LOG_TAG "[OpenSSL Wrapper]" + +namespace awsiotsdk { + namespace network { + OpenSSLInitializer::~OpenSSLInitializer() { + CONF_modules_free(); +#if OPENSSL_VERSION_NUMBER >= 0x10002000L && OPENSSL_VERSION_NUMBER < 0x10100000L + ERR_remove_thread_state(NULL); +#endif + CONF_modules_unload(1); + SSL_COMP_free_compression_methods(); + ERR_free_strings(); + EVP_cleanup(); + CRYPTO_cleanup_all_ex_data(); + } + + OpenSSLInitializer *OpenSSLInitializer::getInstance() { + static OpenSSLInitializer initializer; + return &initializer; + } + + std::atomic_bool OpenSSLConnection::is_lib_initialized(false); + + OpenSSLConnection::OpenSSLConnection(util::String endpoint, uint16_t endpoint_port, + std::chrono::milliseconds tls_handshake_timeout, + std::chrono::milliseconds tls_read_timeout, + std::chrono::milliseconds tls_write_timeout, + bool server_verification_flag) { + endpoint_ = endpoint; + endpoint_port_ = endpoint_port; + server_verification_flag_ = server_verification_flag; + int timeout_ms = static_cast(tls_handshake_timeout.count()); + tls_handshake_timeout_ = {timeout_ms / 1000, (timeout_ms % 1000) * 1000}; + timeout_ms = static_cast(tls_read_timeout.count()); + tls_read_timeout_ = {timeout_ms / 1000, (timeout_ms % 1000) * 1000}; + timeout_ms = static_cast(tls_write_timeout.count()); + tls_write_timeout_ = {timeout_ms / 1000, (timeout_ms % 1000) * 1000}; + + is_connected_ = false; + certificates_read_flag_ = false; + initializer = OpenSSLInitializer::getInstance(); + p_ssl_handle_ = nullptr; + } + + OpenSSLConnection::OpenSSLConnection(util::String endpoint, + uint16_t endpoint_port, + util::String root_ca_location, + util::String device_cert_location, + util::String device_private_key_location, + std::chrono::milliseconds tls_handshake_timeout, + std::chrono::milliseconds tls_read_timeout, + std::chrono::milliseconds tls_write_timeout, + bool server_verification_flag) + : OpenSSLConnection(endpoint, endpoint_port, tls_handshake_timeout, tls_read_timeout, tls_write_timeout, + server_verification_flag) { + root_ca_location_ = root_ca_location; + device_cert_location_ = device_cert_location; + device_private_key_location_ = device_private_key_location; + } + + OpenSSLConnection::OpenSSLConnection(util::String endpoint, + uint16_t endpoint_port, + util::String root_ca_location, + std::chrono::milliseconds tls_handshake_timeout, + std::chrono::milliseconds tls_read_timeout, + std::chrono::milliseconds tls_write_timeout, + bool server_verification_flag) + : OpenSSLConnection(endpoint, endpoint_port, tls_handshake_timeout, tls_read_timeout, tls_write_timeout, + server_verification_flag) { + root_ca_location_ = root_ca_location; + device_cert_location_.clear(); + device_private_key_location_.clear(); + } + + int OpenSSLConnection::WaitForSelect(int error_code) { + fd_set socketFds; + struct timeval timeout = {tls_write_timeout_.tv_sec, tls_write_timeout_.tv_usec}; + FD_ZERO(&socketFds); + FD_SET(server_tcp_socket_fd_, &socketFds); + if (SSL_ERROR_WANT_READ == error_code) { + return select(server_tcp_socket_fd_ + 1, &socketFds, NULL, NULL, &timeout); + } else if (SSL_ERROR_WANT_WRITE == error_code) { + return select(server_tcp_socket_fd_ + 1, NULL, &socketFds, NULL, &timeout); + } else { + return 0; + } + } + + ResponseCode OpenSSLConnection::Initialize() { +#ifdef WIN32 + // TODO : Check if it is possible to replace this with std::call_once + WSADATA wsa_data; + int result; + bool was_wsa_initialized = true; + int s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if(INVALID_SOCKET == s) { + if(WSANOTINITIALISED == WSAGetLastError()) { + was_wsa_initialized = false; + } + } else { + closesocket(s); + } + + if(!was_wsa_initialized) { + // Initialize Winsock + result = WSAStartup(MAKEWORD(2, 2), &wsa_data); + if(0 != result) { + AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, "WSAStartup failed: %d", result); + return ResponseCode::NETWORK_SSL_INIT_ERROR; + } + } +#endif + + if (!is_lib_initialized) { + OpenSSL_add_all_algorithms(); + ERR_load_BIO_strings(); + ERR_load_crypto_strings(); + SSL_load_error_strings(); + signal(SIGPIPE, SIG_IGN); + is_lib_initialized = true; + } + const SSL_METHOD *method; + + if (SSL_library_init() < 0) { + return ResponseCode::NETWORK_SSL_INIT_ERROR; + } + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L && OPENSSL_VERSION_NUMBER < 0x10100000L + method = TLSv1_2_method(); +#else + method = TLS_method(); +#endif + + if ((p_ssl_context_ = SSL_CTX_new(method)) == NULL) { + AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, " SSL INIT Failed - Unable to create SSL Context"); + return ResponseCode::NETWORK_SSL_INIT_ERROR; + } + + return ResponseCode::SUCCESS; + } + + bool OpenSSLConnection::IsPhysicalLayerConnected() { + // Use this to add implementation which can check for physical layer disconnect + return true; + } + + bool OpenSSLConnection::IsConnected() { + return is_connected_; + } + + ResponseCode OpenSSLConnection::ConnectTCPSocket() { + const char *endpoint_char = endpoint_.c_str(); + if (nullptr == endpoint_char) { + return ResponseCode::NETWORK_TCP_NO_ENDPOINT_SPECIFIED; + } + +#ifndef WIN32 + if (res_init() == -1) { + AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, "DNS initialize error"); + } +#endif + + hostent *host = gethostbyname(endpoint_char); + if (nullptr == host) { + return ResponseCode::NETWORK_TCP_NO_ENDPOINT_SPECIFIED; + } + + sockaddr_in dest_addr; + + dest_addr.sin_family = AF_INET; + dest_addr.sin_port = htons(endpoint_port_); + dest_addr.sin_addr.s_addr = *(uint32_t *) (host->h_addr); + memset(&(dest_addr.sin_zero), '\0', 8); + + AWS_LOG_INFO(OPENSSL_WRAPPER_LOG_TAG, + "resolved %s to %s", + endpoint_.c_str(), + inet_ntoa(dest_addr.sin_addr)); + + int connect_status = connect(server_tcp_socket_fd_, (sockaddr *) &dest_addr, sizeof(sockaddr)); + if (-1 != connect_status) { + return ResponseCode::SUCCESS; + } + +#ifdef WIN32 + closesocket(server_tcp_socket_fd_); +#else + close(server_tcp_socket_fd_); +#endif + AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, "connect - %s", strerror(errno)); + return ResponseCode::NETWORK_TCP_CONNECT_ERROR; + } + + ResponseCode OpenSSLConnection::SetSocketToNonBlocking() { + int status; + ResponseCode ret_val = ResponseCode::SUCCESS; +#if defined(WIN32) || defined(WIN64) + u_long flag = 1L; + status = ioctlsocket(server_tcp_socket_fd_, FIONBIO, &flag); + if (0 > status) { + AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, "ioctlsocket - %s", strerror(errno)); + ret_val = ResponseCode::NETWORK_TCP_CONNECT_ERROR; + } +#else + int flags = fcntl(server_tcp_socket_fd_, F_GETFL, 0); + // set underlying socket to non blocking + if (0 > flags) { + ret_val = ResponseCode::NETWORK_TCP_CONNECT_ERROR; + } + + status = fcntl(server_tcp_socket_fd_, F_SETFL, flags | O_NONBLOCK); + if (0 > status) { + AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, "fcntl - %s", strerror(errno)); + ret_val = ResponseCode::NETWORK_TCP_CONNECT_ERROR; + } +#endif + + return ret_val; + } + + ResponseCode OpenSSLConnection::AttemptConnect() { + ResponseCode ret_val = ResponseCode::FAILURE; + int rc = 0; + int errorCode = 0; + int select_retCode = 0; + + do { + ERR_clear_error(); + rc = SSL_connect(p_ssl_handle_); + + if (1 == rc) { //1 = SSL_CONNECTED, <= 0 is Error + ret_val = ResponseCode::SUCCESS; + break; + } + + errorCode = SSL_get_error(p_ssl_handle_, rc); + + if (SSL_ERROR_WANT_READ == errorCode) { + select_retCode = WaitForSelect(errorCode); + if (0 == select_retCode) { // 0 == SELECT_TIMEOUT + AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, " SSL Connect time out while waiting for read"); + ret_val = ResponseCode::NETWORK_SSL_CONNECT_TIMEOUT_ERROR; + } else if (-1 == select_retCode) { // -1 == SELECT_ERROR + AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, " SSL Connect Select error for read %d", select_retCode); + ret_val = ResponseCode::NETWORK_SSL_CONNECT_ERROR; + } + } else if (SSL_ERROR_WANT_WRITE == errorCode) { + select_retCode = WaitForSelect(errorCode); + if (0 == select_retCode) { // 0 == SELECT_TIMEOUT + AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, " SSL Connect time out while waiting for write"); + ret_val = ResponseCode::NETWORK_SSL_CONNECT_TIMEOUT_ERROR; + } else if (-1 == select_retCode) { // -1 == SELECT_ERROR + AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, + " SSL Connect Select error for write %d", + select_retCode); + ret_val = ResponseCode::NETWORK_SSL_CONNECT_ERROR; + } + } else { + ret_val = ResponseCode::NETWORK_SSL_CONNECT_ERROR; + } + + } while (ResponseCode::NETWORK_SSL_CONNECT_ERROR != ret_val && + ResponseCode::NETWORK_SSL_CONNECT_TIMEOUT_ERROR != ret_val); + + return ret_val; + } + + ResponseCode OpenSSLConnection::LoadCerts() { + AWS_LOG_DEBUG(OPENSSL_WRAPPER_LOG_TAG, "Root CA : %s", root_ca_location_.c_str()); + if (!SSL_CTX_load_verify_locations(p_ssl_context_, root_ca_location_.c_str(), NULL)) { + AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, " Root CA Loading error"); + return ResponseCode::NETWORK_SSL_ROOT_CRT_PARSE_ERROR; + } + + // TODO: streamline error codes for TLS + if (0 < device_cert_location_.length() && 0 < device_private_key_location_.length()) { + AWS_LOG_DEBUG(OPENSSL_WRAPPER_LOG_TAG, "Device crt : %s", device_cert_location_.c_str()); + if (!SSL_CTX_use_certificate_chain_file(p_ssl_context_, device_cert_location_.c_str())) { + AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, " Device Certificate Loading error"); + return ResponseCode::NETWORK_SSL_DEVICE_CRT_PARSE_ERROR; + } + AWS_LOG_DEBUG(OPENSSL_WRAPPER_LOG_TAG, "Device privkey : %s", device_private_key_location_.c_str()); + if (1 != SSL_CTX_use_PrivateKey_file(p_ssl_context_, + device_private_key_location_.c_str(), + SSL_FILETYPE_PEM)) { + AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, " Device Private Key Loading error"); + return ResponseCode::NETWORK_SSL_KEY_PARSE_ERROR; + } + } + + certificates_read_flag_ = true; + return ResponseCode::SUCCESS; + } + + ResponseCode OpenSSLConnection::PerformSSLConnect() { + ResponseCode networkResponse = ResponseCode::SUCCESS; + + // Configure a non-zero callback if desired + SSL_set_verify(p_ssl_handle_, SSL_VERIFY_PEER, nullptr); + + server_tcp_socket_fd_ = socket(AF_INET, SOCK_STREAM, 0); + if (-1 == server_tcp_socket_fd_) { + return ResponseCode::NETWORK_TCP_SETUP_ERROR; + } + + networkResponse = ConnectTCPSocket(); + if (ResponseCode::SUCCESS != networkResponse) { + AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, "TCP Connection error"); + return networkResponse; + } + + SSL_set_fd(p_ssl_handle_, server_tcp_socket_fd_); + + networkResponse = SetSocketToNonBlocking(); + if (ResponseCode::SUCCESS != networkResponse) { + AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, " Unable to set the socket to Non-Blocking"); + } else { + networkResponse = AttemptConnect(); + if (X509_V_OK != SSL_get_verify_result(p_ssl_handle_)) { + AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, " Server Certificate Verification failed."); + networkResponse = ResponseCode::NETWORK_SSL_CONNECT_ERROR; + } else { + // ensure you have a valid certificate returned, otherwise no certificate exchange happened + auto cert_destroyer = [](X509 *cert) { + if (nullptr != cert) X509_free(cert); + }; + std::unique_ptr cert(SSL_get_peer_certificate(p_ssl_handle_), + cert_destroyer); + if (nullptr == cert) { + AWS_LOG_ERROR(OPENSSL_WRAPPER_LOG_TAG, " No certificate exchange happened"); + networkResponse = ResponseCode::NETWORK_SSL_CONNECT_ERROR; + } + } + } + + if (ResponseCode::SUCCESS != networkResponse) { +#ifdef WIN32 + closesocket(server_tcp_socket_fd_); +#else + close(server_tcp_socket_fd_); +#endif + } + + return networkResponse; + } + + ResponseCode OpenSSLConnection::ConnectInternal() { + ResponseCode networkResponse = ResponseCode::SUCCESS; + + X509_VERIFY_PARAM *param = nullptr; + + if (!certificates_read_flag_) { + networkResponse = LoadCerts(); + if (ResponseCode::SUCCESS != networkResponse) { + return networkResponse; + } + } + + if (nullptr == p_ssl_handle_) { + p_ssl_handle_ = SSL_new(p_ssl_context_); + } + + // Requires OpenSSL v1.0.2 and above + if (server_verification_flag_) { + param = SSL_get0_param(p_ssl_handle_); + // Enable automatic hostname checks + X509_VERIFY_PARAM_set_hostflags(param, X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); + + // Check if it is an IPv4 or an IPv6 address to enable ip checking + // Enable host name check otherwise + char dst[INET6_ADDRSTRLEN]; + if (inet_pton(AF_INET, endpoint_.c_str(), (void *) dst) || + inet_pton(AF_INET6, endpoint_.c_str(), (void *) dst)) { + X509_VERIFY_PARAM_set1_ip_asc(param, endpoint_.c_str()); + } else { + X509_VERIFY_PARAM_set1_host(param, endpoint_.c_str(), 0); + } + } + + networkResponse = PerformSSLConnect(); + if (ResponseCode::SUCCESS != networkResponse) { + SSL_free(p_ssl_handle_); + p_ssl_handle_ = nullptr; + } + + if (ResponseCode::SUCCESS == networkResponse) { + is_connected_ = true; + } + + return networkResponse; + } + + ResponseCode OpenSSLConnection::WriteInternal(const util::String &buf, size_t &size_written_bytes_out) { + int error_code = 0; + int select_retCode = -1; + int cur_written_length = 0; + size_t total_written_length = 0; + ResponseCode rc = ResponseCode::SUCCESS; + size_t bytes_to_write = buf.length(); + + do { + ERR_clear_error(); + if (nullptr == p_ssl_handle_) { + return ResponseCode::NETWORK_SSL_WRITE_ERROR; + } + cur_written_length = SSL_write(p_ssl_handle_, buf.c_str(), bytes_to_write); + error_code = SSL_get_error(p_ssl_handle_, cur_written_length); + if (0 < cur_written_length) { + total_written_length += (size_t) cur_written_length; + } else if (SSL_ERROR_WANT_WRITE == error_code) { + select_retCode = WaitForSelect(error_code); + if (0 == select_retCode) { //0 == SELECT_TIMEOUT + rc = ResponseCode::NETWORK_SSL_WRITE_TIMEOUT_ERROR; + } else if (-1 == select_retCode) { //-1 == SELECT_TIMEOUT + rc = ResponseCode::NETWORK_SSL_WRITE_ERROR; + } + } else { + rc = ResponseCode::NETWORK_SSL_WRITE_ERROR; + } + + } while (is_connected_ && ResponseCode::NETWORK_SSL_WRITE_ERROR != rc && + ResponseCode::NETWORK_SSL_WRITE_TIMEOUT_ERROR != rc && + total_written_length < bytes_to_write); + + if (ResponseCode::SUCCESS == rc) { + size_written_bytes_out = total_written_length; + } + + return rc; + } + + ResponseCode OpenSSLConnection::ReadInternal(util::Vector &buf, size_t buf_read_offset, + size_t size_bytes_to_read, size_t &size_read_bytes_out) { + int ssl_retcode; + int select_retCode; + size_t total_read_length = buf_read_offset; + size_t remaining_bytes_to_read = size_bytes_to_read; + int cur_read_len = 0; + ResponseCode errorStatus = ResponseCode::SUCCESS; + + do { + ERR_clear_error(); + if (nullptr == p_ssl_handle_) { + return ResponseCode::NETWORK_SSL_READ_ERROR; + } + cur_read_len = SSL_read(p_ssl_handle_, &buf[total_read_length], (int) remaining_bytes_to_read); + if (0 < cur_read_len) { + total_read_length += (size_t) cur_read_len; + remaining_bytes_to_read -= cur_read_len; + } else { + ssl_retcode = SSL_get_error(p_ssl_handle_, cur_read_len); + switch (ssl_retcode) { + case SSL_ERROR_WANT_READ: + select_retCode = WaitForSelect(SSL_ERROR_WANT_READ); + if (0 < select_retCode) { + continue; + } else if (0 == select_retCode) { //0 == SELECT_TIMEOUT + errorStatus = ResponseCode::NETWORK_SSL_NOTHING_TO_READ; + } else { // SELECT_ERROR + errorStatus = ResponseCode::NETWORK_SSL_READ_ERROR; + } + break; + case SSL_ERROR_ZERO_RETURN: + errorStatus = ResponseCode::NETWORK_SSL_CONNECTION_CLOSED_ERROR; + break; + default: + errorStatus = ResponseCode::NETWORK_SSL_READ_ERROR; + break; + } + } + if (ResponseCode::NETWORK_SSL_NOTHING_TO_READ == errorStatus || + ResponseCode::NETWORK_SSL_READ_ERROR == errorStatus || + ResponseCode::NETWORK_SSL_CONNECTION_CLOSED_ERROR == errorStatus) { + break; + } + } while (is_connected_ && total_read_length < size_bytes_to_read); + + if (ResponseCode::SUCCESS == errorStatus) { + size_read_bytes_out = total_read_length; + } + + return errorStatus; + } + + ResponseCode OpenSSLConnection::DisconnectInternal() { + if (!is_connected_) { + return ResponseCode::SUCCESS; + } + is_connected_ = false; + + std::chrono::milliseconds timeout = std::chrono::milliseconds(tls_read_timeout_.tv_sec * 1000 + + tls_read_timeout_.tv_usec / 1000); + + std::unique_lock shutdown_lock(clean_shutdown_action_lock_); + + // TODO: add config for disconnect timeout + // wait for tls_read_timeout and then exit the shutdown loop if it is not successful + this->shutdown_timeout_condition_.wait_for(shutdown_lock, std::chrono::milliseconds(timeout), [this] { + int rc = SSL_shutdown(p_ssl_handle_); + if (1 == rc) { + return true; + } + int errorCode = SSL_get_error(p_ssl_handle_, rc); + WaitForSelect(errorCode); + return false; + }); + + SSL_free(p_ssl_handle_); + p_ssl_handle_ = nullptr; + +#if OPENSSL_VERSION_NUMBER >= 0x10002000L && OPENSSL_VERSION_NUMBER < 0x10100000L + ERR_remove_thread_state(NULL); +#endif + + certificates_read_flag_ = false; +#ifdef WIN32 + closesocket(server_tcp_socket_fd_); +#else + close(server_tcp_socket_fd_); +#endif + return ResponseCode::SUCCESS; + } + + OpenSSLConnection::~OpenSSLConnection() { + if (is_connected_) { + Disconnect(); + } + SSL_CTX_free(p_ssl_context_); +#ifdef WIN32 + WSACleanup(); +#endif + } + } +} diff --git a/libguh-core/OpenSSL/OpenSSLConnection.hpp b/libguh-core/OpenSSL/OpenSSLConnection.hpp new file mode 100644 index 00000000..e8aff31e --- /dev/null +++ b/libguh-core/OpenSSL/OpenSSLConnection.hpp @@ -0,0 +1,286 @@ +/* + * Copyright 2010-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +/** + * @file OpenSSLConnection.hpp + * @brief Defines a reference implementation for an OpenSSL library wrapper + */ + +#pragma once + +#ifdef WIN32 +#include +#include +#pragma comment(lib,"ws2_32") +#else +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "NetworkConnection.hpp" +#include "ResponseCode.hpp" + +namespace awsiotsdk { + namespace network { + /** + * @brief Dummy class for uninitializing the OpenSSL library' + * + * TODO: This is a hotfix and will be updated after design changes + */ + class OpenSSLInitializer { + private: + // Rule of 5 stuff + // Default constructor + OpenSSLInitializer() = default; + // Default Copy constructor + OpenSSLInitializer(const OpenSSLInitializer &) = default; + // Default Move constructor + OpenSSLInitializer(OpenSSLInitializer &&) = default; + // Default Copy assignment operator + OpenSSLInitializer &operator=(const OpenSSLInitializer &) & = default; + // Default Move assignment operator + OpenSSLInitializer &operator=(OpenSSLInitializer &&) & = default; + + public: + static OpenSSLInitializer* getInstance(); + + ~OpenSSLInitializer(); + + }; + + /** + * @brief OpenSSL Wrapper Class + * + * Defines a reference wrapper for OpenSSL libraries + */ + class OpenSSLConnection : public NetworkConnection { + protected: + static std::atomic_bool is_lib_initialized; ///< Boolean, True = Library is initialized, False otherwise + OpenSSLInitializer *initializer; ///< Pointer to dummy library instance + util::String root_ca_location_; ///< Pointer to string containing the filename (including path) of the root CA file. + util::String device_cert_location_; ///< Pointer to string containing the filename (including path) of the device certificate. + util::String device_private_key_location_; ///< Pointer to string containing the filename (including path) of the device private key file. + bool server_verification_flag_; ///< Boolean. True = perform server certificate hostname validation. False = skip validation \b NOT recommended. + std::atomic_bool is_connected_; ///< Boolean indicating connection status + struct timeval tls_handshake_timeout_; ///< Timeout for TLS handshake command + struct timeval tls_read_timeout_; ///< Timeout for the TLS Read command + struct timeval tls_write_timeout_; ///< Timeout for the TLS Write command + + // Endpoint information + uint16_t endpoint_port_; ///< Endpoint port + util::String endpoint_; ///< Endpoint for this connection + + SSL_CTX *p_ssl_context_; ///< SSL Context instance + SSL *p_ssl_handle_; ///< SSL Handle + int server_tcp_socket_fd_; ///< Server Socket descriptor + + bool certificates_read_flag_; + + std::mutex clean_shutdown_action_lock_; + std::condition_variable shutdown_timeout_condition_; + + /** + * @brief Wait for socket FDs to become ready for read or write operations + * + * It is assumed that this function will be called only on SSL_ERROR_WANT_READ or SSL_ERROR_WANT_WRITE + * + * @param error_code - error generated by preceding socket operation + * @return int - return code of the select operation + */ + int WaitForSelect(int error_code); + + /** + * @brief Set TLS socket to non-blocking mode + * + * @return ResponseCode - successful operation or TLS error + */ + ResponseCode SetSocketToNonBlocking(); + + /** + * @brief Create a TCP socket and open the connection + * + * Creates an open socket connection + * + * @return ResponseCode - successful connection or TCP error + */ + ResponseCode ConnectTCPSocket(); + + /** + * @brief Attempt connection + * + * Attempts TLS Connection + * + * @return ResponseCode - successful connection or TLS error + */ + ResponseCode AttemptConnect(); + + /** + * @brief Create a TLS socket and open the connection + * + * Creates an open socket connection including TLS handshake. + * + * @return ResponseCode - successful connection or TLS error + */ + ResponseCode LoadCerts(); + + /** + * @brief Perform the connect process after creating the SSL handle and context + * + * Performs the steps necessary for connecting to an endpoint after the creation of the SSL instance + * + * @return ResponseCode - successful connection or TLS error + */ + ResponseCode PerformSSLConnect(); + + /** + * @brief Create a TLS socket and open the connection + * + * Creates an open socket connection including TLS handshake. + * + * @return ResponseCode - successful connection or TLS error + */ + ResponseCode ConnectInternal(); + + /** + * @brief Write bytes to the network socket + * + * @param util::String - const reference to buffer which should be written to socket + * @return size_t - number of bytes written or Network error + * @return ResponseCode - successful write or Network error code + */ + ResponseCode WriteInternal(const util::String &buf, size_t &size_written_bytes_out); + + /** + * @brief Read bytes from the network socket + * + * @param util::String - reference to buffer where read bytes should be copied + * @param size_t - number of bytes to read + * @param size_t - reference to store number of bytes read + * @return ResponseCode - successful read or TLS error code + */ + ResponseCode ReadInternal(util::Vector &buf, size_t buf_read_offset, + size_t size_bytes_to_read, size_t &size_read_bytes_out); + + /** + * @brief Disconnect from network socket + * + * @return ResponseCode - successful read or TLS error code + */ + ResponseCode DisconnectInternal(); + + public: + + OpenSSLConnection(util::String endpoint, uint16_t endpoint_port, + std::chrono::milliseconds tls_handshake_timeout, + std::chrono::milliseconds tls_read_timeout, + std::chrono::milliseconds tls_write_timeout, + bool server_verification_flag); + + /** + * @brief Constructor for the OpenSSL TLS implementation + * + * Performs any initialization required by the TLS layer. + * + * @param util::String endpoint - The target endpoint to connect to + * @param uint16_t endpoint_port - The port on the target to connect to + * @param util::String root_ca_location - Path of the location of the Root CA + * @param util::String device_cert_location - Path to the location of the Device Cert + * @param util::String device_private_key_location - Path to the location of the device private key file + * @param std::chrono::milliseconds tls_handshake_timeout - The value to use for timeout of handshake operation + * @param std::chrono::milliseconds tls_read_timeout - The value to use for timeout of read operation + * @param std::chrono::milliseconds tls_write_timeout - The value to use for timeout of write operation + * @param bool server_verification_flag - used to decide whether server verification is needed or not + * + */ + OpenSSLConnection(util::String endpoint, uint16_t endpoint_port, util::String root_ca_location, + util::String device_cert_location, util::String device_private_key_location, + std::chrono::milliseconds tls_handshake_timeout, + std::chrono::milliseconds tls_read_timeout, std::chrono::milliseconds tls_write_timeout, + bool server_verification_flag); + + OpenSSLConnection(util::String endpoint, uint16_t endpoint_port, util::String root_ca_location, + std::chrono::milliseconds tls_handshake_timeout, + std::chrono::milliseconds tls_read_timeout, std::chrono::milliseconds tls_write_timeout, + bool server_verification_flag); + + /** + * @brief Initialize the OpenSSL object + * + * Initializes the OpenSSL object + */ + ResponseCode Initialize(); + + /** + * @brief sets the path to the root CA + * + * Called to change the location of the root CA after the constructor has initialized the OpenSSL object. + * + * @param root_ca_location + */ + void SetRootCAPath(util::String root_ca_location) { + root_ca_location_ = root_ca_location; + certificates_read_flag_ = false; + } + + /** + * @brief sets the endpoint and the port + * + * Called to change the endpoint and the port after the constructor has initialized the OpenSSL object. + * @param endpoint + * @param endpoint_port + */ + void SetEndpointAndPort(util::String endpoint, uint16_t endpoint_port) { + endpoint_ = endpoint; + endpoint_port_ = endpoint_port; + } + + /** + * @brief Check if TLS layer is still connected + * + * Called to check if the TLS layer is still connected or not. + * + * @return bool - indicating status of network TLS layer connection + */ + bool IsConnected(); + + /** + * @brief Check if Network Physical layer is still connected + * + * Called to check if the Network Physical layer is still connected or not. + * + * @return bool - indicating status of network physical layer connection + */ + bool IsPhysicalLayerConnected(); + + virtual ~OpenSSLConnection(); + }; + } +} diff --git a/libguh-core/awsconnector.cpp b/libguh-core/awsconnector.cpp index e5d85f2f..dbe7eb2b 100644 --- a/libguh-core/awsconnector.cpp +++ b/libguh-core/awsconnector.cpp @@ -79,7 +79,7 @@ void AWSConnector::doConnect() { m_setupInProgress = true; m_subscriptionCache.clear(); - m_networkConnection = std::shared_ptr(new MbedTLSConnection( + m_networkConnection = std::shared_ptr(new OpenSSLConnection( m_currentEndpoint.toStdString(), 8883, m_caFile.toStdString(), @@ -90,6 +90,7 @@ void AWSConnector::doConnect() std::chrono::milliseconds(3000), true )); + m_networkConnection->Initialize(); m_client = MqttClient::Create(m_networkConnection, std::chrono::milliseconds(2800), &onDisconnectedCallback, m_disconnectContextData); m_client->SetAutoReconnectEnabled(false); diff --git a/libguh-core/awsconnector.h b/libguh-core/awsconnector.h index c9280827..ed95f23d 100644 --- a/libguh-core/awsconnector.h +++ b/libguh-core/awsconnector.h @@ -25,7 +25,7 @@ #include #include -#include "MbedTLS/MbedTLSConnection.hpp" +#include "OpenSSL/OpenSSLConnection.hpp" #include #include #include "util/logging/Logging.hpp" @@ -107,7 +107,7 @@ private: QString readSyncedNameCache(); private: - std::shared_ptr m_networkConnection; + std::shared_ptr m_networkConnection; std::shared_ptr m_client; QString m_currentEndpoint; QString m_caFile; diff --git a/libguh-core/libguh-core.pro b/libguh-core/libguh-core.pro index 7aa268e0..667594f3 100644 --- a/libguh-core/libguh-core.pro +++ b/libguh-core/libguh-core.pro @@ -10,10 +10,6 @@ LIBS += -L$$top_builddir/libguh/ -lguh -lssl -lcrypto -lavahi-common -lavahi-cli target.path = /usr/lib/$$system('dpkg-architecture -q DEB_HOST_MULTIARCH') INSTALLS += target -exists("/usr/include/mbedtls/net_sockets.h") { - DEFINES += MBEDTLS_NEW_HEADERS -} - # icons for the webserver RESOURCES += $$top_srcdir/icons.qrc \ $$top_srcdir/data/debug-interface/debug-interface.qrc @@ -76,7 +72,7 @@ HEADERS += guhcore.h \ awsconnector.h \ cloudmanager.h \ cloudnotifications.h \ - MbedTLS/MbedTLSConnection.hpp \ + OpenSSL/OpenSSLConnection.hpp \ janusconnector.h \ pushbuttondbusservice.h \ guhdbusservice.h \ @@ -155,7 +151,7 @@ SOURCES += guhcore.cpp \ awsconnector.cpp \ cloudmanager.cpp \ cloudnotifications.cpp \ - MbedTLS/MbedTLSConnection.cpp \ + OpenSSL/OpenSSLConnection.cpp \ janusconnector.cpp \ pushbuttondbusservice.cpp \ guhdbusservice.cpp \ diff --git a/server/server.pro b/server/server.pro index ae6b5472..90f777b7 100644 --- a/server/server.pro +++ b/server/server.pro @@ -10,7 +10,7 @@ INSTALLS += target QT *= sql xml websockets bluetooth dbus network -LIBS += -L$$top_builddir/libguh/ -lguh -L$$top_builddir/libguh-core -lguh-core -lssl -lcrypto -laws-iot-sdk-cpp -lmbedtls -lmbedx509 -lmbedcrypto +LIBS += -L$$top_builddir/libguh/ -lguh -L$$top_builddir/libguh-core -lguh-core -lssl -lcrypto -laws-iot-sdk-cpp # Server files include(qtservice/qtservice.pri) diff --git a/tests/auto/autotests.pri b/tests/auto/autotests.pri index 274266f1..261694c6 100644 --- a/tests/auto/autotests.pri +++ b/tests/auto/autotests.pri @@ -7,7 +7,7 @@ INCLUDEPATH += $$top_srcdir/libguh \ $$top_srcdir/tests/auto/ LIBS += -L$$top_builddir/libguh/ -lguh -L$$top_builddir/plugins/mock/ \ - -L$$top_builddir/libguh-core/ -lguh-core -lssl -lcrypto -laws-iot-sdk-cpp -lmbedtls -lmbedx509 -lmbedcrypto -lavahi-common -lavahi-client + -L$$top_builddir/libguh-core/ -lguh-core -lssl -lcrypto -laws-iot-sdk-cpp -lavahi-common -lavahi-client SOURCES += ../guhtestbase.cpp \