added cloud connection

This commit is contained in:
Michael Zanetti 2017-09-11 19:47:50 +02:00
parent 211cb3f637
commit 275e3b3921
18 changed files with 1022 additions and 25 deletions

3
debian/control vendored
View File

@ -23,7 +23,8 @@ Build-Depends: debhelper (>= 9.0.0),
libavahi-client-dev,
libavahi-common-dev,
libssl-dev,
libmbedtls-dev,
libaws-iot-device-sdk-cpp,
Package: guh
Architecture: any

View File

@ -0,0 +1,348 @@
/*
* 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 <iostream>
#include <util/memory/stl/Vector.hpp>
#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<uint32_t>(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<uint32_t>(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<unsigned char> &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();
}
}
}

View File

@ -0,0 +1,179 @@
/*
* 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 <atomic>
#include "mbedtls/config.h"
#include "mbedtls/platform.h"
#include "mbedtls/net_sockets.h"
#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<unsigned char> &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();
};
}
}

View File

@ -0,0 +1,6 @@
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

View File

@ -0,0 +1,166 @@
#include "awsconnector.h"
#include "loggingcategories.h"
#include <QDebug>
#include <QDateTime>
#include <QJsonDocument>
#include <QtConcurrent/QtConcurrentRun>
using namespace awsiotsdk;
using namespace awsiotsdk::network;
using namespace awsiotsdk::mqtt;
QHash<quint16, AWSConnector*> AWSConnector::s_requestMap;
AWSConnector::AWSConnector(QObject *parent) : QObject(parent)
{
connect(this, &AWSConnector::connected, this, &AWSConnector::onConnected);
}
AWSConnector::~AWSConnector()
{
}
void AWSConnector::connect2AWS(const QString &endpoint, const QString &clientId, const QString &caFile, const QString &clientCertFile, const QString &clientPrivKeyFile)
{
m_networkConnection = std::shared_ptr<MbedTLSConnection>(new MbedTLSConnection(
endpoint.toStdString(),
8883,
caFile.toStdString(),
clientCertFile.toStdString(),
clientPrivKeyFile.toStdString(),
std::chrono::milliseconds(30000),
std::chrono::milliseconds(30000),
std::chrono::milliseconds(30000),
true
));
m_client = MqttClient::Create(m_networkConnection, std::chrono::milliseconds(30000));
m_client->SetDisconnectCallbackPtr(&onDisconnected, std::shared_ptr<DisconnectCallbackContextData>(this));
m_client->SetAutoReconnectEnabled(true);
m_clientId = clientId;
m_connectingFuture = QtConcurrent::run([&]() {
ResponseCode rc = m_client->Connect(std::chrono::milliseconds(30000), true, mqtt::Version::MQTT_3_1_1, std::chrono::seconds(60), Utf8String::Create(m_clientId.toStdString()), nullptr, nullptr, nullptr);
if (rc == ResponseCode::MQTT_CONNACK_CONNECTION_ACCEPTED) {
qCDebug(dcCloud) << "Connected to AWS.";
emit connected();
} else {
qCWarning(dcCloud) << "Error connecting to AWS. Response code:" << QString::fromStdString(ResponseHelper::ToString(rc));
m_client.reset();
m_networkConnection.reset();
}
});
}
DisconnectCallbackContextData::~DisconnectCallbackContextData() {}
void AWSConnector::disconnectAWS()
{
if (isConnected()) {
m_client->Disconnect(std::chrono::seconds(2));
}
}
bool AWSConnector::isConnected() const
{
return m_connectingFuture.isFinished() && m_networkConnection && m_client && m_client->IsConnected();
}
quint16 AWSConnector::publish(const QString &topic, const QVariantMap &message)
{
if (!isConnected()) {
qCWarning(dcCloud()) << "Can't publish to AWS: Not connected.";
return -1;
}
QString fullTopic = QString("%1/%2").arg(m_clientId, topic);
QJsonDocument jsonDoc = QJsonDocument::fromVariant(message);
uint16_t packetId = 0;
ResponseCode res = m_client->PublishAsync(Utf8String::Create(fullTopic.toStdString()), false, false, mqtt::QoS::QOS1, jsonDoc.toJson().toStdString(), &publishCallback, packetId);
qCDebug(dcCloud()) << "publish call queued with status:" << QString::fromStdString(ResponseHelper::ToString(res)) << packetId;
s_requestMap.insert(packetId, this);
return packetId;
}
void AWSConnector::subscribe(const QStringList &topics)
{
m_subscribedTopics.append(topics);
if (!isConnected()) {
qCDebug(dcCloud()) << "Can't subscribe to AWS: Not connected. Subscription will happen upon next connection.";
return;
}
subscribeInternally(topics);
}
void AWSConnector::onConnected()
{
if (!m_subscribedTopics.isEmpty()) {
subscribeInternally(m_subscribedTopics);
}
}
void AWSConnector::subscribeInternally(const QStringList &topics)
{
util::Vector<std::shared_ptr<mqtt::Subscription>> subscription_list;
foreach (const QString &topic, topics) {
QString finalTopic = QString("%1/%2").arg(m_clientId, topic);
qCDebug(dcCloud()) << "topic to subscribe is" << finalTopic << "is valid topic:" << Subscription::IsValidTopicName(finalTopic.toStdString());
auto subscription = mqtt::Subscription::Create(Utf8String::Create(finalTopic.toStdString()), mqtt::QoS::QOS1, &onSubscriptionReceivedCallback, std::shared_ptr<SubscriptionHandlerContextData>(this));
subscription_list.push_back(subscription);
}
uint16_t packetId;
ResponseCode res = m_client->SubscribeAsync(subscription_list, subscribeCallback, packetId);
qCDebug(dcCloud()) << "subscribe call queued with status:" << QString::fromStdString(ResponseHelper::ToString(res)) << packetId;
s_requestMap.insert(packetId, this);
}
void AWSConnector::publishCallback(uint16_t actionId, ResponseCode rc)
{
AWSConnector* obj = s_requestMap.take(actionId);
if (!obj) {
qCWarning(dcCloud())<< "Received a response callback but don't have an object waiting for it.";
return;
}
switch (rc) {
case ResponseCode::SUCCESS:
emit obj->responseReceived(actionId, true);
qCDebug(dcCloud()) << "Successfully published" << actionId;
break;
default:
qCDebug(dcCloud())<< "Error publishing data to AWS:" << QString::fromStdString(ResponseHelper::ToString(rc));
emit obj->responseReceived(actionId, false);
}
}
void AWSConnector::subscribeCallback(uint16_t actionId, ResponseCode rc)
{
qCDebug(dcCloud()) << "subscribed to topic" << actionId << QString::fromStdString(ResponseHelper::ToString(rc));
}
ResponseCode AWSConnector::onSubscriptionReceivedCallback(util::String topic_name, util::String payload, std::shared_ptr<SubscriptionHandlerContextData> p_app_handler_data)
{
QJsonParseError error;
QJsonDocument jsonDoc = QJsonDocument::fromJson(QByteArray::fromStdString(payload), &error);
if (error.error != QJsonParseError::NoError) {
qCDebug(dcCloud()) << "Failed to parse JSON from AWS subscription on topic" << QString::fromStdString(topic_name) << ":" << error.errorString() << "\n" << QString::fromStdString(payload);
return ResponseCode::JSON_PARSING_ERROR;
}
AWSConnector *connector = dynamic_cast<AWSConnector*>(p_app_handler_data.get());
QString topic = QString::fromStdString(topic_name);
topic.remove(QRegExp("^" + connector->m_clientId + "/"));
emit connector->subscriptionReceived(topic, jsonDoc.toVariant().toMap());
return ResponseCode::SUCCESS;
}
ResponseCode AWSConnector::onDisconnected(util::String mqtt_client_id, std::shared_ptr<DisconnectCallbackContextData> p_app_handler_data)
{
Q_UNUSED(p_app_handler_data)
qCDebug(dcCloud()) << "disconnected" << QString::fromStdString(mqtt_client_id);
return ResponseCode::SUCCESS;
}

View File

@ -0,0 +1,54 @@
#ifndef AWSCONNECTOR_H
#define AWSCONNECTOR_H
#include <QObject>
#include <QFuture>
#include "MbedTLS/MbedTLSConnection.hpp"
#include <mqtt/Client.hpp>
#include <mqtt/Common.hpp>
class AWSConnector : public QObject, public awsiotsdk::mqtt::SubscriptionHandlerContextData, public awsiotsdk::DisconnectCallbackContextData
{
Q_OBJECT
public:
explicit AWSConnector(QObject *parent = 0);
~AWSConnector();
void connect2AWS(const QString &endpoint, const QString &clientId, const QString &caFile, const QString &clientCertFile, const QString &clientPrivKeyFile);
void disconnectAWS();
bool isConnected() const;
quint16 publish(const QString &topic, const QVariantMap &message);
void subscribe(const QStringList &topics);
signals:
void connected();
void responseReceived(quint16 id, bool success);
void subscriptionReceived(const QString &topic, const QVariantMap &data);
private slots:
void onConnected();
private:
void subscribeInternally(const QStringList &topcis);
static void publishCallback(uint16_t actionId, awsiotsdk::ResponseCode rc);
static void subscribeCallback(uint16_t actionId, awsiotsdk::ResponseCode rc);
static awsiotsdk::ResponseCode onSubscriptionReceivedCallback(awsiotsdk::util::String topic_name, awsiotsdk::util::String payload,
std::shared_ptr<SubscriptionHandlerContextData> p_app_handler_data);
static awsiotsdk::ResponseCode onDisconnected(awsiotsdk::util::String mqtt_client_id,
std::shared_ptr<DisconnectCallbackContextData> p_app_handler_data);
private:
std::shared_ptr<awsiotsdk::network::MbedTLSConnection> m_networkConnection;
std::shared_ptr<awsiotsdk::MqttClient> m_client;
QString m_clientId;
QFuture<void> m_connectingFuture;
QStringList m_subscribedTopics;
static QHash<quint16, AWSConnector*> s_requestMap;
};
#endif // AWSCONNECTOR_H

View File

@ -0,0 +1,123 @@
#include "cloudmanager.h"
#include "guhcore.h"
#include <QNetworkSession>
#include <QNetworkConfigurationManager>
CloudManager::CloudManager(QObject *parent) : QObject(parent)
{
m_awsConnector = new AWSConnector(this);
connect(m_awsConnector, &AWSConnector::subscriptionReceived, this, &CloudManager::subscriptionReceived);
// Extract the machine id so we have a unique identifier for this machine
// TODO: this only works for debian based systems, perhaps we should find something more general
QFile f("/etc/machine-id");
if (f.open(QFile::ReadOnly)) {
m_deviceId = QString::fromLatin1(f.readAll()).trimmed();
qCDebug(dcCloud()) << "Device ID is:" << m_deviceId;
setEnabled(true);
m_awsConnector->subscribe({"pair/response"});
} else {
qWarning(dcCloud()) << "Failed to open /etc/machine-id for reading. Cloud connection will not work.";
}
connect(GuhCore::instance()->networkManager(), &NetworkManager::stateChanged, this, &CloudManager::onlineStateChanged);
}
void CloudManager::setServerUrl(const QString &serverUrl)
{
m_serverUrl = serverUrl;
}
void CloudManager::setDeviceId(const QString &deviceId)
{
m_deviceId = deviceId;
}
void CloudManager::setClientCertificates(const QString &caCertificate, const QString &clientCertificate, const QString &clientCertificateKey)
{
m_caCertificate = caCertificate;
m_clientCertificate = clientCertificate;
m_clientCertificateKey = clientCertificateKey;
}
bool CloudManager::enabled() const
{
return m_enabled;
}
void CloudManager::setEnabled(bool enabled)
{
if (enabled) {
bool missingConfig = false;
if (m_deviceId.isEmpty()) {
qCWarning(dcCloud()) << "Don't have a unique device ID (/etc/machine-id).";
missingConfig = true;
}
if (GuhCore::instance()->configuration()->cloudServerUrl().isEmpty()) {
qCWarning(dcCloud()) << "Cloud server URL not set in configuration.";
missingConfig = true;
}
if (GuhCore::instance()->configuration()->cloudCertificate().isEmpty()) {
qCWarning(dcCloud()) << "Cloud certificate not set in configuration.";
missingConfig = true;
}
if (GuhCore::instance()->configuration()->cloudCertificateKey().isEmpty()) {
qCWarning(dcCloud()) << "Cloud certificate key not set in configuration.";
missingConfig = true;
}
if (GuhCore::instance()->configuration()->cloudCertificateCA().isEmpty()) {
qCWarning(dcCloud()) << "Cloud certificate CA not set in configuration.";
missingConfig = true;
}
if (missingConfig) {
qCWarning(dcCloud()) << "Cloud configuration incomplete. Not enabling cloud connection.";
return;
}
m_enabled = true;
if (!m_awsConnector->isConnected() && GuhCore::instance()->networkManager()->state() == NetworkManager::NetworkManagerStateConnectedGlobal) {
connect2aws();
}
}
}
int CloudManager::pairDevice(const QString &idToken, const QString &authToken, const QString &cognitoId)
{
QVariantMap map;
map.insert("id", ++m_id);
map.insert("idToken", idToken);
map.insert("authToken", authToken);
map.insert("cognitoId", cognitoId);
m_awsConnector->publish("pair", map);
return m_id;
}
void CloudManager::connect2aws()
{
m_awsConnector->connect2AWS(GuhCore::instance()->configuration()->cloudServerUrl(),
m_deviceId,
GuhCore::instance()->configuration()->cloudCertificateCA(),
GuhCore::instance()->configuration()->cloudCertificate(),
GuhCore::instance()->configuration()->cloudCertificateKey()
);
}
void CloudManager::onlineStateChanged()
{
qWarning() << "online state changed" << GuhCore::instance()->networkManager()->state();
if (GuhCore::instance()->networkManager()->state() == NetworkManager::NetworkManagerStateConnectedGlobal) {
if (m_enabled && !m_awsConnector->isConnected()) {
connect2aws();
}
}
}
void CloudManager::subscriptionReceived(const QString &topic, const QVariantMap &message)
{
qCDebug(dcCloud()) << "subscription reveiv ed" << topic << message;
if (topic == "pair/response") {
emit pairingReply(message.value("id").toInt(), message.value("status").toInt());
}
}

View File

@ -0,0 +1,49 @@
#ifndef CLOUDMANAGER_H
#define CLOUDMANAGER_H
#include "awsconnector.h"
#include <QObject>
#include <QTimer>
#include <QNetworkSession>
class CloudManager : public QObject
{
Q_OBJECT
public:
explicit CloudManager(QObject *parent = nullptr);
void setServerUrl(const QString &serverUrl);
void setDeviceId(const QString &deviceId);
void setClientCertificates(const QString &caCertificate, const QString &clientCertificate, const QString &clientCertificateKey);
bool enabled() const;
void setEnabled(bool enabled);
int pairDevice(const QString &idToken, const QString &authToken, const QString &cognitoId);
signals:
void pairingReply(int pairingTransactionId, int status);
private:
void connect2aws();
private slots:
void onlineStateChanged();
void subscriptionReceived(const QString &topic, const QVariantMap &message);
private:
QNetworkSession *m_networkSession;
QTimer m_reconnectTimer;
bool m_enabled = false;
AWSConnector *m_awsConnector = nullptr;
int m_id = 0; // id for transactions. e.g. pairDevice
QString m_serverUrl;
QString m_deviceId;
QString m_caCertificate;
QString m_clientCertificate;
QString m_clientCertificateKey;
};
#endif // CLOUDMANAGER_H

View File

@ -305,30 +305,46 @@ void GuhConfiguration::setBluetoothServerEnabled(const bool &enabled)
emit bluetoothServerEnabled();
}
QString GuhConfiguration::cloudServerUrl() const
{
GuhSettings settings(GuhSettings::SettingsRoleGlobal);
settings.beginGroup("Cloud");
return settings.value("cloudServerUrl").toString();
}
QString GuhConfiguration::cloudCertificateCA() const
{
GuhSettings settings(GuhSettings::SettingsRoleGlobal);
settings.beginGroup("Cloud");
return settings.value("cloudCertificateCA").toString();
}
QString GuhConfiguration::cloudCertificate() const
{
GuhSettings settings(GuhSettings::SettingsRoleGlobal);
settings.beginGroup("Cloud");
return settings.value("cloudCertificate").toString();
}
QString GuhConfiguration::cloudCertificateKey() const
{
GuhSettings settings(GuhSettings::SettingsRoleGlobal);
settings.beginGroup("Cloud");
return settings.value("cloudCertificateKey").toString();
}
QString GuhConfiguration::sslCertificate() const
{
GuhSettings settings(GuhSettings::SettingsRoleGlobal);
settings.beginGroup("SSL");
return settings.value("certificate", "/etc/ssl/certs/guhd-certificate.crt").toString();
return settings.value("certificate").toString();
}
QString GuhConfiguration::sslCertificateKey() const
{
GuhSettings settings(GuhSettings::SettingsRoleGlobal);
settings.beginGroup("SSL");
return settings.value("certificate-key", "/etc/ssl/certs/guhd-certificate.key").toString();
}
void GuhConfiguration::setSslCertificate(const QString &sslCertificate, const QString &sslCertificateKey)
{
qCDebug(dcApplication()) << "Configuration: SSL certificate:" << sslCertificate << "SSL certificate key:" << sslCertificateKey;
GuhSettings settings(GuhSettings::SettingsRoleGlobal);
settings.beginGroup("SSL");
settings.setValue("certificate", sslCertificate);
settings.setValue("certificate-key", sslCertificateKey);
settings.endGroup();
emit sslCertificateChanged();
return settings.value("certificate-key").toString();
}
void GuhConfiguration::setServerUuid(const QUuid &uuid)

View File

@ -86,6 +86,9 @@ public:
QLocale locale() const;
void setLocale(const QLocale &locale);
QString sslCertificate() const;
QString sslCertificateKey() const;
// TCP server
QHash<QString, ServerConfiguration> tcpServerConfigurations() const;
void setTcpServerConfiguration(const ServerConfiguration &config);
@ -105,9 +108,11 @@ public:
bool bluetoothServerEnabled() const;
void setBluetoothServerEnabled(const bool &enabled);
QString sslCertificate() const;
QString sslCertificateKey() const;
void setSslCertificate(const QString &sslCertificate, const QString &sslCertificateKey);
// Cloud
QString cloudServerUrl() const;
QString cloudCertificateCA() const;
QString cloudCertificate() const;
QString cloudCertificateKey() const;
private:
QHash<QString, ServerConfiguration> m_tcpServerConfigs;
@ -138,8 +143,6 @@ signals:
void webSocketServerConfigurationRemoved(const QString &configId);
void bluetoothServerEnabledChanged();
void sslCertificateChanged();
};
}

View File

@ -408,6 +408,12 @@ UserManager *GuhCore::userManager() const
return m_userManager;
}
CloudManager *GuhCore::cloudManager() const
{
return m_cloudManager;
}
/*! Constructs GuhCore with the given \a parent. This is private.
Use \l{GuhCore::instance()} to access the single instance.*/
GuhCore::GuhCore(QObject *parent) :
@ -440,6 +446,8 @@ void GuhCore::init() {
m_userManager = new UserManager(this);
m_cloudManager = new CloudManager(this);
connect(m_configuration, &GuhConfiguration::localeChanged, this, &GuhCore::onLocaleChanged);
connect(m_deviceManager, &DeviceManager::pluginConfigChanged, this, &GuhCore::pluginConfigChanged);
@ -464,6 +472,7 @@ void GuhCore::init() {
connect(m_timeManager, &TimeManager::tick, m_deviceManager, &DeviceManager::timeTick);
m_logger->logSystemEvent(m_timeManager->currentDateTime(), true);
emit initialized();
// Evaluate rules on current time

View File

@ -33,6 +33,7 @@
#include "devicemanager.h"
#include "ruleengine.h"
#include "servermanager.h"
#include "cloudmanager.h"
#include "time/timemanager.h"
@ -77,6 +78,7 @@ public:
BluetoothServer *bluetoothServer() const;
NetworkManager *networkManager() const;
UserManager *userManager() const;
CloudManager *cloudManager() const;
static QStringList getAvailableLanguages();
@ -110,6 +112,7 @@ private:
RuleEngine *m_ruleEngine;
LogEngine *m_logger;
TimeManager *m_timeManager;
CloudManager *m_cloudManager;
NetworkManager *m_networkManager;
UserManager *m_userManager;

View File

@ -144,7 +144,7 @@ JsonRPCServer::JsonRPCServer(const QSslConfiguration &sslConfiguration, QObject
setDescription("SetupRemoteAccess", "Setup the remote connection by providing AWS token information");
params.insert("idToken", JsonTypes::basicTypeToString(JsonTypes::String));
params.insert("authToken", JsonTypes::basicTypeToString(JsonTypes::String));
params.insert("cognitoIdentityPoolIdentityId", JsonTypes::basicTypeToString(JsonTypes::String));
params.insert("cognitoId", JsonTypes::basicTypeToString(JsonTypes::String));
setParams("SetupRemoteAccess", params);
returns.insert("status", JsonTypes::basicTypeToString(JsonTypes::Int));
setReturns("SetupRemoteAccess", returns);
@ -264,6 +264,14 @@ JsonReply *JsonRPCServer::RemoveToken(const QVariantMap &params)
JsonReply *JsonRPCServer::SetupRemoteAccess(const QVariantMap &params)
{
int id = GuhCore::instance()->cloudManager()->pairDevice(params.value("idToken").toString(), params.value("authToken").toString(), params.value("cognitoId").toString());
qWarning() << "waiting for id" << id;
JsonReply *reply = createAsyncReply("SetupRemoteAccess");
m_pairingRequests.insert(id, reply);
connect(reply, &JsonReply::finished, [this, id](){
m_pairingRequests.remove(id);
});
return reply;
}
/*! Returns the list of registred \l{JsonHandler}{JsonHandlers} and their name.*/
@ -353,6 +361,8 @@ void JsonRPCServer::setup()
registerHandler(new StateHandler(this));
registerHandler(new ConfigurationHandler(this));
registerHandler(new NetworkManagerHandler(this));
connect(GuhCore::instance()->cloudManager(), &CloudManager::pairingReply, this, &JsonRPCServer::pairingFinished);
}
void JsonRPCServer::processData(const QUuid &clientId, const QByteArray &data)
@ -486,6 +496,20 @@ void JsonRPCServer::asyncReplyFinished()
reply->deleteLater();
}
void JsonRPCServer::pairingFinished(int pairingTransactionId, int status)
{
qWarning() << "pairing finished" << pairingTransactionId << status;
JsonReply *reply = m_pairingRequests.take(pairingTransactionId);
if (!reply) {
return;
}
qWarning() << "blubbbbb";
QVariantMap returns;
returns.insert("status", status);
reply->setData(returns);
reply->finished();
}
void JsonRPCServer::registerHandler(JsonHandler *handler)
{
m_handlers.insert(handler->name(), handler);

View File

@ -81,6 +81,8 @@ private slots:
void asyncReplyFinished();
void pairingFinished(int pairingTransactionId, int status);
private:
QMap<TransportInterface*, bool> m_interfaces;
QHash<QString, JsonHandler *> m_handlers;
@ -89,6 +91,8 @@ private:
// clientId, notificationsEnabled
QHash<QUuid, bool> m_clients;
QHash<int, JsonReply*> m_pairingRequests;
int m_notificationId;
void registerHandler(JsonHandler *handler);

View File

@ -67,6 +67,9 @@ HEADERS += $$top_srcdir/libguh-core/guhcore.h \
$$top_srcdir/libguh-core/tokeninfo.h \
$$top_srcdir/libguh-core/certificategenerator.h \
$$top_srcdir/libguh-core/logging/logvaluetool.h
$$top_srcdir/libguh-core/awsconnector.h \
$$top_srcdir/libguh-core/cloudconnector.h \
$$top_srcdir/libguh-core/MbedTLS/MbedTLSConnection.hpp \
SOURCES += $$top_srcdir/libguh-core/guhcore.cpp \
@ -121,4 +124,6 @@ SOURCES += $$top_srcdir/libguh-core/guhcore.cpp \
$$top_srcdir/libguh-core/tokeninfo.cpp \
$$top_srcdir/libguh-core/certificategenerator.cpp \
$$top_srcdir/libguh-core/logging/logvaluetool.cpp
$$top_srcdir/libguh-core/awsconnector.cpp \
$$top_srcdir/libguh-core/cloudconnector.cpp \
$$top_srcdir/libguh-core/MbedTLS/MbedTLSConnection.cpp \

View File

@ -8,9 +8,9 @@ INCLUDEPATH += ../libguh-core ../libguh-core/jsonrpc ../libguh
target.path = /usr/bin
INSTALLS += target
QT *= sql xml websockets bluetooth dbus
QT *= sql xml websockets bluetooth dbus network
LIBS += -L$$top_builddir/libguh/ -lguh -L$$top_builddir/libguh-core -lguh-core
LIBS += -L$$top_builddir/libguh/ -lguh -L$$top_builddir/libguh-core -lguh-core -lssl -lcrypto -laws-iot-sdk-cpp -lmbedtls -lmbedx509 -lmbedcrypto
# Translations
TRANSLATIONS *= $$top_srcdir/translations/guhd-en_US.ts \

View File

@ -13,7 +13,7 @@ INCLUDEPATH += $$top_srcdir/server/ \
$$top_srcdir/libguh \
$$top_srcdir/tests/auto/
LIBS += -L$$top_builddir/libguh/ -lguh -lssl -lcrypto
LIBS += -L$$top_builddir/libguh/ -lguh -lssl -lcrypto -laws-iot-sdk-cpp -lmbedtls -lmbedx509 -lmbedcrypto
target.path = /usr/lib/$$system('dpkg-architecture -q DEB_HOST_MULTIARCH')
INSTALLS += target

View File

@ -0,0 +1,7 @@
#!/bin/bash
if [ -z $4 ]; then
echo "usage: $0 host idToken authToken cognitoId"
else
(echo '{"id":1, "method": "JSONRPC.SetupRemoteAccess", "params": { "idToken": "'$2'", "authToken": "'$3'", "cognitoId": "'$4'"}}'; sleep 1) | nc $1 2222
fi