diff --git a/configs/fne-config.example.yml b/configs/fne-config.example.yml index 03619d0a..69512010 100644 --- a/configs/fne-config.example.yml +++ b/configs/fne-config.example.yml @@ -229,6 +229,8 @@ master: file: key_container.ekc # Container password. password: "PASSWORD" + # Remote access password for the crypto container. + remoteAccessPassword: "PASSWORD" # Amount of time between updates of crypto container file. (minutes) time: 30 diff --git a/src/common/network/RTPFNEHeader.h b/src/common/network/RTPFNEHeader.h index fafa9788..b1a6d4b5 100644 --- a/src/common/network/RTPFNEHeader.h +++ b/src/common/network/RTPFNEHeader.h @@ -68,6 +68,9 @@ namespace network ACK = 0x7EU, //!< Packet Acknowledge NAK = 0x7FU, //!< Packet Negative Acknowledge + KEYS_INVENTORY = 0x8EU, //!< Encryption Key Container Inventory + KEYS_UPDATE = 0x8FU, //!< Encryption Key Container Update + TRANSFER = 0x90U, //!< Network Transfer Function ANNOUNCE = 0x91U, //!< Network Announce Function diff --git a/src/fne/CryptoContainer.cpp b/src/fne/CryptoContainer.cpp index 2667fca7..601ecd51 100644 --- a/src/fne/CryptoContainer.cpp +++ b/src/fne/CryptoContainer.cpp @@ -141,9 +141,11 @@ std::mutex CryptoContainer::s_mutex; /* Initializes a new instance of the CryptoContainer class. */ -CryptoContainer::CryptoContainer(const std::string& filename, const std::string& password, uint32_t reloadTime, bool enabled) : Thread(), +CryptoContainer::CryptoContainer(const std::string& filename, const std::string& password, const std::string& remotePassword, + uint32_t reloadTime, bool enabled) : Thread(), m_file(filename), m_password(password), + m_remotePassword(remotePassword), m_reloadTime(reloadTime), m_lastLoadTime(0U), #if !defined(ENABLE_SSL) diff --git a/src/fne/CryptoContainer.h b/src/fne/CryptoContainer.h index b2242efe..4a50e284 100644 --- a/src/fne/CryptoContainer.h +++ b/src/fne/CryptoContainer.h @@ -167,10 +167,11 @@ public: * @brief Initializes a new instance of the CryptoContainer class. * @param filename Full-path to the crypto container file. * @param password Crypto container file access password. + * @param remotePassword Remote access password for the crypto container. * @param reloadTime Interval of time to reload the crypto container. * @param enabled Flag indicating if crypto container is enabled. */ - CryptoContainer(const std::string& filename, const std::string& password, uint32_t reloadTime, bool enabled); + CryptoContainer(const std::string& filename, const std::string& password, const std::string& remotePassword, uint32_t reloadTime, bool enabled); /** * @brief Finalizes a instance of the CryptoContainer class. */ @@ -251,9 +252,21 @@ public: */ const uint64_t lastLoadTime() const { return m_lastLoadTime; } + /** + * @brief Returns the filename of this lookup table. + * @return const std::string& Filename of this lookup table. + */ + const std::string& filename() const { return m_file; } + /** + * @brief Returns the remote access password for the crypto container. + * @return const std::string& Remote access password. + */ + const std::string& getRemotePassword() const { return m_remotePassword; } + private: std::string m_file; std::string m_password; + std::string m_remotePassword; uint32_t m_reloadTime; uint64_t m_lastLoadTime; diff --git a/src/fne/HostFNE.cpp b/src/fne/HostFNE.cpp index 0c6954d6..245ec89d 100644 --- a/src/fne/HostFNE.cpp +++ b/src/fne/HostFNE.cpp @@ -415,6 +415,7 @@ bool HostFNE::readParams() #endif // ENABLE_SSL std::string cryptoContainerEKC = cryptoContainer["file"].as(); std::string cryptoContainerPassword = cryptoContainer["password"].as(); + std::string cryptoContainerRemotePassword = cryptoContainer["remoteAccessPassword"].as(); uint32_t cryptoContainerReload = cryptoContainer["time"].as(30U); std::string peerListLookupFile = systemConf["peer_acl"]["file"].as(); @@ -458,7 +459,7 @@ bool HostFNE::readParams() if (cryptoContainerReload > 0U) LogInfo(" Reload: %u mins", cryptoContainerReload); - m_cryptoLookup = new CryptoContainer(cryptoContainerEKC, cryptoContainerPassword, cryptoContainerReload, cryptoContainerEnabled); + m_cryptoLookup = new CryptoContainer(cryptoContainerEKC, cryptoContainerPassword, cryptoContainerRemotePassword, cryptoContainerReload, cryptoContainerEnabled); m_cryptoLookup->read(); return true; diff --git a/src/fne/network/MetadataNetwork.cpp b/src/fne/network/MetadataNetwork.cpp index 48235fc2..6f12bdd0 100644 --- a/src/fne/network/MetadataNetwork.cpp +++ b/src/fne/network/MetadataNetwork.cpp @@ -8,6 +8,7 @@ * */ #include "fne/Defines.h" +#include "common/edac/SHA256.h" #include "common/zlib/Compression.h" #include "common/Log.h" #include "common/Utils.h" @@ -710,6 +711,412 @@ void MetadataNetwork::taskNetworkRx(NetPacketRequest* req) } break; + case NET_FUNC::KEYS_INVENTORY: // Encryption Key Container Inventory + { + lookups::PeerId peerEntry = network->m_peerListLookup->find(peerId); + if (peerEntry.peerDefault()) { + LogError(LOG_MASTER, "PEER %u requested enc. key inventory but is not allowed, no response", peerId); + break; + } else { + if (!peerEntry.canRequestKeys()) { + LogError(LOG_MASTER, "PEER %u requested enc. key inventory but is not allowed, no response", peerId); + break; + } + } + + // keys inventory operates differently from the rest of the network opcodes...and does not require + // an established connection to the master, so we will not validate the peer connection state here + if (peerId > 0 && !peerEntry.peerDefault()) { + if (req->length < 80) { + LogError(LOG_MASTER, "PEER %u requested enc. key inventory, but payload length was invalid (%u bytes), no response", peerId, req->length); + break; + } + + // scope intentional + { + // get the peer password hash from the frame message + DECLARE_UINT8_ARRAY(peerHash, 32U); + ::memcpy(peerHash, req->buffer + 8U, 32U); + + uint8_t peerSalt[4U]; + ::memset(peerSalt, 0x00U, 4U); + ::memcpy(peerSalt, req->buffer + 40U, 4U); + + std::string passwordForPeer = network->m_password; + + // check if the peer is in the peer ACL list + bool validAcl = true; + if (network->m_peerListLookup->getACL()) { + if (!network->m_peerListLookup->isPeerAllowed(peerId) && !network->m_peerListLookup->isPeerListEmpty()) { + LogWarning(LOG_MASTER, "PEER %u RPTK, failed peer ACL check", peerId); + validAcl = false; + } else { + lookups::PeerId peerEntry = network->m_peerListLookup->find(peerId); + if (peerEntry.peerDefault()) { + validAcl = false; // default peer IDs are a no-no as they have no data thus fail ACL check + } else { + passwordForPeer = peerEntry.peerPassword(); + if (passwordForPeer.length() == 0) { + passwordForPeer = network->m_password; + } + } + } + + if (network->m_peerListLookup->isPeerListEmpty()) { + LogWarning(LOG_MASTER, "Peer List ACL enabled, but we have an empty peer list? Passing all peers."); + validAcl = true; + } + } + + if (validAcl) { + size_t size = passwordForPeer.size(); + uint8_t* in = new uint8_t[size + sizeof(uint32_t)]; + ::memcpy(in, peerSalt, sizeof(uint32_t)); + for (size_t i = 0U; i < size; i++) + in[i + sizeof(uint32_t)] = passwordForPeer.at(i); + + uint8_t out[32U]; + edac::SHA256 sha256; + sha256.buffer(in, (uint32_t)(size + sizeof(uint32_t)), out); + + delete[] in; + + // validate hash + bool validHash = false; + if (req->length >= 80) { + validHash = true; + for (uint8_t i = 0; i < 32U; i++) { + if (peerHash[i] != out[i]) { + validHash = false; + break; + } + } + } + + if (!validHash) { + LogError(LOG_MASTER, "PEER %u requested enc. key inventory, but had invalid authentication, no response", peerId); + break; + } + } else { + LogError(LOG_MASTER, "PEER %u requested enc. key inventory, but had invalid ACL, no response", peerId); + break; + } + } + + // scope intentional + { + // get remote access password hash from the frame message + DECLARE_UINT8_ARRAY(remoteAccessHash, 32U); + ::memcpy(remoteAccessHash, req->buffer + 44U, 32U); + + uint8_t remoteSalt[4U]; + ::memset(remoteSalt, 0x00U, 4U); + ::memcpy(remoteSalt, req->buffer + 76U, 4U); + + std::string remoteAccessPassword = network->m_host->m_cryptoLookup->getRemotePassword(); + + size_t size = remoteAccessPassword.size(); + uint8_t* in = new uint8_t[size + sizeof(uint32_t)]; + ::memcpy(in, remoteSalt, sizeof(uint32_t)); + for (size_t i = 0U; i < size; i++) + in[i + sizeof(uint32_t)] = remoteAccessPassword.at(i); + + uint8_t out[32U]; + edac::SHA256 sha256; + sha256.buffer(in, (uint32_t)(size + sizeof(uint32_t)), out); + + delete[] in; + + // validate hash + bool validHash = false; + if (req->length >= 80) { + validHash = true; + for (uint8_t i = 0; i < 32U; i++) { + if (remoteAccessHash[i] != out[i]) { + validHash = false; + break; + } + } + } + + if (!validHash) { + LogError(LOG_MASTER, "PEER %u requested enc. key inventory, but had invalid access authentication, no response", peerId); + break; + } + } + + // scope intentional + { + // read entire file into buffer + std::stringstream b; + std::ifstream stream(network->m_host->m_cryptoLookup->filename(), std::ios::in | std::ios::binary); + + uint32_t len = 0U; + UInt8Array bufferUInt8Array = nullptr; + uint8_t* buffer = nullptr; + + if (stream.is_open()) { + stream.seekg(0, std::ios::end); + len = (uint32_t)stream.tellg(); + stream.seekg(0, std::ios::beg); + + bufferUInt8Array = std::make_unique(len); + buffer = bufferUInt8Array.get(); + ::memset(buffer, 0x00U, len); + + uint32_t i = 0U; + while (stream.peek() != EOF) { + buffer[i] = (uint8_t)stream.get(); + i++; + } + + stream.close(); + } + + PacketBuffer pkt(true, "Remote EKC, Key Inventory"); + pkt.encode((uint8_t*)buffer, len); + + LogInfoEx(LOG_REPL, "PEER %u Remote EKC, Key Inventory, blocks %u, streamId = %u", peerId, pkt.fragments.size(), streamId); + if (pkt.fragments.size() > 0U) { + for (auto frag : pkt.fragments) { + network->writePeer(peerId, network->m_peerId, { NET_FUNC::KEYS_INVENTORY, NET_SUBFUNC::NOP }, + frag.second->data, FRAG_SIZE, 0U, streamId); + Thread::sleep(60U); // pace block transmission + } + } + + pkt.clear(); + } + } + } + break; + + case NET_FUNC::KEYS_UPDATE: // Encryption Key Container Update + { + lookups::PeerId peerEntry = network->m_peerListLookup->find(peerId); + if (peerEntry.peerDefault()) { + LogError(LOG_MASTER, "PEER %u requested enc. key update but is not allowed, no response", peerId); + break; + } else { + if (!peerEntry.canRequestKeys()) { + LogError(LOG_MASTER, "PEER %u requested enc. key update but is not allowed, no response", peerId); + break; + } + } + + // keys inventory operates differently from the rest of the network opcodes...and does not require + // an established connection to the master, so we will not validate the peer connection state here + if (peerId > 0 && !peerEntry.peerDefault()) { + // scope intentional + { + // get the peer password hash from the frame message + DECLARE_UINT8_ARRAY(peerHash, 32U); + ::memcpy(peerHash, req->buffer + 8U, 32U); + + uint8_t peerSalt[4U]; + ::memset(peerSalt, 0x00U, 4U); + ::memcpy(peerSalt, req->buffer + 40U, 4U); + + std::string passwordForPeer = network->m_password; + + // check if the peer is in the peer ACL list + bool validAcl = true; + if (network->m_peerListLookup->getACL()) { + if (!network->m_peerListLookup->isPeerAllowed(peerId) && !network->m_peerListLookup->isPeerListEmpty()) { + LogWarning(LOG_MASTER, "PEER %u RPTK, failed peer ACL check", peerId); + validAcl = false; + } else { + lookups::PeerId peerEntry = network->m_peerListLookup->find(peerId); + if (peerEntry.peerDefault()) { + validAcl = false; // default peer IDs are a no-no as they have no data thus fail ACL check + } else { + passwordForPeer = peerEntry.peerPassword(); + if (passwordForPeer.length() == 0) { + passwordForPeer = network->m_password; + } + } + } + + if (network->m_peerListLookup->isPeerListEmpty()) { + LogWarning(LOG_MASTER, "Peer List ACL enabled, but we have an empty peer list? Passing all peers."); + validAcl = true; + } + } + + if (validAcl) { + size_t size = passwordForPeer.size(); + uint8_t* in = new uint8_t[size + sizeof(uint32_t)]; + ::memcpy(in, peerSalt, sizeof(uint32_t)); + for (size_t i = 0U; i < size; i++) + in[i + sizeof(uint32_t)] = passwordForPeer.at(i); + + uint8_t out[32U]; + edac::SHA256 sha256; + sha256.buffer(in, (uint32_t)(size + sizeof(uint32_t)), out); + + delete[] in; + + // validate hash + bool validHash = false; + if (req->length - 8U == 32U) { + validHash = true; + for (uint8_t i = 0; i < 32U; i++) { + if (peerHash[i] != out[i]) { + validHash = false; + break; + } + } + } + + if (!validHash) { + LogError(LOG_MASTER, "PEER %u requested enc. key update, but had invalid authentication, no response", peerId); + break; + } + } else { + LogError(LOG_MASTER, "PEER %u requested enc. key update, but had invalid ACL, no response", peerId); + break; + } + } + + // scope intentional + { + // get remote access password hash from the frame message + DECLARE_UINT8_ARRAY(remoteAccessHash, 32U); + ::memcpy(remoteAccessHash, req->buffer + 44U, 32U); + + uint8_t remoteSalt[4U]; + ::memset(remoteSalt, 0x00U, 4U); + ::memcpy(remoteSalt, req->buffer + 76U, 4U); + + std::string remoteAccessPassword = network->m_host->m_cryptoLookup->getRemotePassword(); + + size_t size = remoteAccessPassword.size(); + uint8_t* in = new uint8_t[size + sizeof(uint32_t)]; + ::memcpy(in, remoteSalt, sizeof(uint32_t)); + for (size_t i = 0U; i < size; i++) + in[i + sizeof(uint32_t)] = remoteAccessPassword.at(i); + + uint8_t out[32U]; + edac::SHA256 sha256; + sha256.buffer(in, (uint32_t)(size + sizeof(uint32_t)), out); + + delete[] in; + + // validate hash + bool validHash = false; + if (req->length - 8U == 32U) { + validHash = true; + for (uint8_t i = 0; i < 32U; i++) { + if (remoteAccessHash[i] != out[i]) { + validHash = false; + break; + } + } + } + + if (!validHash) { + LogError(LOG_MASTER, "PEER %u requested enc. key update, but had invalid access authentication, no response", peerId); + break; + } + } + + // scope intentional + { + DECLARE_UINT8_ARRAY(rawPayload, req->length); + ::memcpy(rawPayload, req->buffer, req->length); + + // Utils::dump(1U, "MetadataNetwork::taskNetworkRx(), KEYS_UPDATE, Raw Payload", rawPayload, req->length); + + if (mdNetwork->m_peerKeyUpdatePkt.find(peerId) == mdNetwork->m_peerKeyUpdatePkt.end()) { + mdNetwork->m_peerKeyUpdatePkt.insert(peerId, MetadataNetwork::PacketBufferEntry()); + + MetadataNetwork::PacketBufferEntry& pkt = mdNetwork->m_peerKeyUpdatePkt[peerId]; + pkt.buffer = new PacketBuffer(true, "Remote EKC, Key Update"); + pkt.streamId = streamId; + + pkt.locked = false; + } else { + MetadataNetwork::PacketBufferEntry& pkt = mdNetwork->m_peerKeyUpdatePkt[peerId]; + if (!pkt.locked && pkt.streamId != streamId) { + LogError(LOG_REPL, "PEER %u Remote EKC, Key Update, stream ID mismatch, expected %u, got %u", peerId, pkt.streamId, streamId); + pkt.buffer->clear(); + pkt.streamId = streamId; + } + + if (pkt.streamId != streamId) { + // otherwise drop the packet + break; + } + } + + MetadataNetwork::PacketBufferEntry& pkt = mdNetwork->m_peerKeyUpdatePkt[peerId]; + if (pkt.locked) { + while (pkt.locked && pkt.timeout < TIMEOUT_MAX_REPL) { + pkt.timeout++; + Thread::sleep(1U); + } + + if (pkt.timeout >= TIMEOUT_MAX_REPL) { + LogError(LOG_STP, "PEER %u Remote EKC, Key Update, timeout waiting for packet buffer to unlock", peerId); + pkt.buffer->clear(); + pkt.streamId = 0U; + mdNetwork->m_peerKeyUpdatePkt.erase(peerId); + break; + } + } + + pkt.locked = true; + pkt.timeout = 0U; + + uint32_t decompressedLen = 0U; + uint8_t* decompressed = nullptr; + + if (pkt.buffer->decode(rawPayload, &decompressed, &decompressedLen)) { + mdNetwork->m_peerKeyUpdatePkt.lock(); + // randomize filename + std::ostringstream s; + s << network->m_cryptoLookup->filename(); + + std::string filename = s.str(); + std::ofstream file(filename, std::ofstream::out); + if (file.fail()) { + LogError(LOG_PEER, "Cannot open the crypto container file - %s", filename.c_str()); + pkt.buffer->clear(); + delete pkt.buffer; + pkt.streamId = 0U; + if (decompressed != nullptr) { + delete[] decompressed; + } + mdNetwork->m_peerKeyUpdatePkt.unlock(); + mdNetwork->m_peerKeyUpdatePkt.erase(peerId); + break; + } + + for (uint32_t i = 0U; i < decompressedLen; i++) { + file << (char)decompressed[i]; + } + + file.close(); + + network->m_cryptoLookup->stop(true); + network->m_cryptoLookup->reload(); + + pkt.buffer->clear(); + delete pkt.buffer; + pkt.streamId = 0U; + if (decompressed != nullptr) { + delete[] decompressed; + } + mdNetwork->m_peerKeyUpdatePkt.unlock(); + mdNetwork->m_peerKeyUpdatePkt.erase(peerId); + } else { + pkt.locked = false; + } + } + } + } + break; + case NET_FUNC::REPL: if (req->fneHeader.getSubFunction() == NET_SUBFUNC::REPL_ACT_PEER_LIST) { // Peer Replication Active Peer List if (peerId > 0 && (network->m_peers.find(peerId) != network->m_peers.end())) { diff --git a/src/fne/network/MetadataNetwork.h b/src/fne/network/MetadataNetwork.h index 46ef5eb8..f561338f 100644 --- a/src/fne/network/MetadataNetwork.h +++ b/src/fne/network/MetadataNetwork.h @@ -117,6 +117,7 @@ namespace network bool locked; uint32_t timeout; }; + concurrent::unordered_map m_peerKeyUpdatePkt; concurrent::unordered_map m_peerReplicaActPkt; concurrent::unordered_map m_peerTreeListPkt;