diff --git a/services/tunnelbroker/docker-server/contents/server/src/Constants.h b/services/tunnelbroker/docker-server/contents/server/src/Constants.h index 96ed7292b..b11441dd5 100644 --- a/services/tunnelbroker/docker-server/contents/server/src/Constants.h +++ b/services/tunnelbroker/docker-server/contents/server/src/Constants.h @@ -1,50 +1,52 @@ #pragma once #include #include #include namespace comm { namespace network { // AWS DynamoDB const std::string DEVICE_SESSIONS_TABLE_NAME = "tunnelbroker-device-session"; const std::string DEVICE_SESSIONS_VERIFICATION_MESSAGES_TABLE_NAME = "tunnelbroker-verification-message"; const std::string DEVICE_PUBLIC_KEY_TABLE_NAME = "tunnelbroker-public-key"; const std::string MESSAGES_TABLE_NAME = "tunnelbroker-message"; // Sessions const size_t SIGNATURE_REQUEST_LENGTH = 64; const size_t SESSION_ID_LENGTH = 64; const size_t SESSION_RECORD_TTL = 30 * 24 * 3600; // 30 days const size_t SESSION_SIGN_RECORD_TTL = 24 * 3600; // 24 hours +const std::regex SESSION_ID_FORMAT_REGEX( + "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"); // gRPC Server const std::string SERVER_LISTEN_ADDRESS = "0.0.0.0:50051"; // AMQP (RabbitMQ) const std::string AMQP_FANOUT_EXCHANGE_NAME = "allBrokers"; // message TTL const size_t AMQP_MESSAGE_TTL = 300 * 1000; // 5 min // queue TTL in case of no consumers (tunnelbroker is down) const size_t AMQP_QUEUE_TTL = 24 * 3600 * 1000; // 24 hours // routing message headers name const std::string AMQP_HEADER_FROM_DEVICEID = "fromDeviceid"; const std::string AMQP_HEADER_TO_DEVICEID = "toDeviceid"; const long long AMQP_SHORTEST_RECONNECTION_ATTEMPT_INTERVAL = 1000 * 60; // 1 min // DeviceID const size_t DEVICEID_CHAR_LENGTH = 64; const std::regex DEVICEID_FORMAT_REGEX( "^(ks|mobile|web):[a-zA-Z0-9]{" + std::to_string(DEVICEID_CHAR_LENGTH) + "}$"); // Config const std::string CONFIG_FILE_PATH = std::string(std::getenv("HOME")) + "/tunnelbroker/tunnelbroker.ini"; } // namespace network } // namespace comm diff --git a/services/tunnelbroker/docker-server/contents/server/src/Service/TunnelbrokerServiceImpl.cpp b/services/tunnelbroker/docker-server/contents/server/src/Service/TunnelbrokerServiceImpl.cpp index d41480770..50e7d808a 100644 --- a/services/tunnelbroker/docker-server/contents/server/src/Service/TunnelbrokerServiceImpl.cpp +++ b/services/tunnelbroker/docker-server/contents/server/src/Service/TunnelbrokerServiceImpl.cpp @@ -1,209 +1,223 @@ #include "TunnelbrokerServiceImpl.h" #include "AmqpManager.h" #include "AwsTools.h" #include "ConfigManager.h" #include "CryptoTools.h" #include "DatabaseManager.h" #include "DeliveryBroker.h" #include "Tools.h" namespace comm { namespace network { TunnelBrokerServiceImpl::TunnelBrokerServiceImpl() { Aws::InitAPI({}); // List of AWS DynamoDB tables to check if they are created and can be // accessed before any AWS API methods const std::list tablesList = { config::ConfigManager::getInstance().getParameter( config::ConfigManager::OPTION_DYNAMODB_SESSIONS_TABLE), config::ConfigManager::getInstance().getParameter( config::ConfigManager::OPTION_DYNAMODB_SESSIONS_VERIFICATION_TABLE), config::ConfigManager::getInstance().getParameter( config::ConfigManager::OPTION_DYNAMODB_SESSIONS_PUBLIC_KEY_TABLE)}; for (const std::string &table : tablesList) { if (!database::DatabaseManager::getInstance().isTableAvailable(table)) { throw std::runtime_error( "Error: AWS DynamoDB table '" + table + "' is not available"); } }; }; TunnelBrokerServiceImpl::~TunnelBrokerServiceImpl() { Aws::ShutdownAPI({}); }; grpc::Status TunnelBrokerServiceImpl::SessionSignature( grpc::ServerContext *context, const tunnelbroker::SessionSignatureRequest *request, tunnelbroker::SessionSignatureResponse *reply) { const std::string deviceID = request->deviceid(); if (!validateDeviceID(deviceID)) { std::cout << "gRPC: " << "Format validation failed for " << deviceID << std::endl; return grpc::Status( grpc::StatusCode::INVALID_ARGUMENT, "Format validation failed for deviceID"); } const std::string toSign = generateRandomString(SIGNATURE_REQUEST_LENGTH); std::shared_ptr SessionSignItem = std::make_shared(toSign, deviceID); database::DatabaseManager::getInstance().putSessionSignItem(*SessionSignItem); reply->set_tosign(toSign); return grpc::Status::OK; }; grpc::Status TunnelBrokerServiceImpl::NewSession( grpc::ServerContext *context, const tunnelbroker::NewSessionRequest *request, tunnelbroker::NewSessionResponse *reply) { std::shared_ptr deviceSessionItem; std::shared_ptr sessionSignItem; std::shared_ptr publicKeyItem; const std::string deviceID = request->deviceid(); if (!validateDeviceID(deviceID)) { std::cout << "gRPC: " << "Format validation failed for " << deviceID << std::endl; return grpc::Status( grpc::StatusCode::INVALID_ARGUMENT, "Format validation failed for deviceID"); } const std::string signature = request->signature(); const std::string publicKey = request->publickey(); const std::string newSessionID = generateUUID(); try { sessionSignItem = database::DatabaseManager::getInstance().findSessionSignItem(deviceID); if (sessionSignItem == nullptr) { std::cout << "gRPC: " << "Session sign request not found for deviceID: " << deviceID << std::endl; return grpc::Status( grpc::StatusCode::NOT_FOUND, "Session sign request not found"); } publicKeyItem = database::DatabaseManager::getInstance().findPublicKeyItem(deviceID); if (publicKeyItem == nullptr) { std::shared_ptr newPublicKeyItem = std::make_shared(deviceID, publicKey); database::DatabaseManager::getInstance().putPublicKeyItem( *newPublicKeyItem); } else if (publicKey != publicKeyItem->getPublicKey()) { std::cout << "gRPC: " << "The public key doesn't match for deviceID" << std::endl; return grpc::Status( grpc::StatusCode::PERMISSION_DENIED, "The public key doesn't match for deviceID"); } const std::string verificationMessage = sessionSignItem->getSign(); if (!comm::network::crypto::rsaVerifyString( publicKey, verificationMessage, signature)) { std::cout << "gRPC: " << "Signature for the verification message is not valid" << std::endl; return grpc::Status( grpc::StatusCode::PERMISSION_DENIED, "Signature for the verification message is not valid"); } database::DatabaseManager::getInstance().removeSessionSignItem(deviceID); deviceSessionItem = std::make_shared( newSessionID, deviceID, request->publickey(), request->notifytoken(), tunnelbroker::NewSessionRequest_DeviceTypes_Name(request->devicetype()), request->deviceappversion(), request->deviceos()); database::DatabaseManager::getInstance().putSessionItem(*deviceSessionItem); } catch (std::runtime_error &e) { std::cout << "gRPC: " << "Error while processing 'NewSession' request: " << e.what() << std::endl; return grpc::Status(grpc::StatusCode::INTERNAL, e.what()); } reply->set_sessionid(newSessionID); return grpc::Status::OK; }; grpc::Status TunnelBrokerServiceImpl::Send( grpc::ServerContext *context, const tunnelbroker::SendRequest *request, google::protobuf::Empty *reply) { try { const std::string sessionID = request->sessionid(); + if (!validateSessionID(sessionID)) { + std::cout << "gRPC: " + << "Format validation failed for " << sessionID << std::endl; + return grpc::Status( + grpc::StatusCode::INVALID_ARGUMENT, + "Format validation failed for sessionID"); + } std::shared_ptr sessionItem = database::DatabaseManager::getInstance().findSessionItem(sessionID); if (sessionItem == nullptr) { std::cout << "gRPC: " << "Session " << sessionID << " not found" << std::endl; return grpc::Status( grpc::StatusCode::PERMISSION_DENIED, "No such session found. SessionID: " + sessionID); } const std::string clientDeviceID = sessionItem->getDeviceID(); if (!AMQPSend( request->todeviceid(), clientDeviceID, std::string(request->payload()))) { std::cout << "gRPC: " << "Error while publish the message to AMQP" << std::endl; return grpc::Status( grpc::StatusCode::INTERNAL, "Error while publish the message to AMQP"); } } catch (std::runtime_error &e) { std::cout << "gRPC: " << "Error while processing 'Send' request: " << e.what() << std::endl; return grpc::Status(grpc::StatusCode::INTERNAL, e.what()); } return grpc::Status::OK; }; grpc::Status TunnelBrokerServiceImpl::Get( grpc::ServerContext *context, const tunnelbroker::GetRequest *request, grpc::ServerWriter *writer) { try { const std::string sessionID = request->sessionid(); + if (!validateSessionID(sessionID)) { + std::cout << "gRPC: " + << "Format validation failed for " << sessionID << std::endl; + return grpc::Status( + grpc::StatusCode::INVALID_ARGUMENT, + "Format validation failed for sessionID"); + } std::shared_ptr sessionItem = database::DatabaseManager::getInstance().findSessionItem(sessionID); if (sessionItem == nullptr) { std::cout << "gRPC: " << "Session " << sessionID << " not found" << std::endl; return grpc::Status( grpc::StatusCode::PERMISSION_DENIED, "No such session found. SessionID: " + sessionID); } const std::string clientDeviceID = sessionItem->getDeviceID(); std::vector messagesToDeliver; while (1) { messagesToDeliver = DeliveryBroker::getInstance().get(clientDeviceID); for (auto const &message : messagesToDeliver) { tunnelbroker::GetResponse response; response.set_fromdeviceid(message.fromDeviceID); response.set_payload(message.payload); if (!writer->Write(response)) { throw std::runtime_error( "gRPC: 'Get' writer error on sending data to the client"); } AMQPAck(message.deliveryTag); } if (!DeliveryBroker::getInstance().isEmpty(clientDeviceID)) { DeliveryBroker::getInstance().remove(clientDeviceID); } DeliveryBroker::getInstance().wait(clientDeviceID); } } catch (std::runtime_error &e) { std::cout << "gRPC: " << "Error while processing 'Get' request: " << e.what() << std::endl; return grpc::Status(grpc::StatusCode::INTERNAL, e.what()); } return grpc::Status::OK; }; } // namespace network } // namespace comm diff --git a/services/tunnelbroker/docker-server/contents/server/src/Tools/Tools.cpp b/services/tunnelbroker/docker-server/contents/server/src/Tools/Tools.cpp index 4d4bd36b0..3b608a89a 100644 --- a/services/tunnelbroker/docker-server/contents/server/src/Tools/Tools.cpp +++ b/services/tunnelbroker/docker-server/contents/server/src/Tools/Tools.cpp @@ -1,60 +1,71 @@ #include "Tools.h" #include "ConfigManager.h" #include "Constants.h" #include #include #include #include #include #include #include #include namespace comm { namespace network { std::string generateRandomString(std::size_t length) { const std::string CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; thread_local std::random_device generator; std::uniform_int_distribution<> distribution(0, CHARACTERS.size() - 1); std::string random_string; for (std::size_t i = 0; i < length; ++i) { random_string += CHARACTERS[distribution(generator)]; } return random_string; } long long getCurrentTimestamp() { using namespace std::chrono; return duration_cast(system_clock::now().time_since_epoch()) .count(); } bool validateDeviceID(std::string deviceID) { try { static const std::regex deviceIDKeyserverRegexp("^ks:.*"); if (std::regex_match(deviceID, deviceIDKeyserverRegexp)) { return ( deviceID == config::ConfigManager::getInstance().getParameter( config::ConfigManager::OPTION_DEFAULT_KEYSERVER_ID)); } return std::regex_match(deviceID, DEVICEID_FORMAT_REGEX); } catch (const std::exception &e) { std::cout << "Tools: " << "Got an exception at `validateDeviceID`: " << e.what() << std::endl; return false; } } std::string generateUUID() { thread_local boost::uuids::random_generator random_generator; return boost::uuids::to_string(random_generator()); } +bool validateSessionID(std::string sessionID) { + try { + return std::regex_match(sessionID, SESSION_ID_FORMAT_REGEX); + } catch (const std::exception &e) { + std::cout << "Tools: " + << "Got an exception at `validateSessionId`: " << e.what() + << std::endl; + return false; + } +} + } // namespace network } // namespace comm diff --git a/services/tunnelbroker/docker-server/contents/server/src/Tools/Tools.h b/services/tunnelbroker/docker-server/contents/server/src/Tools/Tools.h index af50dc3d7..9749a0639 100644 --- a/services/tunnelbroker/docker-server/contents/server/src/Tools/Tools.h +++ b/services/tunnelbroker/docker-server/contents/server/src/Tools/Tools.h @@ -1,15 +1,16 @@ #pragma once #include #include namespace comm { namespace network { std::string generateRandomString(std::size_t length); long long getCurrentTimestamp(); bool validateDeviceID(std::string deviceID); std::string generateUUID(); +bool validateSessionID(std::string sessionID); } // namespace network } // namespace comm