diff --git a/src/aktualizr_get/get.cc b/src/aktualizr_get/get.cc index 2a682383ba..5b471c9f17 100644 --- a/src/aktualizr_get/get.cc +++ b/src/aktualizr_get/get.cc @@ -1,6 +1,7 @@ #include "get.h" #include "crypto/keymanager.h" #include "http/httpclient.h" +#include "storage/invstorage.h" std::string aktualizrGet(Config &config, const std::string &url) { auto storage = INvStorage::newStorage(config.storage); diff --git a/src/aktualizr_primary/secondary.cc b/src/aktualizr_primary/secondary.cc index 248f9ec33f..8d3011046d 100644 --- a/src/aktualizr_primary/secondary.cc +++ b/src/aktualizr_primary/secondary.cc @@ -106,9 +106,9 @@ class SecondaryWaiter { LOG_INFO << "Accepted connection from a secondary: (" << sec_ip << ":" << sec_port << ")"; try { - auto sec_creation_res = Uptane::IpUptaneSecondary::create(sec_ip, sec_port, con_socket_.native_handle()); - if (sec_creation_res.first) { - connected_secondaries_.push_back(sec_creation_res.second); + auto sec = Uptane::IpUptaneSecondary::create(sec_ip, sec_port, con_socket_.native_handle()); + if (sec != nullptr) { + connected_secondaries_.push_back(sec); } } catch (const std::exception& exc) { LOG_ERROR << "Failed to initialize a secondary: " << exc.what(); @@ -146,9 +146,9 @@ static Secondaries createIPSecondaries(const IPSecondariesConfig& config) { SecondaryWaiter sec_waiter{config.secondaries_wait_port, config.secondaries_timeout_s, result}; for (auto& ip_sec_cfg : config.secondaries_cfg) { - auto sec_creation_res = Uptane::IpUptaneSecondary::connectAndCreate(ip_sec_cfg.ip, ip_sec_cfg.port); - if (sec_creation_res.first) { - result.push_back(sec_creation_res.second); + auto sec = Uptane::IpUptaneSecondary::connectAndCreate(ip_sec_cfg.ip, ip_sec_cfg.port); + if (sec != nullptr) { + result.push_back(sec); } else { sec_waiter.addSecondary(ip_sec_cfg.ip, ip_sec_cfg.port); } diff --git a/src/aktualizr_secondary/CMakeLists.txt b/src/aktualizr_secondary/CMakeLists.txt index 4f15355c87..bff586cd86 100644 --- a/src/aktualizr_secondary/CMakeLists.txt +++ b/src/aktualizr_secondary/CMakeLists.txt @@ -1,12 +1,12 @@ set(AKTUALIZR_SECONDARY_SRC main.cc) set(AKTUALIZR_SECONDARY_LIB_SRC + update_agent_file.cc aktualizr_secondary.cc aktualizr_secondary_config.cc - aktualizr_secondary_common.cc aktualizr_secondary_metadata.cc - socket_server.cc - ) + secondary_tcp_server.cc + aktualizr_secondary_factory.cc) # do not link tests with libaktualizr list(REMOVE_ITEM TEST_LIBS aktualizr_lib) @@ -30,14 +30,14 @@ add_library(aktualizr_secondary_lib SHARED target_link_libraries(aktualizr_secondary_lib ${AKTUALIZR_EXTERNAL_LIBS}) target_include_directories(aktualizr_secondary_lib PUBLIC $ - ${PROJECT_SOURCE_DIR}/src/libaktualizr-posix - ) + ${PROJECT_SOURCE_DIR}/src/libaktualizr-posix) if (BUILD_ISOTP) target_sources(aktualizr_secondary_lib PRIVATE $) endif (BUILD_ISOTP) add_executable(aktualizr-secondary ${AKTUALIZR_SECONDARY_SRC}) + target_link_libraries(aktualizr-secondary aktualizr_secondary_lib) install(TARGETS aktualizr_secondary_lib LIBRARY DESTINATION lib COMPONENT aktualizr) @@ -46,13 +46,13 @@ install(TARGETS aktualizr-secondary RUNTIME DESTINATION bin) set(ALL_AKTUALIZR_SECONDARY_HEADERS + update_agent.h + update_agent_file.h aktualizr_secondary.h - aktualizr_secondary_interface.h aktualizr_secondary_config.h - aktualizr_secondary_common.h aktualizr_secondary_metadata.h - socket_server.h - ) + secondary_tcp_server.h + aktualizr_secondary_factory.h) include(AddAktualizrTest) @@ -62,47 +62,30 @@ list(INSERT TEST_LIBS 0 aktualizr_secondary_lib) add_aktualizr_test(NAME aktualizr_secondary_config SOURCES aktualizr_secondary_config_test.cc PROJECT_WORKING_DIRECTORY) +add_aktualizr_test(NAME secondary_tcp_server + SOURCES secondary_tcp_server_test.cc PROJECT_WORKING_DIRECTORY) + if(BUILD_OSTREE) -add_aktualizr_test(NAME aktualizr_secondary_uptane_verification_ostree - SOURCES uptane_verification_ostree_test.cc - ARGS ${PROJECT_BINARY_DIR}/ostree_repo - PROJECT_WORKING_DIRECTORY) + target_sources(aktualizr_secondary_lib PRIVATE update_agent_ostree.cc) + list(APPEND AKTUALIZR_SECONDARY_LIB_SRC update_agent_ostree.cc) + list(APPEND ALL_AKTUALIZR_SECONDARY_HEADERS update_agent_ostree.h) -set_target_properties(t_aktualizr_secondary_uptane_verification_ostree PROPERTIES LINK_FLAGS -Wl,--export-dynamic) -target_link_libraries(t_aktualizr_secondary_uptane_verification_ostree aktualizr_secondary_static_lib uptane_generator_lib) + add_aktualizr_test(NAME aktualizr_secondary_ostree SOURCES aktualizr_secondary_ostree_test.cc + ARGS ${PROJECT_BINARY_DIR}/ostree_repo PROJECT_WORKING_DIRECTORY) + + set_target_properties(t_aktualizr_secondary_ostree PROPERTIES LINK_FLAGS -Wl,--export-dynamic) + target_link_libraries(t_aktualizr_secondary_ostree aktualizr_secondary_lib uptane_generator_lib $) else(BUILD_OSTREE) - list(APPEND TEST_SOURCES uptane_verification_ostree_test.cc) + list(APPEND TEST_SOURCES aktualizr_secondary_ostree_test.cc update_agent_ostree.cc) + list(APPEND ALL_AKTUALIZR_SECONDARY_HEADERS update_agent_ostree.h) endif(BUILD_OSTREE) -add_aktualizr_test(NAME aktualizr_secondary_uptane_verification - SOURCES uptane_verification_test.cc +add_aktualizr_test(NAME aktualizr_secondary + SOURCES aktualizr_secondary_test.cc PROJECT_WORKING_DIRECTORY) -target_link_libraries(t_aktualizr_secondary_uptane_verification aktualizr_secondary_static_lib uptane_generator_lib) - -add_aktualizr_test(NAME aktualizr_secondary_update - SOURCES update_test.cc - ARGS ${PROJECT_BINARY_DIR}/ostree_repo - PROJECT_WORKING_DIRECTORY LIBRARIES aktualizr_secondary_lib) - -if(BUILD_OSTREE) - add_aktualizr_test(NAME aktualizr_secondary_uptane_verification - SOURCES uptane_verification_test.cc - ARGS ${PROJECT_BINARY_DIR}/ostree_repo - LIBRARIES aktualizr_secondary_lib uptane_generator_lib $ - PROJECT_WORKING_DIRECTORY ) - - set_target_properties(t_aktualizr_secondary_uptane_verification PROPERTIES LINK_FLAGS -Wl,--export-dynamic) - target_link_libraries(t_aktualizr_secondary_uptane_verification aktualizr_secondary_lib uptane_generator_lib) - - add_aktualizr_test(NAME aktualizr_secondary_uptane - SOURCES uptane_test.cc - LIBRARIES aktualizr_secondary_lib uptane_generator_lib virtual_secondary $ $ $ - ARGS ${PROJECT_BINARY_DIR}/ostree_repo PROJECT_WORKING_DIRECTORY) -else(BUILD_OSTREE) - list(APPEND TEST_SOURCES uptane_verification_test.cc uptane_test.cc) -endif(BUILD_OSTREE) +target_link_libraries(t_aktualizr_secondary aktualizr_secondary_lib uptane_generator_lib $) # test running the executable with command line option --help add_test(NAME aktualizr_secondary_cmdline--help COMMAND aktualizr-secondary --help) diff --git a/src/aktualizr_secondary/aktualizr_secondary.cc b/src/aktualizr_secondary/aktualizr_secondary.cc index e37bdd5756..bc13a92e14 100644 --- a/src/aktualizr_secondary/aktualizr_secondary.cc +++ b/src/aktualizr_secondary/aktualizr_secondary.cc @@ -1,97 +1,68 @@ #include "aktualizr_secondary.h" -#include -#include - +#include "crypto/keymanager.h" #include "logging/logging.h" -#ifdef BUILD_OSTREE -#include "package_manager/ostreemanager.h" // TODO: Hide behind PackageManagerInterface -#endif -#include "socket_server.h" +#include "update_agent.h" +#include "uptane/manifest.h" #include "utilities/utils.h" -class SecondaryAdapter : public Uptane::SecondaryInterface { - public: - SecondaryAdapter(AktualizrSecondary& sec) : secondary(sec) {} - ~SecondaryAdapter() override = default; - - Uptane::EcuSerial getSerial() override { return secondary.getSerialResp(); } - Uptane::HardwareIdentifier getHwId() override { return secondary.getHwIdResp(); } - PublicKey getPublicKey() override { return secondary.getPublicKeyResp(); } - Json::Value getManifest() override { return secondary.getManifestResp(); } - bool putMetadata(const Uptane::RawMetaPack& meta_pack) override { - return secondary.putMetadataResp(Metadata(meta_pack)); - } - int32_t getRootVersion(bool director) override { return secondary.getRootVersionResp(director); } - bool putRoot(const std::string& root, bool director) override { return secondary.putRootResp(root, director); } - bool sendFirmware(const std::shared_ptr& data) override { - return secondary.AktualizrSecondary::sendFirmwareResp(data); - } - data::ResultCode::Numeric install(const std::string& target_name) override { - return secondary.AktualizrSecondary::installResp(target_name); - } +#include +#include - private: - AktualizrSecondary& secondary; -}; - -AktualizrSecondary::AktualizrSecondary(const AktualizrSecondaryConfig& config, - const std::shared_ptr& storage) - : AktualizrSecondaryCommon(config, storage), - socket_server_(std_::make_unique(*this), SocketFromPort(config.network.port)) { - // note: we don't use TlsConfig here and supply the default to - // KeyManagerConf. Maybe we should figure a cleaner way to do that - // (split KeyManager?) +AktualizrSecondary::AktualizrSecondary(AktualizrSecondaryConfig config, std::shared_ptr storage, + std::shared_ptr bootloader, std::shared_ptr key_mngr, + std::shared_ptr update_agent) + : config_(std::move(config)), + storage_(std::move(storage)), + bootloader_(std::move(bootloader)), + keys_(std::move(key_mngr)), + update_agent_(std::move(update_agent)) { if (!uptaneInitialize()) { LOG_ERROR << "Failed to initialize"; return; } + manifest_issuer_ = std::make_shared(keys_, ecu_serial_); + if (rebootDetected()) { LOG_INFO << "Reboot has been detected, applying the new ostree revision: " << pending_target_.sha256Hash(); - // TODO: refactor this + // TODO: refactor this to make it simpler as we don't need to persist/store + // an installation status of each ECU but store it just for a given secondary ECU std::vector installed_versions; boost::optional pending_target; - storage->loadInstalledVersions(ecu_serial_.ToString(), nullptr, &pending_target); + storage_->loadInstalledVersions(ecu_serial_.ToString(), nullptr, &pending_target); if (!!pending_target) { - data::InstallationResult install_res = pacman->finalizeInstall(*pending_target); - storage->saveEcuInstallationResult(ecu_serial_, install_res); + data::InstallationResult install_res = update_agent_->applyPendingInstall(*pending_target); + storage_->saveEcuInstallationResult(ecu_serial_, install_res); if (install_res.success) { - storage->saveInstalledVersion(ecu_serial_.ToString(), *pending_target, InstalledVersionUpdateMode::kCurrent); + storage_->saveInstalledVersion(ecu_serial_.ToString(), *pending_target, InstalledVersionUpdateMode::kCurrent); } else { - // finalize failed - // unset pending flag so that the rest of the uptane process can - // go forward again - storage->saveInstalledVersion(ecu_serial_.ToString(), *pending_target, InstalledVersionUpdateMode::kNone); - director_repo_.dropTargets(*storage); + storage_->saveInstalledVersion(ecu_serial_.ToString(), *pending_target, InstalledVersionUpdateMode::kNone); + director_repo_.dropTargets(*storage_); } } - pacman->rebootFlagClear(); + bootloader_->rebootFlagClear(); } } -void AktualizrSecondary::run() { - connectToPrimary(); - socket_server_.Run(); -} - -void AktualizrSecondary::stop() { /* TODO? */ -} +Uptane::EcuSerial AktualizrSecondary::getSerial() const { return ecu_serial_; } -Uptane::EcuSerial AktualizrSecondary::getSerialResp() const { return ecu_serial_; } +Uptane::HardwareIdentifier AktualizrSecondary::getHwId() const { return hardware_id_; } -Uptane::HardwareIdentifier AktualizrSecondary::getHwIdResp() const { return hardware_id_; } +PublicKey AktualizrSecondary::getPublicKey() const { return keys_->UptanePublicKey(); } -PublicKey AktualizrSecondary::getPublicKeyResp() const { return keys_.UptanePublicKey(); } - -Json::Value AktualizrSecondary::getManifestResp() const { - Json::Value manifest = pacman->getManifest(getSerialResp()); +Uptane::Manifest AktualizrSecondary::getManifest() const { + Uptane::InstalledImageInfo installed_image_info; + Uptane::Manifest manifest; + if (update_agent_->getInstalledImageInfo(installed_image_info)) { + manifest = manifest_issuer_->assembleAndSignManifest(installed_image_info); + } - return keys_.signTuf(manifest); + return manifest; } -int32_t AktualizrSecondary::getRootVersionResp(bool director) const { +int32_t AktualizrSecondary::getRootVersion(bool director) const { std::string root_meta; if (!storage_->loadLatestRoot(&root_meta, (director) ? Uptane::RepositoryType::Director() : Uptane::RepositoryType::Image())) { @@ -102,30 +73,23 @@ int32_t AktualizrSecondary::getRootVersionResp(bool director) const { return Uptane::extractVersionUntrusted(root_meta); } -bool AktualizrSecondary::putRootResp(const std::string& root, bool director) { +bool AktualizrSecondary::putRoot(const std::string& root, bool director) { (void)root; (void)director; LOG_ERROR << "putRootResp is not implemented yet"; return false; } -bool AktualizrSecondary::putMetadataResp(const Metadata& metadata) { return doFullVerification(metadata); } +bool AktualizrSecondary::putMetadata(const Metadata& metadata) { return doFullVerification(metadata); } -bool AktualizrSecondary::sendFirmwareResp(const std::shared_ptr& firmware) { +bool AktualizrSecondary::sendFirmware(const std::string& firmware) { + // TODO: how to handle the case when secondary is rebooted after metadata are received if (!pending_target_.IsValid()) { LOG_ERROR << "No any pending target to receive update data/image for"; return false; } - std::unique_ptr downloader; - // TBD: this stuff has to be refactored and improved - if (pending_target_.IsOstree()) { - downloader = std_::make_unique(config_.pacman.sysroot, keys_); - } else { - downloader = std_::make_unique(); - } - - if (!downloader->download(pending_target_, *firmware)) { + if (!update_agent_->download(pending_target_, firmware)) { LOG_ERROR << "Failed to pull/store an update data"; pending_target_ = Uptane::Target::Unknown(); return false; @@ -134,50 +98,36 @@ bool AktualizrSecondary::sendFirmwareResp(const std::shared_ptr& fi return true; } -data::ResultCode::Numeric AktualizrSecondary::installResp(const std::string& target_name) { +data::ResultCode::Numeric AktualizrSecondary::install(const std::string& target_name) { + // TODO: how to handle the case when secondary is rebooted after metadata are received if (!pending_target_.IsValid()) { LOG_ERROR << "No any pending target to receive update data/image for"; return data::ResultCode::Numeric::kInternalError; } if (pending_target_.filename() != target_name) { - LOG_ERROR << "Targte name to install and the pending target name does not match"; + LOG_ERROR << "name of the target to install and a name of the pending target do not match"; return data::ResultCode::Numeric::kInternalError; } - auto install_result = pacman->install(pending_target_); + auto install_result = update_agent_->install(pending_target_); - switch (install_result.result_code.num_code) { + switch (install_result) { case data::ResultCode::Numeric::kOk: { storage_->saveInstalledVersion(ecu_serial_.ToString(), pending_target_, InstalledVersionUpdateMode::kCurrent); pending_target_ = Uptane::Target::Unknown(); + LOG_INFO << "The target has been successfully installed: " << target_name; break; } case data::ResultCode::Numeric::kNeedCompletion: { storage_->saveInstalledVersion(ecu_serial_.ToString(), pending_target_, InstalledVersionUpdateMode::kPending); + LOG_INFO << "The target has been successfully installed, but a reboot is required to be applied: " << target_name; break; } - default: {} + default: { LOG_INFO << "Failed to install the target: " << target_name; } } - LOG_INFO << "Target has been successfully installed: " << target_name; - return install_result.result_code.num_code; -} - -void AktualizrSecondary::connectToPrimary() { - Socket socket(config_.network.primary_ip, config_.network.primary_port); - - if (socket.bind(config_.network.port) != 0) { - LOG_ERROR << "Failed to bind a connection socket to the secondary's port"; - return; - } - - if (socket.connect() == 0) { - LOG_INFO << "Connected to Primary, sending info about this secondary..."; - socket_server_.HandleOneConnection(socket.getFD()); - } else { - LOG_INFO << "Failed to connect to Primary"; - } + return install_result; } bool AktualizrSecondary::doFullVerification(const Metadata& metadata) { @@ -227,101 +177,56 @@ bool AktualizrSecondary::doFullVerification(const Metadata& metadata) { return false; } - auto targetsForThisEcu = director_repo_.getTargets(getSerial(), getHardwareID()); + auto targetsForThisEcu = director_repo_.getTargets(getSerial(), getHwId()); if (targetsForThisEcu.size() != 1) { LOG_ERROR << "Invalid number of targets (should be 1): " << targetsForThisEcu.size(); return false; } + if (!update_agent_->isTargetSupported(targetsForThisEcu[0])) { + LOG_ERROR << "The given target type is not supported: " << targetsForThisEcu[0].type(); + return false; + } + pending_target_ = targetsForThisEcu[0]; return true; } -bool OstreeDirectDownloader::download(const Uptane::Target& target, const std::string& data) { - std::string treehub_server; - bool download_result = false; - - try { - std::string ca, cert, pkey, server_url; - extractCredentialsArchive(data, &ca, &cert, &pkey, &server_url); - _keyMngr.loadKeys(&pkey, &cert, &ca); - boost::trim(server_url); - treehub_server = server_url; - } catch (std::runtime_error& exc) { - LOG_ERROR << exc.what(); +bool AktualizrSecondary::uptaneInitialize() { + if (keys_->generateUptaneKeyPair().size() == 0) { + LOG_ERROR << "Failed to generate uptane key pair"; return false; } - auto install_res = OstreeManager::pull(_sysrootPath, treehub_server, _keyMngr, target); - - switch (install_res.result_code.num_code) { - case data::ResultCode::Numeric::kOk: { - LOG_INFO << "The target revision has been successfully downloaded: " << target.sha256Hash(); - download_result = true; - break; - } - case data::ResultCode::Numeric::kAlreadyProcessed: { - LOG_INFO << "The target revision is already present on the local ostree repo: " << target.sha256Hash(); - download_result = true; - break; - } - default: { - LOG_ERROR << "Failed to download the target revision: " << target.sha256Hash() << " ( " - << install_res.result_code.toString() << " ): " << install_res.description; - } - } + // from uptane/initialize.cc but we only take care of our own serial/hwid + EcuSerials ecu_serials; - return download_result; -} + if (storage_->loadEcuSerials(&ecu_serials)) { + ecu_serial_ = ecu_serials[0].first; + hardware_id_ = ecu_serials[0].second; -void OstreeDirectDownloader::extractCredentialsArchive(const std::string& archive, std::string* ca, std::string* cert, - std::string* pkey, std::string* treehub_server) { - ::extractCredentialsArchive(archive, ca, cert, pkey, treehub_server); -} - -void extractCredentialsArchive(const std::string& archive, std::string* ca, std::string* cert, std::string* pkey, - std::string* treehub_server) { - { - std::stringstream as(archive); - *ca = Utils::readFileFromArchive(as, "ca.pem"); - } - { - std::stringstream as(archive); - *cert = Utils::readFileFromArchive(as, "client.pem"); + return true; } - { - std::stringstream as(archive); - *pkey = Utils::readFileFromArchive(as, "pkey.pem"); - } - { - std::stringstream as(archive); - *treehub_server = Utils::readFileFromArchive(as, "server.url", true); - } -} -bool FakeDownloader::download(const Uptane::Target& target, const std::string& data) { - auto target_hashes = target.hashes(); - if (target_hashes.size() == 0) { - LOG_ERROR << "No hash found in the target metadata: " << target.filename(); - return false; + std::string ecu_serial_local = config_.uptane.ecu_serial; + if (ecu_serial_local.empty()) { + ecu_serial_local = keys_->UptanePublicKey().KeyId(); } - try { - auto received_image_data_hash = Uptane::Hash::generate(target_hashes[0].type(), data); - - if (!target.MatchHash(received_image_data_hash)) { - LOG_ERROR << "The received image data hash doesn't match the hash specified in the target metadata," - " hash type: " - << target_hashes[0].TypeString(); + std::string ecu_hardware_id = config_.uptane.ecu_hardware_id; + if (ecu_hardware_id.empty()) { + ecu_hardware_id = Utils::getHostname(); + if (ecu_hardware_id == "") { return false; } - - } catch (const std::exception& exc) { - LOG_ERROR << "Failed to generate a hash of the received image data: " << exc.what(); - return false; } + ecu_serials.emplace_back(Uptane::EcuSerial(ecu_serial_local), Uptane::HardwareIdentifier(ecu_hardware_id)); + storage_->storeEcuSerials(ecu_serials); + ecu_serial_ = ecu_serials[0].first; + hardware_id_ = ecu_serials[0].second; + return true; } diff --git a/src/aktualizr_secondary/aktualizr_secondary.h b/src/aktualizr_secondary/aktualizr_secondary.h index 4006d44d36..8d4174f2e2 100644 --- a/src/aktualizr_secondary/aktualizr_secondary.h +++ b/src/aktualizr_secondary/aktualizr_secondary.h @@ -1,93 +1,66 @@ #ifndef AKTUALIZR_SECONDARY_H #define AKTUALIZR_SECONDARY_H -#include - -#include "aktualizr_secondary_common.h" #include "aktualizr_secondary_config.h" -#include "aktualizr_secondary_interface.h" -#include "crypto/keymanager.h" -#include "socket_server.h" -#include "storage/invstorage.h" -#include "utilities/types.h" -#include "utilities/utils.h" +#include "aktualizr_secondary_metadata.h" +#include "uptane/secondaryinterface.h" #include "uptane/directorrepository.h" #include "uptane/imagesrepository.h" -#include "uptane/tuf.h" +#include "uptane/manifest.h" -#include "aktualizr_secondary_metadata.h" +class UpdateAgent; +class INvStorage; +class Bootloader; +class KeyManager; -class AktualizrSecondary : public AktualizrSecondaryInterface, private AktualizrSecondaryCommon { +class AktualizrSecondary : public Uptane::SecondaryInterface { public: - AktualizrSecondary(const AktualizrSecondaryConfig& config, const std::shared_ptr& storage); - void run() override; - void stop() override; - - // implementation of primary's SecondaryInterface - Uptane::EcuSerial getSerialResp() const; - Uptane::HardwareIdentifier getHwIdResp() const; - PublicKey getPublicKeyResp() const; - Json::Value getManifestResp() const; - bool putMetadataResp(const Metadata& metadata); - int32_t getRootVersionResp(bool director) const; - bool putRootResp(const std::string& root, bool director); - bool sendFirmwareResp(const std::shared_ptr& firmware); - data::ResultCode::Numeric installResp(const std::string& target_name); - - bool pendingRebootToApplyUpdate() { - // TODO: it's up to a pack man to know whether there is a pending install or not - return storage_->hasPendingInstall() && pending_target_.IsValid(); - } - - bool rebootDetected() { return pacman->rebootDetected() && storage_->hasPendingInstall(); } + using Ptr = std::shared_ptr; + + public: + // TODO: free AktualizrSecondary from dependencies as much as possible, e.g. bootloader + AktualizrSecondary(AktualizrSecondaryConfig config, std::shared_ptr storage, + std::shared_ptr bootloader, std::shared_ptr key_mngr, + std::shared_ptr update_agent); + + Uptane::EcuSerial getSerial() const override; + Uptane::HardwareIdentifier getHwId() const override; + PublicKey getPublicKey() const override; + + Uptane::Manifest getManifest() const override; + bool putMetadata(const Uptane::RawMetaPack& meta_pack) override { return putMetadata(Metadata(meta_pack)); } + int32_t getRootVersion(bool director) const override; + bool putRoot(const std::string& root, bool director) override; + bool sendFirmware(const std::string& firmware) override; + data::ResultCode::Numeric install(const std::string& target_name) override; + + bool putMetadata(const Metadata& metadata); private: - void connectToPrimary(); + bool rebootDetected() { return bootloader_->rebootDetected() && storage_->hasPendingInstall(); } bool doFullVerification(const Metadata& metadata); + bool uptaneInitialize(); private: - SocketServer socket_server_; + // Uptane verification Uptane::DirectorRepository director_repo_; Uptane::ImagesRepository image_repo_; - Uptane::Target pending_target_{Uptane::Target::Unknown()}; -}; - -// TBD: consider moving this and SotaUptaneClient::secondaryTreehubCredentials() to encapsulate them in one place that -// is shared between IP Secondary's component -void extractCredentialsArchive(const std::string& archive, std::string* ca, std::string* cert, std::string* pkey, - std::string* treehub_server); -class Downloader { - public: - Downloader() = default; - virtual ~Downloader() = default; - - Downloader(const Downloader&) = delete; - Downloader& operator=(const Downloader&) = delete; - - virtual bool download(const Uptane::Target& target, const std::string& data) = 0; -}; - -class OstreeDirectDownloader : public Downloader { - public: - OstreeDirectDownloader(const boost::filesystem::path& sysroot_path, KeyManager& key_mngr) - : _sysrootPath(sysroot_path), _keyMngr(key_mngr) {} + // installation + Uptane::Target pending_target_{Uptane::Target::Unknown()}; - bool download(const Uptane::Target& target, const std::string& data) override; + AktualizrSecondaryConfig config_; + std::shared_ptr storage_; + std::shared_ptr bootloader_; - private: - static void extractCredentialsArchive(const std::string& archive, std::string* ca, std::string* cert, - std::string* pkey, std::string* treehub_server); + std::shared_ptr keys_; + Uptane::ManifestIssuer::Ptr manifest_issuer_; - private: - boost::filesystem::path _sysrootPath; - KeyManager& _keyMngr; -}; + Uptane::EcuSerial ecu_serial_{Uptane::EcuSerial::Unknown()}; + Uptane::HardwareIdentifier hardware_id_{Uptane::HardwareIdentifier::Unknown()}; -class FakeDownloader : public Downloader { - public: - bool download(const Uptane::Target& target, const std::string& data) override; + std::shared_ptr update_agent_; }; #endif // AKTUALIZR_SECONDARY_H diff --git a/src/aktualizr_secondary/aktualizr_secondary_common.cc b/src/aktualizr_secondary/aktualizr_secondary_common.cc deleted file mode 100644 index 1bcde1cc81..0000000000 --- a/src/aktualizr_secondary/aktualizr_secondary_common.cc +++ /dev/null @@ -1,63 +0,0 @@ -#include "aktualizr_secondary_common.h" -#include "package_manager/packagemanagerfactory.h" -#include "utilities/utils.h" - -AktualizrSecondaryCommon::AktualizrSecondaryCommon(const AktualizrSecondaryConfig &config, - std::shared_ptr storage) - : config_(config), - storage_(std::move(storage)), - keys_(storage_, config.keymanagerConfig()), - ecu_serial_(Uptane::EcuSerial::Unknown()), - hardware_id_(Uptane::HardwareIdentifier::Unknown()) { - pacman = PackageManagerFactory::makePackageManager(config_.pacman, config_.bootloader, storage_, nullptr); - - // Load Root keys from storage - std::string root; - storage_->loadLatestRoot(&root, Uptane::RepositoryType::Director()); - if (root.size() > 0) { - LOG_DEBUG << "Loading root.json:" << root; - root_ = Uptane::Root(Uptane::RepositoryType::Director(), Utils::parseJSON(root)); - } else { - LOG_INFO << "No root.json in local storage, defaulting will accept the first root.json provided"; - root_ = Uptane::Root(Uptane::Root::Policy::kAcceptAll); - } -} - -bool AktualizrSecondaryCommon::uptaneInitialize() { - if (keys_.generateUptaneKeyPair().size() == 0) { - LOG_ERROR << "Failed to generate uptane key pair"; - return false; - } - - // from uptane/initialize.cc but we only take care of our own serial/hwid - EcuSerials ecu_serials; - - if (storage_->loadEcuSerials(&ecu_serials)) { - ecu_serial_ = ecu_serials[0].first; - hardware_id_ = ecu_serials[0].second; - - return true; - } - - std::string ecu_serial_local = config_.uptane.ecu_serial; - if (ecu_serial_local.empty()) { - ecu_serial_local = keys_.UptanePublicKey().KeyId(); - } - - std::string ecu_hardware_id = config_.uptane.ecu_hardware_id; - if (ecu_hardware_id.empty()) { - ecu_hardware_id = Utils::getHostname(); - if (ecu_hardware_id == "") { - return false; - } - } - - ecu_serials.emplace_back(Uptane::EcuSerial(ecu_serial_local), Uptane::HardwareIdentifier(ecu_hardware_id)); - storage_->storeEcuSerials(ecu_serials); - ecu_serial_ = ecu_serials[0].first; - hardware_id_ = ecu_serials[0].second; - - storage_->importInstalledVersions(config_.import.base_path); - - return true; -} diff --git a/src/aktualizr_secondary/aktualizr_secondary_common.h b/src/aktualizr_secondary/aktualizr_secondary_common.h deleted file mode 100644 index 79d82200c2..0000000000 --- a/src/aktualizr_secondary/aktualizr_secondary_common.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef AKTUALIZR_SECONDARY_COMMON_H_ -#define AKTUALIZR_SECONDARY_COMMON_H_ - -#include -#include -#include - -#include "aktualizr_secondary_config.h" -#include "crypto/keymanager.h" -#include "package_manager/packagemanagerinterface.h" -#include "storage/invstorage.h" - -class AktualizrSecondaryCommon { - public: - AktualizrSecondaryCommon(const AktualizrSecondaryConfig& /*config*/, std::shared_ptr /*storage*/); - - bool uptaneInitialize(); - - const Uptane::EcuSerial& getSerial() const { return ecu_serial_; } - const Uptane::HardwareIdentifier& getHardwareID() const { return hardware_id_; } - - protected: - AktualizrSecondaryConfig config_; - std::shared_ptr storage_; - KeyManager keys_; - Uptane::EcuSerial ecu_serial_; - Uptane::HardwareIdentifier hardware_id_; - std::shared_ptr pacman; - Uptane::Root root_; -}; - -#endif // AKTUALIZR_SECONDARY_COMMON_H_ diff --git a/src/aktualizr_secondary/aktualizr_secondary_factory.cc b/src/aktualizr_secondary/aktualizr_secondary_factory.cc new file mode 100644 index 0000000000..f86b282170 --- /dev/null +++ b/src/aktualizr_secondary/aktualizr_secondary_factory.cc @@ -0,0 +1,37 @@ +#include "aktualizr_secondary_factory.h" + +#include "crypto/keymanager.h" +#include "update_agent_file.h" + +#ifdef BUILD_OSTREE +#include "package_manager/ostreemanager.h" +#include "update_agent_ostree.h" +#endif + +// TODO: consider implementation of a proper registry/builder/factory +AktualizrSecondary::Ptr AktualizrSecondaryFactory::create(const AktualizrSecondaryConfig& config) { + auto storage = INvStorage::newStorage(config.storage); + return AktualizrSecondaryFactory::create(config, storage); +} + +AktualizrSecondary::Ptr AktualizrSecondaryFactory::create(const AktualizrSecondaryConfig& config, + const std::shared_ptr& storage) { + auto bootloader = std::make_shared(config.bootloader, storage); + auto key_mngr = std::make_shared(storage, config.keymanagerConfig()); + + UpdateAgent::Ptr update_agent; + + if (config.pacman.type != PackageManager::kOstree) { + update_agent = std::make_shared(config.pacman.target_name, config.pacman.filepath.string()); + } +#ifdef BUILD_OSTREE + else { + std::shared_ptr pack_man = + std::make_shared(config.pacman, bootloader, storage, nullptr); + update_agent = + std::make_shared(config.pacman.sysroot, key_mngr, pack_man, config.uptane.ecu_hardware_id); + } +#endif + + return std::make_shared(config, storage, bootloader, key_mngr, update_agent); +} diff --git a/src/aktualizr_secondary/aktualizr_secondary_factory.h b/src/aktualizr_secondary/aktualizr_secondary_factory.h new file mode 100644 index 0000000000..d1b16882d1 --- /dev/null +++ b/src/aktualizr_secondary/aktualizr_secondary_factory.h @@ -0,0 +1,13 @@ +#ifndef AKTUALIZR_SECONDARY_FACTORY_H +#define AKTUALIZR_SECONDARY_FACTORY_H + +#include "aktualizr_secondary.h" + +class AktualizrSecondaryFactory { + public: + static AktualizrSecondary::Ptr create(const AktualizrSecondaryConfig& config); + static AktualizrSecondary::Ptr create(const AktualizrSecondaryConfig& config, + const std::shared_ptr& storage); +}; + +#endif // AKTUALIZR_SECONDARY_FACTORY_H diff --git a/src/aktualizr_secondary/aktualizr_secondary_interface.h b/src/aktualizr_secondary/aktualizr_secondary_interface.h deleted file mode 100644 index 68329ac4d0..0000000000 --- a/src/aktualizr_secondary/aktualizr_secondary_interface.h +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef AKTUALIZR_SECONDARY_INTERFACE_H_ -#define AKTUALIZR_SECONDARY_INTERFACE_H_ - -class AktualizrSecondaryInterface { - public: - virtual ~AktualizrSecondaryInterface() = default; - virtual void run() = 0; - virtual void stop() = 0; -}; - -#endif // AKTUALIZR_SECONDARY_INTERFACE_H_ diff --git a/src/aktualizr_secondary/uptane_verification_ostree_test.cc b/src/aktualizr_secondary/aktualizr_secondary_ostree_test.cc similarity index 67% rename from src/aktualizr_secondary/uptane_verification_ostree_test.cc rename to src/aktualizr_secondary/aktualizr_secondary_ostree_test.cc index e066aefe40..880c9c67f9 100644 --- a/src/aktualizr_secondary/uptane_verification_ostree_test.cc +++ b/src/aktualizr_secondary/aktualizr_secondary_ostree_test.cc @@ -1,12 +1,18 @@ #include -#include -#include "aktualizr_secondary.h" +#include + +#include "boost/algorithm/string/trim.hpp" +#include "boost/process.hpp" + +#include "logging/logging.h" #include "test_utils.h" -#include "uptane_repo.h" -#include +#include "aktualizr_secondary.h" +#include "aktualizr_secondary_factory.h" #include "package_manager/ostreemanager.h" +#include "update_agent_ostree.h" +#include "uptane_repo.h" class Treehub { public: @@ -48,11 +54,11 @@ class Treehub { class OstreeRootfs { public: OstreeRootfs(const std::string& rootfs_template) { - auto sysroot_copy = Process("cp").run({"-r", rootfs_template, getPath()}); + auto sysroot_copy = Process("cp").run({"-r", rootfs_template, getPath().c_str()}); EXPECT_EQ(std::get<0>(sysroot_copy), 0) << std::get<1>(sysroot_copy); auto deployment_rev = Process("ostree").run( - {"rev-parse", std::string("--repo"), getPath() + "/ostree/repo", "generate-remote/generated"}); + {"rev-parse", std::string("--repo"), getPath().string() + "/ostree/repo", "generate-remote/generated"}); EXPECT_EQ(std::get<0>(deployment_rev), 0) << std::get<2>(deployment_rev); @@ -63,7 +69,7 @@ class OstreeRootfs { getDeploymentRev(), getDeploymentSerial())); } - const std::string getPath() const { return _sysroot_dir; } + const boost::filesystem::path& getPath() const { return _sysroot_dir; } const char* getDeploymentRev() const { return _rev.c_str(); } int getDeploymentSerial() const { return 0; } const char* getOSName() const { return _os_name.c_str(); } @@ -74,7 +80,7 @@ class OstreeRootfs { private: const std::string _os_name{"dummy-os"}; TemporaryDirectory _tmp_dir; - std::string _sysroot_dir{(_tmp_dir / "ostree-rootfs").c_str()}; + boost::filesystem::path _sysroot_dir{_tmp_dir / "ostree-rootfs"}; std::string _rev; GObjectUniquePtr _deployment; }; @@ -96,23 +102,11 @@ class AktualizrSecondaryWrapper { _config.storage.type = StorageType::kSqlite; _storage = INvStorage::newStorage(_config.storage); - _secondary = std::make_shared(_config, _storage); - } - - AktualizrSecondaryWrapper() { - // binary update - AktualizrSecondaryConfig config; - config.pacman.type = PackageManager::kNone; - - config.storage.path = _storage_dir.Path(); - config.storage.type = StorageType::kSqlite; - - auto storage = INvStorage::newStorage(config.storage); - _secondary = std::make_shared(config, storage); + _secondary = AktualizrSecondaryFactory::create(_config, _storage); } public: - std::shared_ptr& operator->() { return _secondary; } + Uptane::SecondaryInterface::Ptr& operator->() { return _secondary; } Uptane::Target getPendingVersion() const { return getVersion().first; } @@ -122,27 +116,26 @@ class AktualizrSecondaryWrapper { boost::optional current_target; boost::optional pending_target; - _storage->loadInstalledVersions(_secondary->getSerialResp().ToString(), ¤t_target, &pending_target); + _storage->loadInstalledVersions(_secondary->getSerial().ToString(), ¤t_target, &pending_target); return std::make_pair(!pending_target ? Uptane::Target::Unknown() : *pending_target, !current_target ? Uptane::Target::Unknown() : *current_target); } - std::string hardwareID() const { return _secondary->getHwIdResp().ToString(); } + std::string hardwareID() const { return _secondary->getHwId().ToString(); } - std::string serial() const { return _secondary->getSerialResp().ToString(); } + std::string serial() const { return _secondary->getSerial().ToString(); } void reboot() { boost::filesystem::remove(_storage_dir / _config.bootloader.reboot_sentinel_name); - _secondary.reset(new AktualizrSecondary(_config, _storage)); + _secondary = AktualizrSecondaryFactory::create(_config, _storage); } private: TemporaryDirectory _storage_dir; AktualizrSecondaryConfig _config; - - std::shared_ptr _secondary; std::shared_ptr _storage; + Uptane::SecondaryInterface::Ptr _secondary; }; class UptaneRepoWrapper { @@ -189,7 +182,7 @@ class UptaneRepoWrapper { UptaneRepo _uptane_repo{_root_dir.Path(), "", ""}; }; -class OstreeSecondaryUptaneVerificationTest : public ::testing::Test { +class SecondaryOstreeTest : public ::testing::Test { public: static const char* curOstreeRootfsRev(OstreeDeployment* ostree_depl) { (void)ostree_depl; @@ -217,11 +210,12 @@ class OstreeSecondaryUptaneVerificationTest : public ::testing::Test { } protected: - OstreeSecondaryUptaneVerificationTest() {} + SecondaryOstreeTest() {} - Metadata addDefaultTarget() { return addTarget(_treehub->curRev()); } + Uptane::RawMetaPack addDefaultTarget() { return addTarget(_treehub->curRev()); } - Metadata addTarget(const std::string& rev = "", const std::string& hardware_id = "", const std::string& serial = "") { + Uptane::RawMetaPack addTarget(const std::string& rev = "", const std::string& hardware_id = "", + const std::string& serial = "") { auto rev_to_apply = rev.empty() ? _treehub->curRev() : rev; auto hw_id = hardware_id.empty() ? _secondary.hardwareID() : hardware_id; auto serial_id = serial.empty() ? _secondary.serial() : serial; @@ -231,19 +225,22 @@ class OstreeSecondaryUptaneVerificationTest : public ::testing::Test { return currentMetadata(); } - Metadata currentMetadata() const { return _uptane_repo.getCurrentMetadata(); } + Uptane::RawMetaPack currentMetadata() const { return _uptane_repo.getCurrentMetadata(); } - std::shared_ptr getCredsToSend() const { + std::string getCredsToSend() const { std::map creds_map = { {"ca.pem", ""}, {"client.pem", ""}, {"pkey.pem", ""}, {"server.url", _treehub->url()}}; std::stringstream creads_strstream; Utils::writeArchive(creds_map, creads_strstream); - return std::make_shared(creads_strstream.str()); + return creads_strstream.str(); } Uptane::Hash treehubCurRevHash() const { return Uptane::Hash(Uptane::Hash::Type::kSha256, _treehub->curRev()); } + Uptane::Hash sysrootCurRevHash() const { + return Uptane::Hash(Uptane::Hash::Type::kSha256, _sysroot->getDeploymentRev()); + } const std::string& treehubCurRev() const { return _treehub->curRev(); } protected: @@ -255,35 +252,60 @@ class OstreeSecondaryUptaneVerificationTest : public ::testing::Test { UptaneRepoWrapper _uptane_repo; }; -std::shared_ptr OstreeSecondaryUptaneVerificationTest::_treehub{nullptr}; -std::string OstreeSecondaryUptaneVerificationTest::_ostree_rootfs_template{"./build/ostree_repo"}; -std::shared_ptr OstreeSecondaryUptaneVerificationTest::_sysroot{nullptr}; +std::shared_ptr SecondaryOstreeTest::_treehub{nullptr}; +std::string SecondaryOstreeTest::_ostree_rootfs_template{"./build/ostree_repo"}; +std::shared_ptr SecondaryOstreeTest::_sysroot{nullptr}; + +TEST_F(SecondaryOstreeTest, fullUptaneVerificationInvalidRevision) { + EXPECT_TRUE(_secondary->putMetadata(addTarget("invalid-revision"))); + EXPECT_FALSE(_secondary->sendFirmware(getCredsToSend())); +} + +TEST_F(SecondaryOstreeTest, fullUptaneVerificationInvalidHwID) { + EXPECT_FALSE(_secondary->putMetadata(addTarget("", "invalid-hardware-id", ""))); +} + +TEST_F(SecondaryOstreeTest, fullUptaneVerificationInvalidSerial) { + EXPECT_FALSE(_secondary->putMetadata(addTarget("", "", "invalid-serial-id"))); +} + +TEST_F(SecondaryOstreeTest, verifyUpdatePositive) { + // check the version reported in the manifest just after an initial boot + Uptane::Manifest manifest = _secondary->getManifest(); + EXPECT_TRUE(manifest.verifySignature(_secondary->getPublicKey())); + EXPECT_EQ(manifest.installedImageHash(), sysrootCurRevHash()); + + // do update + EXPECT_TRUE(_secondary->putMetadata(addDefaultTarget())); + EXPECT_TRUE(_secondary->sendFirmware(getCredsToSend())); + EXPECT_EQ(_secondary->install(treehubCurRev()), data::ResultCode::Numeric::kNeedCompletion); -TEST_F(OstreeSecondaryUptaneVerificationTest, fullUptaneVerificationPositive) { - EXPECT_TRUE(_secondary->putMetadataResp(addDefaultTarget())); - EXPECT_TRUE(_secondary->sendFirmwareResp(getCredsToSend())); - EXPECT_EQ(_secondary->installResp(treehubCurRev()), data::ResultCode::Numeric::kNeedCompletion); - EXPECT_TRUE(_secondary->pendingRebootToApplyUpdate()); + // check if the update was installed and pending EXPECT_TRUE(_secondary.getPendingVersion().MatchHash(treehubCurRevHash())); + // manifest should still report the old version + manifest = _secondary->getManifest(); + EXPECT_TRUE(manifest.verifySignature(_secondary->getPublicKey())); + EXPECT_EQ(manifest.installedImageHash(), sysrootCurRevHash()); + // emulate reboot _sysroot->setNewDeploymentRev(treehubCurRev()); - _secondary.reboot(); + + // check if the version in the DB and reported in the manifest matches with the installed and applied one EXPECT_FALSE(_secondary.getPendingVersion().IsValid()); EXPECT_TRUE(_secondary.getCurrentVersion().MatchHash(treehubCurRevHash())); -} - -TEST_F(OstreeSecondaryUptaneVerificationTest, fullUptaneVerificationInvalidRevision) { - EXPECT_TRUE(_secondary->putMetadataResp(addTarget("invalid-revision"))); - EXPECT_FALSE(_secondary->sendFirmwareResp(getCredsToSend())); -} - -TEST_F(OstreeSecondaryUptaneVerificationTest, fullUptaneVerificationInvalidHwID) { - EXPECT_FALSE(_secondary->putMetadataResp(addTarget("", "invalid-hardware-id", ""))); -} + manifest = _secondary->getManifest(); + EXPECT_TRUE(manifest.verifySignature(_secondary->getPublicKey())); + EXPECT_EQ(manifest.installedImageHash(), treehubCurRevHash()); -TEST_F(OstreeSecondaryUptaneVerificationTest, fullUptaneVerificationInvalidSerial) { - EXPECT_FALSE(_secondary->putMetadataResp(addTarget("", "", "invalid-serial-id"))); + // emulate reboot + // check if the installed version persists after a reboot + _secondary.reboot(); + EXPECT_FALSE(_secondary.getPendingVersion().IsValid()); + EXPECT_TRUE(_secondary.getCurrentVersion().MatchHash(treehubCurRevHash())); + manifest = _secondary->getManifest(); + EXPECT_TRUE(manifest.verifySignature(_secondary->getPublicKey())); + EXPECT_EQ(manifest.installedImageHash(), treehubCurRevHash()); } #ifndef __NO_MAIN__ @@ -295,7 +317,7 @@ int main(int argc, char** argv) { return EXIT_FAILURE; } - OstreeSecondaryUptaneVerificationTest::setOstreeRootfsTemplate(argv[1]); + SecondaryOstreeTest::setOstreeRootfsTemplate(argv[1]); logger_init(); logger_set_threshold(boost::log::trivial::info); @@ -305,9 +327,9 @@ int main(int argc, char** argv) { #endif extern "C" OstreeDeployment* ostree_sysroot_get_booted_deployment(OstreeSysroot* ostree_sysroot) { - return OstreeSecondaryUptaneVerificationTest::curOstreeDeployment(ostree_sysroot); + return SecondaryOstreeTest::curOstreeDeployment(ostree_sysroot); } extern "C" const char* ostree_deployment_get_csum(OstreeDeployment* ostree_deployment) { - return OstreeSecondaryUptaneVerificationTest::curOstreeRootfsRev(ostree_deployment); + return SecondaryOstreeTest::curOstreeRootfsRev(ostree_deployment); } diff --git a/src/aktualizr_secondary/uptane_verification_test.cc b/src/aktualizr_secondary/aktualizr_secondary_test.cc similarity index 66% rename from src/aktualizr_secondary/uptane_verification_test.cc rename to src/aktualizr_secondary/aktualizr_secondary_test.cc index 0ad087b4fc..f93734170d 100644 --- a/src/aktualizr_secondary/uptane_verification_test.cc +++ b/src/aktualizr_secondary/aktualizr_secondary_test.cc @@ -1,22 +1,23 @@ #include #include + #include "aktualizr_secondary.h" +#include "aktualizr_secondary_factory.h" #include "test_utils.h" #include "uptane_repo.h" class AktualizrSecondaryWrapper { public: AktualizrSecondaryWrapper() { - // binary update AktualizrSecondaryConfig config; config.pacman.type = PackageManager::kNone; config.storage.path = _storage_dir.Path(); config.storage.type = StorageType::kSqlite; - auto storage = INvStorage::newStorage(config.storage); - _secondary = std::make_shared(config, storage); + _storage = INvStorage::newStorage(config.storage); + _secondary = AktualizrSecondaryFactory::create(config, _storage); } public: @@ -25,17 +26,17 @@ class AktualizrSecondaryWrapper { Uptane::Target getPendingVersion() const { boost::optional pending_target; - _storage->loadInstalledVersions(_secondary->getSerialResp().ToString(), nullptr, &pending_target); + _storage->loadInstalledVersions(_secondary->getSerial().ToString(), nullptr, &pending_target); return *pending_target; } - std::string hardwareID() const { return _secondary->getHwIdResp().ToString(); } + std::string hardwareID() const { return _secondary->getHwId().ToString(); } - std::string serial() const { return _secondary->getSerialResp().ToString(); } + std::string serial() const { return _secondary->getSerial().ToString(); } private: TemporaryDirectory _storage_dir; - std::shared_ptr _secondary; + AktualizrSecondary::Ptr _secondary; std::shared_ptr _storage; }; @@ -71,9 +72,9 @@ class UptaneRepoWrapper { return metadata; } - std::shared_ptr getImageData(const std::string& targetname) const { - auto image_data = std::make_shared(); - boost::filesystem::load_string_file(_root_dir / targetname, *image_data); + std::string getImageData(const std::string& targetname) const { + std::string image_data; + boost::filesystem::load_string_file(_root_dir / targetname, image_data); return image_data; } @@ -84,14 +85,13 @@ class UptaneRepoWrapper { UptaneRepo _uptane_repo{_root_dir.Path(), "", ""}; }; -class SecondaryUptaneVerificationTest : public ::testing::Test { +class SecondaryTest : public ::testing::Test { protected: - SecondaryUptaneVerificationTest() { - _uptane_repo.addImageFile(_default_target, _secondary->getHwIdResp().ToString(), - _secondary->getSerialResp().ToString()); + SecondaryTest() { + _uptane_repo.addImageFile(_default_target, _secondary->getHwId().ToString(), _secondary->getSerial().ToString()); } - std::shared_ptr getImageData(const std::string& targetname = _default_target) const { + std::string getImageData(const std::string& targetname = _default_target) const { return _uptane_repo.getImageData(targetname); } @@ -101,9 +101,8 @@ class SecondaryUptaneVerificationTest : public ::testing::Test { UptaneRepoWrapper _uptane_repo; }; -class SecondaryUptaneVerificationTestNegative - : public SecondaryUptaneVerificationTest, - public ::testing::WithParamInterface> { +class SecondaryTestNegative : public SecondaryTest, + public ::testing::WithParamInterface> { protected: class MetadataInvalidator : public Metadata { public: @@ -138,15 +137,13 @@ class SecondaryUptaneVerificationTestNegative * * see INSTANTIATE_TEST_SUITE_P for the test instantiations with concrete parameter values */ -TEST_P(SecondaryUptaneVerificationTestNegative, MalformedMetadaJson) { - EXPECT_FALSE(_secondary->putMetadataResp(currentMetadata())); -} +TEST_P(SecondaryTestNegative, MalformedMetadaJson) { EXPECT_FALSE(_secondary->putMetadata(currentMetadata())); } /** * Instantiates the parameterized test for each specified value of std::pair * the parameter value indicates which metadata to malform */ -INSTANTIATE_TEST_SUITE_P(SecondaryUptaneVerificationMalformedMetadata, SecondaryUptaneVerificationTestNegative, +INSTANTIATE_TEST_SUITE_P(SecondaryTestMalformedMetadata, SecondaryTestNegative, ::testing::Values(std::make_pair(Uptane::RepositoryType::Director(), Uptane::Role::Root()), std::make_pair(Uptane::RepositoryType::Director(), Uptane::Role::Targets()), std::make_pair(Uptane::RepositoryType::Image(), Uptane::Role::Root()), @@ -154,60 +151,61 @@ INSTANTIATE_TEST_SUITE_P(SecondaryUptaneVerificationMalformedMetadata, Secondary std::make_pair(Uptane::RepositoryType::Image(), Uptane::Role::Snapshot()), std::make_pair(Uptane::RepositoryType::Image(), Uptane::Role::Targets()))); -TEST_F(SecondaryUptaneVerificationTest, fullUptaneVerificationPositive) { - EXPECT_TRUE(_secondary->putMetadataResp(_uptane_repo.getCurrentMetadata())); - EXPECT_TRUE(_secondary->sendFirmwareResp(getImageData())); - EXPECT_EQ(_secondary->installResp(_default_target), data::ResultCode::Numeric::kOk); +TEST_F(SecondaryTest, fullUptaneVerificationPositive) { + EXPECT_TRUE(_secondary->putMetadata(_uptane_repo.getCurrentMetadata())); + EXPECT_TRUE(_secondary->sendFirmware(getImageData())); + EXPECT_EQ(_secondary->install(_default_target), data::ResultCode::Numeric::kOk); } -TEST_F(SecondaryUptaneVerificationTest, TwoImagesAndOneTarget) { +TEST_F(SecondaryTest, TwoImagesAndOneTarget) { // two images for the same ECU, just one of them is added as a target and signed // default image and corresponding target has been already added, just add another image - _uptane_repo.addImageFile("second_image_00", _secondary->getHwIdResp().ToString(), - _secondary->getSerialResp().ToString(), false); - EXPECT_TRUE(_secondary->putMetadataResp(_uptane_repo.getCurrentMetadata())); + _uptane_repo.addImageFile("second_image_00", _secondary->getHwId().ToString(), _secondary->getSerial().ToString(), + false); + EXPECT_TRUE(_secondary->putMetadata(_uptane_repo.getCurrentMetadata())); } -TEST_F(SecondaryUptaneVerificationTest, IncorrectTargetQuantity) { +TEST_F(SecondaryTest, IncorrectTargetQuantity) { { // two targets for the same ECU - _uptane_repo.addImageFile("second_target", _secondary->getHwIdResp().ToString(), - _secondary->getSerialResp().ToString()); + _uptane_repo.addImageFile("second_target", _secondary->getHwId().ToString(), _secondary->getSerial().ToString()); - EXPECT_FALSE(_secondary->putMetadataResp(_uptane_repo.getCurrentMetadata())); + EXPECT_FALSE(_secondary->putMetadata(_uptane_repo.getCurrentMetadata())); } { // zero targets for the ECU being tested auto metadata = - UptaneRepoWrapper().addImageFile("mytarget", _secondary->getHwIdResp().ToString(), "non-existing-serial"); + UptaneRepoWrapper().addImageFile("mytarget", _secondary->getHwId().ToString(), "non-existing-serial"); - EXPECT_FALSE(_secondary->putMetadataResp(metadata)); + EXPECT_FALSE(_secondary->putMetadata(metadata)); } { // zero targets for the ECU being tested auto metadata = - UptaneRepoWrapper().addImageFile("mytarget", "non-existig-hwid", _secondary->getSerialResp().ToString()); + UptaneRepoWrapper().addImageFile("mytarget", "non-existig-hwid", _secondary->getSerial().ToString()); - EXPECT_FALSE(_secondary->putMetadataResp(metadata)); + EXPECT_FALSE(_secondary->putMetadata(metadata)); } } -TEST_F(SecondaryUptaneVerificationTest, InvalidImageFileSize) { - EXPECT_TRUE(_secondary->putMetadataResp(_uptane_repo.getCurrentMetadata())); +TEST_F(SecondaryTest, InvalidImageFileSize) { + EXPECT_TRUE(_secondary->putMetadata(_uptane_repo.getCurrentMetadata())); auto image_data = getImageData(); - image_data->append("\n"); - EXPECT_FALSE(_secondary->sendFirmwareResp(image_data)); + image_data.append("\n"); + EXPECT_FALSE(_secondary->sendFirmware(image_data)); } -TEST_F(SecondaryUptaneVerificationTest, InvalidImageData) { - EXPECT_TRUE(_secondary->putMetadataResp(_uptane_repo.getCurrentMetadata())); +TEST_F(SecondaryTest, InvalidImageData) { + EXPECT_TRUE(_secondary->putMetadata(_uptane_repo.getCurrentMetadata())); auto image_data = getImageData(); - image_data->operator[](3) = '0'; - EXPECT_FALSE(_secondary->sendFirmwareResp(image_data)); + image_data.operator[](3) = '0'; + EXPECT_FALSE(_secondary->sendFirmware(image_data)); } +// TODO: add more tests in case of file/binary based updates + #ifndef __NO_MAIN__ int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); diff --git a/src/aktualizr_secondary/main.cc b/src/aktualizr_secondary/main.cc index 957e6660d3..dd859c1f00 100644 --- a/src/aktualizr_secondary/main.cc +++ b/src/aktualizr_secondary/main.cc @@ -6,11 +6,12 @@ #include "aktualizr_secondary.h" #include "aktualizr_secondary_config.h" +#include "aktualizr_secondary_factory.h" +#include "logging/logging.h" +#include "secondary_tcp_server.h" #include "utilities/aktualizr_version.h" #include "utilities/utils.h" -#include "logging/logging.h" - namespace bpo = boost::program_options; void check_secondary_options(const bpo::options_description &description, const bpo::variables_map &vm) { @@ -85,11 +86,8 @@ int main(int argc, char *argv[]) { AktualizrSecondaryConfig config(commandline_map); LOG_DEBUG << "Current directory: " << boost::filesystem::current_path().string(); - // storage (share class with primary) - std::shared_ptr storage = INvStorage::newStorage(config.storage); - std::unique_ptr secondary; - secondary = std_::make_unique(config, storage); - secondary->run(); + auto secondary = AktualizrSecondaryFactory::create(config); + SecondaryTcpServer(*secondary, config.network.primary_ip, config.network.primary_port, config.network.port).run(); } catch (std::runtime_error &exc) { LOG_ERROR << "Error: " << exc.what(); diff --git a/src/aktualizr_secondary/socket_server.cc b/src/aktualizr_secondary/secondary_tcp_server.cc similarity index 72% rename from src/aktualizr_secondary/socket_server.cc rename to src/aktualizr_secondary/secondary_tcp_server.cc index 7f054b6536..46bc24193f 100644 --- a/src/aktualizr_secondary/socket_server.cc +++ b/src/aktualizr_secondary/secondary_tcp_server.cc @@ -1,30 +1,38 @@ -#include "socket_server.h" - -#include +#include "secondary_tcp_server.h" #include "AKIpUptaneMes.h" #include "asn1/asn1_message.h" #include "logging/logging.h" +#include "uptane/secondaryinterface.h" #include "utilities/dequeue_buffer.h" -#include "utilities/sockaddr_io.h" #include -#include -#include -void SocketServer::Run() { - if (listen(*socket_, SOMAXCONN) < 0) { +SecondaryTcpServer::SecondaryTcpServer(Uptane::SecondaryInterface &secondary, const std::string &primary_ip, + in_port_t primary_port, in_port_t port) + : SecondaryTcpServer(secondary, port) { + ConnectionSocket conn_socket(primary_ip, primary_port, listen_socket_.port()); + if (conn_socket.connect() == 0) { + LOG_INFO << "Connected to Primary, sending info about this secondary..."; + HandleOneConnection(*conn_socket); + } else { + LOG_INFO << "Failed to connect to Primary"; + } +} + +void SecondaryTcpServer::run() { + if (listen(*listen_socket_, SOMAXCONN) < 0) { throw std::system_error(errno, std::system_category(), "listen"); } - LOG_INFO << "Listening on " << Utils::ipGetSockaddr(*socket_); + LOG_INFO << "Secondary TCP server listens on " << listen_socket_.toString(); - while (true) { + while (keep_running_.load()) { int con_fd; sockaddr_storage peer_sa{}; socklen_t peer_sa_size = sizeof(sockaddr_storage); LOG_DEBUG << "Waiting for connection from client..."; - if ((con_fd = accept(*socket_, reinterpret_cast(&peer_sa), &peer_sa_size)) == -1) { + if ((con_fd = accept(*listen_socket_, reinterpret_cast(&peer_sa), &peer_sa_size)) == -1) { LOG_INFO << "Socket accept failed. aborting"; break; } @@ -32,9 +40,18 @@ void SocketServer::Run() { HandleOneConnection(con_fd); LOG_DEBUG << "Client disconnected"; } + LOG_INFO << "Secondary TCP server exit"; } -void SocketServer::HandleOneConnection(int socket) { +void SecondaryTcpServer::stop() { + keep_running_ = false; + // unblock accept + ConnectionSocket("localhost", listen_socket_.port()).connect(); +} + +in_port_t SecondaryTcpServer::port() const { return listen_socket_.port(); } + +void SecondaryTcpServer::HandleOneConnection(int socket) { // Outside the message loop, because one recv() may have parts of 2 messages // Note that one recv() call returning 2+ messages doesn't work at the // moment. This shouldn't be a problem until we have messages that aren't @@ -66,9 +83,9 @@ void SocketServer::HandleOneConnection(int socket) { Asn1Message::Ptr resp = Asn1Message::Empty(); switch (msg->present()) { case AKIpUptaneMes_PR_getInfoReq: { - Uptane::EcuSerial serial = impl_->getSerial(); - Uptane::HardwareIdentifier hw_id = impl_->getHwId(); - PublicKey pk = impl_->getPublicKey(); + Uptane::EcuSerial serial = impl_.getSerial(); + Uptane::HardwareIdentifier hw_id = impl_.getHwId(); + PublicKey pk = impl_.getPublicKey(); resp->present(AKIpUptaneMes_PR_getInfoResp); auto r = resp->getInfoResp(); SetString(&r->ecuSerial, serial.ToString()); @@ -77,7 +94,7 @@ void SocketServer::HandleOneConnection(int socket) { SetString(&r->key, pk.Value()); } break; case AKIpUptaneMes_PR_manifestReq: { - std::string manifest = Utils::jsonToStr(impl_->getManifest()); + std::string manifest = Utils::jsonToStr(impl_.getManifest()); resp->present(AKIpUptaneMes_PR_manifestResp); auto r = resp->manifestResp(); r->manifest.present = manifest_PR_json; @@ -103,7 +120,7 @@ void SocketServer::HandleOneConnection(int socket) { } bool ok; try { - ok = impl_->putMetadata(meta_pack); + ok = impl_.putMetadata(meta_pack); } catch (Uptane::SecurityException &e) { LOG_WARNING << "Rejected metadata push because of security failure" << e.what(); ok = false; @@ -114,16 +131,15 @@ void SocketServer::HandleOneConnection(int socket) { } break; case AKIpUptaneMes_PR_sendFirmwareReq: { auto fw = msg->sendFirmwareReq(); - auto fw_data = std::make_shared(ToString(fw->firmware)); - auto fut = std::async(std::launch::async, &Uptane::SecondaryInterface::sendFirmware, impl_, fw_data); + auto send_firmware_result = impl_.sendFirmware(ToString(fw->firmware)); resp->present(AKIpUptaneMes_PR_sendFirmwareResp); auto r = resp->sendFirmwareResp(); - r->result = fut.get() ? AKInstallationResult_success : AKInstallationResult_failure; + r->result = send_firmware_result ? AKInstallationResult_success : AKInstallationResult_failure; } break; case AKIpUptaneMes_PR_installReq: { auto request = msg->installReq(); - auto install_result = impl_->install(ToString(request->hash)); + auto install_result = impl_.install(ToString(request->hash)); resp->present(AKIpUptaneMes_PR_installResp); auto response_message = resp->installResp(); @@ -154,34 +170,3 @@ void SocketServer::HandleOneConnection(int socket) { // write error => Shutdown the socket // Timeout on write => shutdown } - -SocketHandle SocketFromPort(in_port_t port) { - // manual socket creation - int socket_fd = socket(AF_INET6, SOCK_STREAM, 0); - if (socket_fd < 0) { - throw std::runtime_error("socket creation failed"); - } - SocketHandle hdl(new int(socket_fd)); - sockaddr_in6 sa{}; - - memset(&sa, 0, sizeof(sa)); - sa.sin6_family = AF_INET6; - sa.sin6_port = htons(port); - sa.sin6_addr = IN6ADDR_ANY_INIT; - - int v6only = 0; - if (setsockopt(*hdl, IPPROTO_IPV6, IPV6_V6ONLY, &v6only, sizeof(v6only)) < 0) { - throw std::system_error(errno, std::system_category(), "setsockopt(IPV6_V6ONLY)"); - } - - int reuseaddr = 1; - if (setsockopt(*hdl, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)) < 0) { - throw std::system_error(errno, std::system_category(), "setsockopt(SO_REUSEADDR)"); - } - - if (bind(*hdl, reinterpret_cast(&sa), sizeof(sa)) < 0) { - throw std::system_error(errno, std::system_category(), "bind"); - } - - return hdl; -} diff --git a/src/aktualizr_secondary/secondary_tcp_server.h b/src/aktualizr_secondary/secondary_tcp_server.h new file mode 100644 index 0000000000..aa2244c2ae --- /dev/null +++ b/src/aktualizr_secondary/secondary_tcp_server.h @@ -0,0 +1,44 @@ +#ifndef AKTUALIZR_SECONDARY_TCP_SERVER_H_ +#define AKTUALIZR_SECONDARY_TCP_SERVER_H_ + +#include "utilities/utils.h" + +#include + +namespace Uptane { +class SecondaryInterface; +} // namespace Uptane +/** + * Listens on a socket, decodes calls (ASN.1) and forwards them to an Uptane Secondary + * implementation + */ +class SecondaryTcpServer { + public: + SecondaryTcpServer(Uptane::SecondaryInterface& secondary, const std::string& primary_ip, in_port_t primary_port, + in_port_t port = 0); + + SecondaryTcpServer(Uptane::SecondaryInterface& secondary, in_port_t port = 0) + : keep_running_(true), impl_(secondary), listen_socket_(port) {} + + SecondaryTcpServer(const SecondaryTcpServer&) = delete; + SecondaryTcpServer& operator=(const SecondaryTcpServer&) = delete; + + public: + /** + * Accept connections on the socket, decode requests and respond using the secondary implementation + */ + void run(); + void stop(); + + in_port_t port() const; + + private: + void HandleOneConnection(int socket); + + private: + std::atomic keep_running_; + Uptane::SecondaryInterface& impl_; + ListenSocket listen_socket_; +}; + +#endif // AKTUALIZR_SECONDARY_TCP_SERVER_H_ diff --git a/src/aktualizr_secondary/secondary_tcp_server_test.cc b/src/aktualizr_secondary/secondary_tcp_server_test.cc new file mode 100644 index 0000000000..6b6619bce0 --- /dev/null +++ b/src/aktualizr_secondary/secondary_tcp_server_test.cc @@ -0,0 +1,131 @@ +#include + +#include "ipuptanesecondary.h" +#include "logging/logging.h" +#include "secondary_tcp_server.h" +#include "test_utils.h" +#include "uptane/secondaryinterface.h" + +class SecondaryMock : public Uptane::SecondaryInterface { + public: + SecondaryMock(const Uptane::EcuSerial& serial, const Uptane::HardwareIdentifier& hdw_id, const PublicKey& pub_key, + const Uptane::Manifest& manifest) + : _serial(serial), _hdw_id(hdw_id), _pub_key(pub_key), _manifest(manifest) {} + + public: + virtual Uptane::EcuSerial getSerial() const { return _serial; } + virtual Uptane::HardwareIdentifier getHwId() const { return _hdw_id; } + virtual PublicKey getPublicKey() const { return _pub_key; } + virtual Uptane::Manifest getManifest() const { return _manifest; } + virtual bool putMetadata(const Uptane::RawMetaPack& meta_pack) { + _metapack = meta_pack; + return true; + } + virtual int32_t getRootVersion(bool director) const { + (void)director; + return 0; + } + virtual bool putRoot(const std::string& root, bool director) { + (void)root; + (void)director; + return true; + } + + virtual bool sendFirmware(const std::string& data) { + _data = data; + return true; + } + + virtual data::ResultCode::Numeric install(const std::string& target_name) { + (void)target_name; + return data::ResultCode::Numeric::kOk; + } + + public: + const Uptane::EcuSerial _serial; + const Uptane::HardwareIdentifier _hdw_id; + const PublicKey _pub_key; + const Uptane::Manifest _manifest; + + Uptane::RawMetaPack _metapack; + std::string _data; +}; + +bool operator==(const Uptane::RawMetaPack& lhs, const Uptane::RawMetaPack& rhs) { + return (lhs.director_root == rhs.director_root) && (lhs.image_root == rhs.image_root) && + (lhs.director_targets == rhs.director_targets) && (lhs.image_snapshot == rhs.image_snapshot) && + (lhs.image_timestamp == rhs.image_timestamp) && (lhs.image_targets == rhs.image_targets); +} + +// Test the serialization/deserialization and the TCP/IP communication implementation +// that occurs during communication between Primary and IP Secondary +TEST(SecondaryTcpServer, TestIpSecondaryRPC) { + // secondary object on Secondary ECU + SecondaryMock secondary(Uptane::EcuSerial("serial"), Uptane::HardwareIdentifier("hardware-id"), + PublicKey("pub-key", KeyType::kED25519), Uptane::Manifest()); + + // create Secondary on Secondary ECU, and run it in a dedicated thread + SecondaryTcpServer secondary_server{secondary}; + std::thread secondary_server_thread{[&secondary_server]() { secondary_server.run(); }}; + + // create Secondary on Primary ECU, try it a few times since the secondary thread + // might not be ready at the moment of the first try + const int max_try = 5; + Uptane::SecondaryInterface::Ptr ip_secondary; + for (int ii = 0; ii < max_try && ip_secondary == nullptr; ++ii) { + ip_secondary = Uptane::IpUptaneSecondary::connectAndCreate("localhost", secondary_server.port()); + } + + ASSERT_TRUE(ip_secondary != nullptr) << "Failed to create IP Secondary"; + EXPECT_EQ(ip_secondary->getSerial(), secondary.getSerial()); + EXPECT_EQ(ip_secondary->getHwId(), secondary.getHwId()); + EXPECT_EQ(ip_secondary->getPublicKey(), secondary.getPublicKey()); + EXPECT_EQ(ip_secondary->getManifest(), secondary.getManifest()); + + Uptane::RawMetaPack meta_pack{"director-root", "director-target", "image_root", + "image_targets", "image_timestamp", "image_snapshot"}; + + EXPECT_TRUE(ip_secondary->putMetadata(meta_pack)); + EXPECT_TRUE(meta_pack == secondary._metapack); + + std::string firmware = "firmware"; + EXPECT_TRUE(ip_secondary->sendFirmware(firmware)); + EXPECT_EQ(firmware, secondary._data); + + EXPECT_EQ(ip_secondary->install(""), data::ResultCode::Numeric::kOk); + + secondary_server.stop(); + secondary_server_thread.join(); +} + +TEST(SecondaryTcpServer, TestIpSecondaryIfSecondaryIsNotRunning) { + in_port_t secondary_port = TestUtils::getFreePortAsInt(); + Uptane::SecondaryInterface::Ptr ip_secondary; + + // trying to connect to a non-running Secondary and create a corresponding instance on Primary + ip_secondary = Uptane::IpUptaneSecondary::connectAndCreate("localhost", secondary_port); + EXPECT_EQ(ip_secondary, nullptr); + + // create Primary's secondary without connecting to Secondary + ip_secondary = std::make_shared("localhost", secondary_port, Uptane::EcuSerial("serial"), + Uptane::HardwareIdentifier("hwid"), + PublicKey("key", KeyType::kED25519)); + + Uptane::RawMetaPack meta_pack{"director-root", "director-target", "image_root", + "image_targets", "image_timestamp", "image_snapshot"}; + + // expect failures since the secondary is not running + EXPECT_EQ(ip_secondary->getManifest(), Json::Value()); + EXPECT_FALSE(ip_secondary->sendFirmware("firmware")); + EXPECT_FALSE(ip_secondary->putMetadata(meta_pack)); + EXPECT_EQ(ip_secondary->install(""), data::ResultCode::Numeric::kInternalError); +} + +int main(int argc, char** argv) { + ::testing::InitGoogleTest(&argc, argv); + + logger_init(); + logger_set_threshold(boost::log::trivial::info); + + return RUN_ALL_TESTS(); +} diff --git a/src/aktualizr_secondary/socket_server.h b/src/aktualizr_secondary/socket_server.h deleted file mode 100644 index cba016ddda..0000000000 --- a/src/aktualizr_secondary/socket_server.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef AKTUALIZR_SECONDARY_SOCKET_SERVER_H_ -#define AKTUALIZR_SECONDARY_SOCKET_SERVER_H_ - -#include "uptane/secondaryinterface.h" -#include "utilities/utils.h" - -/** - * Listens on a socket, decodes calls and forwards them to an Uptane Secondary - * implementation - */ -class SocketServer { - public: - SocketServer(std::shared_ptr implementation, SocketHandle socket) - : impl_(std::move(implementation)), socket_(std::move(socket)) {} - - /** - * Accept connections on the socket, decode requests and respond using the - * wrapped secondary - */ - void Run(); - - void HandleOneConnection(int socket); - - private: - std::shared_ptr impl_; - SocketHandle socket_; -}; - -/** - * Create and return a server socket on the given port number - */ -SocketHandle SocketFromPort(in_port_t port); - -#endif // AKTUALIZR_SECONDARY_SOCKET_SERVER_H_ diff --git a/src/aktualizr_secondary/update_agent.h b/src/aktualizr_secondary/update_agent.h new file mode 100644 index 0000000000..5c35034a43 --- /dev/null +++ b/src/aktualizr_secondary/update_agent.h @@ -0,0 +1,28 @@ +#ifndef AKTUALIZR_SECONDARY_UPDATE_AGENT_H +#define AKTUALIZR_SECONDARY_UPDATE_AGENT_H + +#include "uptane/tuf.h" + +class UpdateAgent { + public: + using Ptr = std::shared_ptr; + + public: + virtual bool isTargetSupported(const Uptane::Target& target) const = 0; + virtual bool getInstalledImageInfo(Uptane::InstalledImageInfo& installed_image_info) const = 0; + + virtual bool download(const Uptane::Target& target, const std::string& data) = 0; + virtual data::ResultCode::Numeric install(const Uptane::Target& target) = 0; + virtual data::InstallationResult applyPendingInstall(const Uptane::Target& target) = 0; + + virtual ~UpdateAgent() = default; + + public: + UpdateAgent(const UpdateAgent&) = delete; + UpdateAgent& operator=(const UpdateAgent&) = delete; + + protected: + UpdateAgent() = default; +}; + +#endif // AKTUALIZR_SECONDARY_UPDATE_AGENT_H diff --git a/src/aktualizr_secondary/update_agent_file.cc b/src/aktualizr_secondary/update_agent_file.cc new file mode 100644 index 0000000000..38c17842a1 --- /dev/null +++ b/src/aktualizr_secondary/update_agent_file.cc @@ -0,0 +1,62 @@ +#include "update_agent_file.h" +#include "logging/logging.h" +#include "uptane/manifest.h" + +// TODO: this is an initial version of a file update on Secondary +bool FileUpdateAgent::isTargetSupported(const Uptane::Target& target) const { return target.type() != "OSTREE"; } + +bool FileUpdateAgent::getInstalledImageInfo(Uptane::InstalledImageInfo& installed_image_info) const { + installed_image_info.name = _target_name; + // TODO: proper verification, file exists, file open/read errors, etc + if (!_target_filepath.empty()) { + auto file_content = Utils::readFile(_target_filepath); + installed_image_info.len = file_content.size(); + installed_image_info.hash = Uptane::ManifestIssuer::generateVersionHashStr(file_content); + } else { + // TODO: fake package manager + const std::string fake_pacman_case = "fake_pacman"; + installed_image_info.len = fake_pacman_case.size(); + installed_image_info.hash = Uptane::ManifestIssuer::generateVersionHashStr(fake_pacman_case); + } + + return true; +} + +bool FileUpdateAgent::download(const Uptane::Target& target, const std::string& data) { + auto target_hashes = target.hashes(); + if (target_hashes.size() == 0) { + LOG_ERROR << "No hash found in the target metadata: " << target.filename(); + return false; + } + + try { + auto received_image_data_hash = Uptane::ManifestIssuer::generateVersionHash(data); + + if (!target.MatchHash(received_image_data_hash)) { + LOG_ERROR << "The received image data hash doesn't match the hash specified in the target metadata," + " hash type: " + << target_hashes[0].TypeString(); + return false; + } + + if (!_target_filepath.empty()) { + Utils::writeFile(_target_filepath, data); + } + + } catch (const std::exception& exc) { + LOG_ERROR << "Failed to generate a hash of the received image data: " << exc.what(); + return false; + } + return true; +} + +data::ResultCode::Numeric FileUpdateAgent::install(const Uptane::Target& target) { + (void)target; + return data::ResultCode::Numeric::kOk; +} + +data::InstallationResult FileUpdateAgent::applyPendingInstall(const Uptane::Target& target) { + (void)target; + return data::InstallationResult(data::ResultCode::Numeric::kInternalError, + "Applying of the pending updates are not supported by the file update agent"); +} diff --git a/src/aktualizr_secondary/update_agent_file.h b/src/aktualizr_secondary/update_agent_file.h new file mode 100644 index 0000000000..62bc4ec5dd --- /dev/null +++ b/src/aktualizr_secondary/update_agent_file.h @@ -0,0 +1,23 @@ +#ifndef AKTUALIZR_SECONDARY_UPDATE_AGENT_FILE_H +#define AKTUALIZR_SECONDARY_UPDATE_AGENT_FILE_H + +#include "update_agent.h" + +class FileUpdateAgent : public UpdateAgent { + public: + FileUpdateAgent(std::string target_name, std::string target_filepath = "") + : _target_name(std::move(target_name)), _target_filepath(std::move(target_filepath)) {} + + public: + bool isTargetSupported(const Uptane::Target& target) const override; + bool getInstalledImageInfo(Uptane::InstalledImageInfo& installed_image_info) const override; + bool download(const Uptane::Target& target, const std::string& data) override; + data::ResultCode::Numeric install(const Uptane::Target& target) override; + data::InstallationResult applyPendingInstall(const Uptane::Target& target) override; + + private: + const std::string _target_name; + const std::string _target_filepath; +}; + +#endif // AKTUALIZR_SECONDARY_UPDATE_AGENT_FILE_H diff --git a/src/aktualizr_secondary/update_agent_ostree.cc b/src/aktualizr_secondary/update_agent_ostree.cc new file mode 100644 index 0000000000..45b7cbd97d --- /dev/null +++ b/src/aktualizr_secondary/update_agent_ostree.cc @@ -0,0 +1,91 @@ +#include "update_agent_ostree.h" + +#include "package_manager/ostreemanager.h" + +// TBD: consider moving this and SotaUptaneClient::secondaryTreehubCredentials() to encapsulate them in one place that +// is shared between IP Secondary's component +static void extractCredentialsArchive(const std::string& archive, std::string* ca, std::string* cert, std::string* pkey, + std::string* treehub_server); + +bool OstreeUpdateAgent::isTargetSupported(const Uptane::Target& target) const { return target.IsOstree(); } + +bool OstreeUpdateAgent::getInstalledImageInfo(Uptane::InstalledImageInfo& installed_image_info) const { + bool result = false; + try { + installed_image_info.hash = _ostreePackMan->getCurrentHash(); + // This is the policy on a target image name in case of ostree + // The policy in followed and implied in meta-updater (garage-sign/push) and the backend + installed_image_info.name = _targetname_prefix + "-" + installed_image_info.hash; + installed_image_info.len = 0; + result = true; + } catch (const std::exception& exc) { + LOG_ERROR << "Failed to get the currently installed revision: " << exc.what(); + } + return result; +} + +bool OstreeUpdateAgent::download(const Uptane::Target& target, const std::string& data) { + std::string treehub_server; + bool download_result = false; + + try { + std::string ca, cert, pkey, server_url; + extractCredentialsArchive(data, &ca, &cert, &pkey, &server_url); + // TODO: why are qe loading this credentials at all ? + _keyMngr->loadKeys(&pkey, &cert, &ca); + boost::trim(server_url); + treehub_server = server_url; + } catch (std::runtime_error& exc) { + LOG_ERROR << exc.what(); + return false; + } + + auto install_res = OstreeManager::pull(_sysrootPath, treehub_server, *_keyMngr, target); + + switch (install_res.result_code.num_code) { + case data::ResultCode::Numeric::kOk: { + LOG_INFO << "The target revision has been successfully downloaded: " << target.sha256Hash(); + download_result = true; + break; + } + case data::ResultCode::Numeric::kAlreadyProcessed: { + LOG_INFO << "The target revision is already present on the local ostree repo: " << target.sha256Hash(); + download_result = true; + break; + } + default: { + LOG_ERROR << "Failed to download the target revision: " << target.sha256Hash() << " ( " + << install_res.result_code.toString() << " ): " << install_res.description; + } + } + + return download_result; +} + +data::ResultCode::Numeric OstreeUpdateAgent::install(const Uptane::Target& target) { + return (_ostreePackMan->install(target)).result_code.num_code; +} + +data::InstallationResult OstreeUpdateAgent::applyPendingInstall(const Uptane::Target& target) { + return _ostreePackMan->finalizeInstall(target); +} + +void extractCredentialsArchive(const std::string& archive, std::string* ca, std::string* cert, std::string* pkey, + std::string* treehub_server) { + { + std::stringstream as(archive); + *ca = Utils::readFileFromArchive(as, "ca.pem"); + } + { + std::stringstream as(archive); + *cert = Utils::readFileFromArchive(as, "client.pem"); + } + { + std::stringstream as(archive); + *pkey = Utils::readFileFromArchive(as, "pkey.pem"); + } + { + std::stringstream as(archive); + *treehub_server = Utils::readFileFromArchive(as, "server.url", true); + } +} diff --git a/src/aktualizr_secondary/update_agent_ostree.h b/src/aktualizr_secondary/update_agent_ostree.h new file mode 100644 index 0000000000..19771f64b1 --- /dev/null +++ b/src/aktualizr_secondary/update_agent_ostree.h @@ -0,0 +1,32 @@ +#ifndef AKTUALIZR_SECONDARY_UPDATE_AGENT_OSTREE_H +#define AKTUALIZR_SECONDARY_UPDATE_AGENT_OSTREE_H + +#include "update_agent.h" + +class OstreeManager; +class KeyManager; + +class OstreeUpdateAgent : public UpdateAgent { + public: + OstreeUpdateAgent(const boost::filesystem::path& sysroot_path, std::shared_ptr& key_mngr, + std::shared_ptr& ostree_pack_man, std::string targetname_prefix) + : _sysrootPath(sysroot_path), + _keyMngr(key_mngr), + _ostreePackMan(ostree_pack_man), + _targetname_prefix(std::move(targetname_prefix)) {} + + public: + bool isTargetSupported(const Uptane::Target& target) const override; + bool getInstalledImageInfo(Uptane::InstalledImageInfo& installed_image_info) const override; + bool download(const Uptane::Target& target, const std::string& data) override; + data::ResultCode::Numeric install(const Uptane::Target& target) override; + data::InstallationResult applyPendingInstall(const Uptane::Target& target) override; + + private: + const boost::filesystem::path& _sysrootPath; + std::shared_ptr _keyMngr; + std::shared_ptr _ostreePackMan; + const ::std::string _targetname_prefix; +}; + +#endif // AKTUALIZR_SECONDARY_UPDATE_AGENT_OSTREE_H diff --git a/src/aktualizr_secondary/update_test.cc b/src/aktualizr_secondary/update_test.cc deleted file mode 100644 index cb2c1a1213..0000000000 --- a/src/aktualizr_secondary/update_test.cc +++ /dev/null @@ -1,87 +0,0 @@ -#include - -#include "aktualizr_secondary.h" -#include "uptane/secondaryinterface.h" - -#include -#include - -#include "config/config.h" -#include "storage/invstorage.h" -#include "utilities/utils.h" -std::string sysroot; - -class ShortCircuitSecondary : public Uptane::SecondaryInterface { - public: - ShortCircuitSecondary(AktualizrSecondary& sec) : secondary(sec) {} - ~ShortCircuitSecondary() override = default; - - Uptane::EcuSerial getSerial() override { return secondary.getSerialResp(); } - Uptane::HardwareIdentifier getHwId() override { return secondary.getHwIdResp(); } - PublicKey getPublicKey() override { return secondary.getPublicKeyResp(); } - Json::Value getManifest() override { return secondary.getManifestResp(); } - bool putMetadata(const Uptane::RawMetaPack& meta_pack) override { return secondary.putMetadataResp(meta_pack); } - int32_t getRootVersion(bool director) override { return secondary.getRootVersionResp(director); } - bool putRoot(const std::string& root, bool director) override { return secondary.putRootResp(root, director); } - bool sendFirmware(const std::shared_ptr& data) override { return secondary.sendFirmwareResp(data); } - data::ResultCode::Numeric install(const std::string& target_name) override { - return secondary.installResp(target_name); - } - - private: - AktualizrSecondary& secondary; -}; - -TEST(aktualizr_secondary_protocol, DISABLED_manual_update) { - // secondary - TemporaryDirectory temp_dir_sec; - AktualizrSecondaryConfig config; - config.network.port = 0; - config.storage.type = StorageType::kSqlite; - config.pacman.sysroot = sysroot; - auto storage = INvStorage::newStorage(config.storage); - - AktualizrSecondary as(config, storage); - - // secondary interface - ShortCircuitSecondary sec_iface{as}; - - // storage - TemporaryDirectory temp_dir; - Utils::copyDir("tests/test_data/secondary_meta", temp_dir.Path()); - StorageConfig storage2_config; - storage2_config.path = temp_dir.Path(); - auto storage2 = INvStorage::newStorage(storage2_config); - - Uptane::RawMetaPack metadata; - EXPECT_TRUE(storage2->loadLatestRoot(&metadata.director_root, Uptane::RepositoryType::Director())); - EXPECT_TRUE( - storage2->loadNonRoot(&metadata.director_targets, Uptane::RepositoryType::Director(), Uptane::Role::Targets())); - EXPECT_TRUE(storage2->loadLatestRoot(&metadata.image_root, Uptane::RepositoryType::Image())); - EXPECT_TRUE(storage2->loadNonRoot(&metadata.image_targets, Uptane::RepositoryType::Image(), Uptane::Role::Targets())); - EXPECT_TRUE( - storage2->loadNonRoot(&metadata.image_timestamp, Uptane::RepositoryType::Image(), Uptane::Role::Timestamp())); - EXPECT_TRUE( - storage2->loadNonRoot(&metadata.image_snapshot, Uptane::RepositoryType::Image(), Uptane::Role::Snapshot())); - - std::string firmware = Utils::readFile(temp_dir.Path() / "firmware.bin"); - - EXPECT_TRUE(sec_iface.putMetadata(metadata)); - EXPECT_TRUE(sec_iface.sendFirmware(std::make_shared(firmware))); - Json::Value manifest = sec_iface.getManifest(); - - EXPECT_EQ(manifest["signed"]["installed_image"]["fileinfo"]["hashes"]["sha256"], - boost::algorithm::to_lower_copy(boost::algorithm::hex(Crypto::sha256digest(firmware)))); -} - -#ifndef __NO_MAIN__ -int main(int argc, char** argv) { - ::testing::InitGoogleTest(&argc, argv); - if (argc != 2) { - std::cerr << "Error: " << argv[0] << " requires the path to an OSTree sysroot as an input argument.\n"; - return EXIT_FAILURE; - } - sysroot = argv[1]; - return RUN_ALL_TESTS(); -} -#endif diff --git a/src/aktualizr_secondary/uptane_test.cc b/src/aktualizr_secondary/uptane_test.cc deleted file mode 100644 index 1ecaacf42d..0000000000 --- a/src/aktualizr_secondary/uptane_test.cc +++ /dev/null @@ -1,77 +0,0 @@ -#include - -#include "aktualizr_secondary.h" -#include "primary/reportqueue.h" -#include "primary/sotauptaneclient.h" - -#include "config/config.h" -#include "httpfake.h" -#include "uptane_test_common.h" - -std::shared_ptr test_storage; -AktualizrSecondaryConfig test_config; -std::string test_sysroot; - -TEST(aktualizr_secondary_uptane, getSerial) { - test_config.pacman.sysroot = test_sysroot; - AktualizrSecondary as(test_config, test_storage); - - EXPECT_NE(as.getSerialResp(), Uptane::EcuSerial("hw")); -} - -TEST(aktualizr_secondary_uptane, getHwId) { - test_config.pacman.sysroot = test_sysroot; - AktualizrSecondary as(test_config, test_storage); - - EXPECT_NE(as.getHwIdResp(), Uptane::HardwareIdentifier("")); -} - -TEST(aktualizr_secondary_uptane, getPublicKey) { - test_config.pacman.sysroot = test_sysroot; - AktualizrSecondary as(test_config, test_storage); - - EXPECT_NO_THROW(as.getPublicKeyResp()); -} - -TEST(aktualizr_secondary_uptane, credentialsPassing) { - TemporaryDirectory temp_dir; - auto http = std::make_shared(temp_dir.Path()); - Config config; - config.storage.path = temp_dir.Path(); - boost::filesystem::copy_file("tests/test_data/cred.zip", (temp_dir / "cred.zip").string()); - config.provision.provision_path = temp_dir / "cred.zip"; - config.provision.mode = ProvisionMode::kSharedCred; - config.provision.primary_ecu_serial = "testecuserial"; - config.uptane.director_server = http->tls_server + "/director"; - config.uptane.repo_server = http->tls_server + "/repo"; - config.pacman.type = PackageManager::kNone; - - auto storage = INvStorage::newStorage(config.storage); - - auto sota_client = std_::make_unique(config, storage, http); - EXPECT_NO_THROW(sota_client->initialize()); - - std::string arch = sota_client->secondaryTreehubCredentials(); - std::string ca, cert, pkey, server_url; - EXPECT_NO_THROW(extractCredentialsArchive(arch, &ca, &cert, &pkey, &server_url)); -} - -#ifndef __NO_MAIN__ -int main(int argc, char **argv) { - ::testing::InitGoogleTest(&argc, argv); - - TemporaryDirectory temp_dir; - test_config.network.port = 0; // random port - test_config.storage.path = temp_dir.Path(); - test_config.storage.type = StorageType::kSqlite; - - test_storage = INvStorage::newStorage(test_config.storage); - - if (argc != 2) { - std::cerr << "Error: " << argv[0] << " requires the path to an OSTree sysroot as an input argument.\n"; - return EXIT_FAILURE; - } - test_sysroot = argv[1]; - return RUN_ALL_TESTS(); -} -#endif diff --git a/src/cert_provider/cert_provider_test.cc b/src/cert_provider/cert_provider_test.cc index 720580fe2a..a5cd7be5d8 100644 --- a/src/cert_provider/cert_provider_test.cc +++ b/src/cert_provider/cert_provider_test.cc @@ -4,6 +4,7 @@ #include "cert_provider_test.h" #include "config/config.h" +#include "crypto/crypto.h" #include "utilities/utils.h" static boost::filesystem::path CERT_PROVIDER_PATH; diff --git a/src/libaktualizr-posix/asn1/asn1_message.cc b/src/libaktualizr-posix/asn1/asn1_message.cc index 855a85d9fb..3419ce324a 100644 --- a/src/libaktualizr-posix/asn1/asn1_message.cc +++ b/src/libaktualizr-posix/asn1/asn1_message.cc @@ -1,12 +1,10 @@ #include "asn1_message.h" +#include +#include #include "logging/logging.h" #include "utilities/dequeue_buffer.h" -#include "utilities/sockaddr_io.h" #include "utilities/utils.h" -#include -#include - #ifndef MSG_NOSIGNAL #define MSG_NOSIGNAL 0 #endif @@ -87,11 +85,12 @@ Asn1Message::Ptr Asn1Rpc(const Asn1Message::Ptr& tx, int con_fd) { } Asn1Message::Ptr Asn1Rpc(const Asn1Message::Ptr& tx, const std::pair& addr) { - Socket connection(addr.first, addr.second); + ConnectionSocket connection(addr.first, addr.second); if (connection.connect() < 0) { - LOG_ERROR << "Failed to connect to the secondary: " << std::strerror(errno); + LOG_ERROR << "Failed to connect to the secondary ( " << addr.first << ":" << addr.second + << "): " << std::strerror(errno); return Asn1Message::Empty(); } - return Asn1Rpc(tx, connection.getFD()); + return Asn1Rpc(tx, *connection); } diff --git a/src/libaktualizr-posix/ipuptanesecondary.cc b/src/libaktualizr-posix/ipuptanesecondary.cc index 68aa3ced18..dcf67351c0 100644 --- a/src/libaktualizr-posix/ipuptanesecondary.cc +++ b/src/libaktualizr-posix/ipuptanesecondary.cc @@ -10,29 +10,28 @@ namespace Uptane { -std::pair> IpUptaneSecondary::connectAndCreate( - const std::string& address, unsigned short port) { +Uptane::SecondaryInterface::Ptr IpUptaneSecondary::connectAndCreate(const std::string& address, unsigned short port) { LOG_INFO << "Connecting to and getting info about IP Secondary: " << address << ":" << port << "..."; - Socket con_sock{address, port}; + ConnectionSocket con_sock{address, port}; if (con_sock.connect() != 0) { LOG_ERROR << "Failed to connect to a secondary: " << std::strerror(errno); - return {false, std::shared_ptr()}; + return std::shared_ptr(); } LOG_INFO << "Connected to IP Secondary: " << "(" << address << ":" << port << ")"; - return create(address, port, con_sock.getFD()); + return create(address, port, *con_sock); } -std::pair> IpUptaneSecondary::create(const std::string& address, - unsigned short port, - int con_fd) { +Uptane::SecondaryInterface::Ptr IpUptaneSecondary::create(const std::string& address, unsigned short port, int con_fd) { Asn1Message::Ptr req(Asn1Message::Empty()); req->present(AKIpUptaneMes_PR_getInfoReq); + auto m = req->getInfoReq(); + auto resp = Asn1Rpc(req, con_fd); if (resp->present() != AKIpUptaneMes_PR_getInfoResp) { @@ -50,7 +49,7 @@ std::pair> IpUptaneSecondary:: LOG_INFO << "Got info on IP Secondary: " << "hw-ID: " << hw_id << " serial: " << serial; - return {true, std::make_shared(address, port, serial, hw_id, pub_key)}; + return std::make_shared(address, port, serial, hw_id, pub_key); } IpUptaneSecondary::IpUptaneSecondary(const std::string& address, unsigned short port, EcuSerial serial, @@ -84,14 +83,14 @@ bool IpUptaneSecondary::putMetadata(const RawMetaPack& meta_pack) { return r->result == AKInstallationResult_success; } -bool IpUptaneSecondary::sendFirmware(const std::shared_ptr& data) { +bool IpUptaneSecondary::sendFirmware(const std::string& data) { std::lock_guard l(install_mutex); LOG_INFO << "Sending firmware to the secondary"; Asn1Message::Ptr req(Asn1Message::Empty()); req->present(AKIpUptaneMes_PR_sendFirmwareReq); auto m = req->sendFirmwareReq(); - SetString(&m->firmware, *data); + SetString(&m->firmware, data); auto resp = Asn1Rpc(req, getAddr()); if (resp->present() != AKIpUptaneMes_PR_sendFirmwareResp) { @@ -127,7 +126,7 @@ data::ResultCode::Numeric IpUptaneSecondary::install(const std::string& target_n return static_cast(r->result); } -Json::Value IpUptaneSecondary::getManifest() { +Manifest IpUptaneSecondary::getManifest() const { LOG_INFO << "Getting the manifest key of a secondary"; Asn1Message::Ptr req(Asn1Message::Empty()); diff --git a/src/libaktualizr-posix/ipuptanesecondary.h b/src/libaktualizr-posix/ipuptanesecondary.h index b858722af9..d6095abb61 100644 --- a/src/libaktualizr-posix/ipuptanesecondary.h +++ b/src/libaktualizr-posix/ipuptanesecondary.h @@ -10,27 +10,25 @@ namespace Uptane { class IpUptaneSecondary : public SecondaryInterface { public: - static std::pair> connectAndCreate(const std::string& address, - unsigned short port); + static SecondaryInterface::Ptr connectAndCreate(const std::string& address, unsigned short port); - static std::pair> create(const std::string& address, - unsigned short port, int con_fd); + static SecondaryInterface::Ptr create(const std::string& address, unsigned short port, int con_fd); explicit IpUptaneSecondary(const std::string& address, unsigned short port, EcuSerial serial, HardwareIdentifier hw_id, PublicKey pub_key); // It looks more natural to return const EcuSerial& and const Uptane::HardwareIdentifier& // and they should be 'const' methods - EcuSerial getSerial() /*const*/ override { return serial_; }; - Uptane::HardwareIdentifier getHwId() /*const*/ override { return hw_id_; } - PublicKey getPublicKey() /*const*/ override { return pub_key_; } + EcuSerial getSerial() const override { return serial_; }; + Uptane::HardwareIdentifier getHwId() const override { return hw_id_; } + PublicKey getPublicKey() const override { return pub_key_; } bool putMetadata(const RawMetaPack& meta_pack) override; - int32_t getRootVersion(bool /* director */) override { return 0; } + int32_t getRootVersion(bool /* director */) const override { return 0; } bool putRoot(const std::string& /* root */, bool /* director */) override { return true; } - bool sendFirmware(const std::shared_ptr& data) override; + bool sendFirmware(const std::string& data) override; data::ResultCode::Numeric install(const std::string& target_name) override; - Json::Value getManifest() override; + Manifest getManifest() const override; private: const std::pair& getAddr() const { return addr_; } diff --git a/src/libaktualizr/bootloader/bootloader.cc b/src/libaktualizr/bootloader/bootloader.cc index 94b9a46e93..4578a4d732 100644 --- a/src/libaktualizr/bootloader/bootloader.cc +++ b/src/libaktualizr/bootloader/bootloader.cc @@ -7,10 +7,12 @@ #include #include "bootloader.h" +#include "storage/invstorage.h" #include "utilities/exceptions.h" #include "utilities/utils.h" -Bootloader::Bootloader(BootloaderConfig config, INvStorage& storage) : config_(std::move(config)), storage_(storage) { +Bootloader::Bootloader(BootloaderConfig config, std::shared_ptr storage) + : config_(std::move(config)), storage_(std::move(storage)) { reboot_sentinel_ = config_.reboot_sentinel_dir / config_.reboot_sentinel_name; reboot_command_ = config_.reboot_command; @@ -87,7 +89,7 @@ bool Bootloader::rebootDetected() const { bool sentinel_exists = boost::filesystem::exists(reboot_sentinel_); bool need_reboot = false; - storage_.loadNeedReboot(&need_reboot); + storage_->loadNeedReboot(&need_reboot); return need_reboot && !sentinel_exists; } @@ -100,7 +102,7 @@ void Bootloader::rebootFlagSet() { // set in storage + volatile flag Utils::writeFile(reboot_sentinel_, std::string(), false); // empty file - storage_.storeNeedReboot(); + storage_->storeNeedReboot(); } void Bootloader::rebootFlagClear() { @@ -110,7 +112,7 @@ void Bootloader::rebootFlagClear() { // clear in storage + volatile flag - storage_.clearNeedReboot(); + storage_->clearNeedReboot(); boost::filesystem::remove(reboot_sentinel_); } diff --git a/src/libaktualizr/bootloader/bootloader.h b/src/libaktualizr/bootloader/bootloader.h index 4a795958a4..2342ec8b49 100644 --- a/src/libaktualizr/bootloader/bootloader.h +++ b/src/libaktualizr/bootloader/bootloader.h @@ -3,11 +3,15 @@ #include "bootloader_config.h" -#include "storage/invstorage.h" +class INvStorage; class Bootloader { public: - Bootloader(BootloaderConfig config, INvStorage& storage); + Bootloader(BootloaderConfig config, std::shared_ptr storage); + Bootloader(const Bootloader&) = delete; + Bootloader& operator=(const Bootloader&) = delete; + + public: void setBootOK() const; void updateNotify() const; @@ -27,7 +31,7 @@ class Bootloader { private: const BootloaderConfig config_; - INvStorage& storage_; + std::shared_ptr storage_; boost::filesystem::path reboot_sentinel_; std::string reboot_command_; bool reboot_detect_supported_{false}; diff --git a/src/libaktualizr/bootloader/bootloader_test.cc b/src/libaktualizr/bootloader/bootloader_test.cc index a29b9a274a..d0f9124118 100644 --- a/src/libaktualizr/bootloader/bootloader_test.cc +++ b/src/libaktualizr/bootloader/bootloader_test.cc @@ -1,6 +1,8 @@ #include #include "bootloader.h" +#include "storage/invstorage.h" +#include "utilities/utils.h" /* Check that the reboot detection feature works */ TEST(bootloader, detectReboot) { @@ -11,7 +13,7 @@ TEST(bootloader, detectReboot) { BootloaderConfig boot_config; boot_config.reboot_sentinel_dir = temp_dir.Path(); - Bootloader bootloader(boot_config, *storage); + Bootloader bootloader(boot_config, storage); ASSERT_TRUE(bootloader.supportRebootDetection()); diff --git a/src/libaktualizr/crypto/keymanager.cc b/src/libaktualizr/crypto/keymanager.cc index e42b75a3e6..2aaeb0bd20 100644 --- a/src/libaktualizr/crypto/keymanager.cc +++ b/src/libaktualizr/crypto/keymanager.cc @@ -1,4 +1,5 @@ #include "keymanager.h" +#include "storage/invstorage.h" #include "utilities/types.h" #include @@ -231,6 +232,7 @@ Json::Value KeyManager::signTuf(const Json::Value &in_data) const { } Json::Value signature; + // TODO: FIX the hardcoded value of a signature method/algorithm signature["method"] = "rsassa-pss"; signature["sig"] = b64sig; diff --git a/src/libaktualizr/crypto/keymanager.h b/src/libaktualizr/crypto/keymanager.h index d7c43f884d..c067e197a2 100644 --- a/src/libaktualizr/crypto/keymanager.h +++ b/src/libaktualizr/crypto/keymanager.h @@ -1,19 +1,22 @@ #ifndef KEYMANAGER_H_ #define KEYMANAGER_H_ -#include "keymanager_config.h" - +#include "crypto.h" #include "http/httpinterface.h" +#include "keymanager_config.h" #include "p11engine.h" -#include "storage/invstorage.h" #include "utilities/utils.h" +class INvStorage; + class KeyManager { public: + KeyManager(std::shared_ptr backend, KeyManagerConfig config); + KeyManager(const KeyManager &) = delete; + KeyManager &operator=(const KeyManager &) = delete; // std::string RSAPSSSign(const std::string &message); // Contains the logic from HttpClient::setCerts() void copyCertsToCurl(HttpInterface &http); - KeyManager(std::shared_ptr backend, KeyManagerConfig config); void loadKeys(const std::string *pkey_content = nullptr, const std::string *cert_content = nullptr, const std::string *ca_content = nullptr); std::string getPkeyFile() const; diff --git a/src/libaktualizr/package_manager/debianmanager.h b/src/libaktualizr/package_manager/debianmanager.h index fa6ca0a76e..51699b9693 100644 --- a/src/libaktualizr/package_manager/debianmanager.h +++ b/src/libaktualizr/package_manager/debianmanager.h @@ -9,9 +9,9 @@ class DebianManager : public PackageManagerInterface { public: - DebianManager(PackageConfig pconfig, BootloaderConfig bconfig, std::shared_ptr storage, - std::shared_ptr http) - : PackageManagerInterface(std::move(pconfig), std::move(bconfig), std::move(storage), std::move(http)) {} + DebianManager(const PackageConfig &pconfig, const BootloaderConfig &bconfig, + const std::shared_ptr &storage, const std::shared_ptr &http) + : PackageManagerInterface(pconfig, bconfig, storage, http) {} ~DebianManager() override = default; std::string name() const override { return "debian"; } Json::Value getInstalledPackages() const override; diff --git a/src/libaktualizr/package_manager/dockerappmanager.h b/src/libaktualizr/package_manager/dockerappmanager.h index 3a66bfe831..79eed29225 100644 --- a/src/libaktualizr/package_manager/dockerappmanager.h +++ b/src/libaktualizr/package_manager/dockerappmanager.h @@ -8,9 +8,9 @@ using DockerAppCb = std::function storage, - std::shared_ptr http) - : OstreeManager(std::move(pconfig), std::move(bconfig), std::move(storage), std::move(http)) { + DockerAppManager(const PackageConfig &pconfig, const BootloaderConfig &bconfig, + const std::shared_ptr &storage, const std::shared_ptr &http) + : OstreeManager(pconfig, bconfig, storage, http) { fake_fetcher_ = std::make_shared("", "", http_); } bool fetchTarget(const Uptane::Target &target, Uptane::Fetcher &fetcher, const KeyManager &keys, diff --git a/src/libaktualizr/package_manager/ostreemanager.cc b/src/libaktualizr/package_manager/ostreemanager.cc index 020407920e..a255300d66 100644 --- a/src/libaktualizr/package_manager/ostreemanager.cc +++ b/src/libaktualizr/package_manager/ostreemanager.cc @@ -224,9 +224,9 @@ data::InstallationResult OstreeManager::finalizeInstall(const Uptane::Target &ta return data::InstallationResult(data::ResultCode::Numeric::kOk, "Successfully booted on new version"); } -OstreeManager::OstreeManager(PackageConfig pconfig, BootloaderConfig bconfig, std::shared_ptr storage, - std::shared_ptr http) - : PackageManagerInterface(std::move(pconfig), std::move(bconfig), std::move(storage), std::move(http)) { +OstreeManager::OstreeManager(PackageConfig pconfig, std::shared_ptr bootloader, + std::shared_ptr storage, std::shared_ptr http) + : PackageManagerInterface(std::move(pconfig), std::move(bootloader), std::move(storage), std::move(http)) { GObjectUniquePtr sysroot_smart = OstreeManager::LoadSysroot(config.sysroot); if (sysroot_smart == nullptr) { throw std::runtime_error("Could not find OSTree sysroot at: " + config.sysroot.string()); diff --git a/src/libaktualizr/package_manager/ostreemanager.h b/src/libaktualizr/package_manager/ostreemanager.h index b86a0a5eb1..4e3d907346 100644 --- a/src/libaktualizr/package_manager/ostreemanager.h +++ b/src/libaktualizr/package_manager/ostreemanager.h @@ -39,7 +39,11 @@ struct PullMetaStruct { class OstreeManager : public PackageManagerInterface { public: - OstreeManager(PackageConfig pconfig, BootloaderConfig bconfig, std::shared_ptr storage, + OstreeManager(const PackageConfig &pconfig, const BootloaderConfig &bconfig, + const std::shared_ptr &storage, const std::shared_ptr &http) + : OstreeManager(pconfig, std::make_shared(bconfig, storage), storage, http) {} + + OstreeManager(PackageConfig pconfig, std::shared_ptr bootloader, std::shared_ptr storage, std::shared_ptr http); ~OstreeManager() override = default; std::string name() const override { return "ostree"; } diff --git a/src/libaktualizr/package_manager/packagemanagerconfig.cc b/src/libaktualizr/package_manager/packagemanagerconfig.cc index ef4db566e2..a85a27075b 100644 --- a/src/libaktualizr/package_manager/packagemanagerconfig.cc +++ b/src/libaktualizr/package_manager/packagemanagerconfig.cc @@ -28,6 +28,8 @@ std::ostream& operator<<(std::ostream& os, PackageManager pm) { void PackageConfig::updateFromPropertyTree(const boost::property_tree::ptree& pt) { CopyFromConfig(type, "type", pt); + CopyFromConfig(filepath, "filepath", pt); + CopyFromConfig(target_name, "target_name", pt); CopyFromConfig(os, "os", pt); CopyFromConfig(sysroot, "sysroot", pt); CopyFromConfig(ostree_server, "ostree_server", pt); @@ -49,6 +51,8 @@ void PackageConfig::updateFromPropertyTree(const boost::property_tree::ptree& pt void PackageConfig::writeToStream(std::ostream& out_stream) const { writeOption(out_stream, type, "type"); + writeOption(out_stream, filepath, "filepath"); + writeOption(out_stream, target_name, "target_name"); writeOption(out_stream, os, "os"); writeOption(out_stream, sysroot, "sysroot"); writeOption(out_stream, ostree_server, "ostree_server"); diff --git a/src/libaktualizr/package_manager/packagemanagerconfig.h b/src/libaktualizr/package_manager/packagemanagerconfig.h index cb6db7401e..187d8b17bc 100644 --- a/src/libaktualizr/package_manager/packagemanagerconfig.h +++ b/src/libaktualizr/package_manager/packagemanagerconfig.h @@ -16,6 +16,10 @@ struct PackageConfig { #else PackageManager type{PackageManager::kNone}; #endif + // optional, if not specified nothing is updated, just image data are verified and delivered + boost::filesystem::path filepath; + // optional, if not specified any target can be applied + std::string target_name; std::string os; boost::filesystem::path sysroot; std::string ostree_server; diff --git a/src/libaktualizr/package_manager/packagemanagerfake.h b/src/libaktualizr/package_manager/packagemanagerfake.h index 9245e73453..3b42514a05 100644 --- a/src/libaktualizr/package_manager/packagemanagerfake.h +++ b/src/libaktualizr/package_manager/packagemanagerfake.h @@ -8,9 +8,9 @@ class PackageManagerFake : public PackageManagerInterface { public: - PackageManagerFake(PackageConfig pconfig, BootloaderConfig bconfig, std::shared_ptr storage, - std::shared_ptr http) - : PackageManagerInterface(std::move(pconfig), std::move(bconfig), std::move(storage), std::move(http)) {} + PackageManagerFake(const PackageConfig &pconfig, const BootloaderConfig &bconfig, + const std::shared_ptr &storage, const std::shared_ptr &http) + : PackageManagerInterface(pconfig, bconfig, storage, http) {} ~PackageManagerFake() override = default; std::string name() const override { return "fake"; } Json::Value getInstalledPackages() const override; diff --git a/src/libaktualizr/package_manager/packagemanagerfake_test.cc b/src/libaktualizr/package_manager/packagemanagerfake_test.cc index d085a9ed57..9c606ff8dc 100644 --- a/src/libaktualizr/package_manager/packagemanagerfake_test.cc +++ b/src/libaktualizr/package_manager/packagemanagerfake_test.cc @@ -77,7 +77,7 @@ TEST(PackageManagerFake, FinalizeAfterReboot) { config.bootloader.reboot_sentinel_dir = temp_dir.Path(); config.storage.path = temp_dir.Path(); std::shared_ptr storage = INvStorage::newStorage(config.storage); - std::shared_ptr bootloader = std::make_shared(config.bootloader, *storage); + std::shared_ptr bootloader = std::make_shared(config.bootloader, storage); PackageManagerFake fakepm(config.pacman, config.bootloader, storage, nullptr); diff --git a/src/libaktualizr/package_manager/packagemanagerinterface.h b/src/libaktualizr/package_manager/packagemanagerinterface.h index e8ce4b5f0d..4aab9882da 100644 --- a/src/libaktualizr/package_manager/packagemanagerinterface.h +++ b/src/libaktualizr/package_manager/packagemanagerinterface.h @@ -35,12 +35,16 @@ enum class TargetStatus { class PackageManagerInterface { public: - PackageManagerInterface(PackageConfig pconfig, BootloaderConfig bconfig, std::shared_ptr storage, - std::shared_ptr http) + PackageManagerInterface(const PackageConfig& pconfig, const BootloaderConfig& bconfig, + const std::shared_ptr& storage, const std::shared_ptr& http) + : PackageManagerInterface(pconfig, std::make_shared(bconfig, storage), storage, http) {} + + PackageManagerInterface(PackageConfig pconfig, std::shared_ptr bootloader, + std::shared_ptr storage, std::shared_ptr http) : config(std::move(pconfig)), storage_(std::move(storage)), http_(std::move(http)), - bootloader_{new Bootloader(std::move(bconfig), *storage_)} {} + bootloader_(std::move(bootloader)) {} virtual ~PackageManagerInterface() = default; virtual std::string name() const = 0; virtual Json::Value getInstalledPackages() const = 0; @@ -55,27 +59,10 @@ class PackageManagerInterface { FetcherProgressCb progress_cb, const api::FlowControlToken* token); virtual TargetStatus verifyTarget(const Uptane::Target& target) const; - // only returns the version - Json::Value getManifest(const Uptane::EcuSerial& ecu_serial) const { - Uptane::Target installed_target = getCurrent(); - Json::Value installed_image; - installed_image["filepath"] = installed_target.filename(); - installed_image["fileinfo"]["length"] = Json::UInt64(installed_target.length()); - installed_image["fileinfo"]["hashes"]["sha256"] = installed_target.sha256Hash(); - - Json::Value unsigned_ecu_version; - unsigned_ecu_version["attacks_detected"] = ""; - unsigned_ecu_version["installed_image"] = installed_image; - unsigned_ecu_version["ecu_serial"] = ecu_serial.ToString(); - unsigned_ecu_version["previous_timeserver_time"] = "1970-01-01T00:00:00Z"; - unsigned_ecu_version["timeserver_time"] = "1970-01-01T00:00:00Z"; - return unsigned_ecu_version; - } - protected: PackageConfig config; std::shared_ptr storage_; std::shared_ptr http_; - std::unique_ptr bootloader_; + std::shared_ptr bootloader_; }; #endif // PACKAGEMANAGERINTERFACE_H_ diff --git a/src/libaktualizr/primary/initializer.cc b/src/libaktualizr/primary/initializer.cc index eac47c1c21..d8449ed457 100644 --- a/src/libaktualizr/primary/initializer.cc +++ b/src/libaktualizr/primary/initializer.cc @@ -8,6 +8,7 @@ #include "bootstrap/bootstrap.h" #include "crypto/keymanager.h" #include "logging/logging.h" +#include "storage/invstorage.h" // Postcondition: device_id is in the storage bool Initializer::initDeviceId() { diff --git a/src/libaktualizr/primary/sotauptaneclient.cc b/src/libaktualizr/primary/sotauptaneclient.cc index e8ebf55c3e..3cd22830f6 100644 --- a/src/libaktualizr/primary/sotauptaneclient.cc +++ b/src/libaktualizr/primary/sotauptaneclient.cc @@ -49,7 +49,7 @@ void SotaUptaneClient::addSecondary(const std::shared_ptrgetCurrent()); } return false; @@ -86,7 +86,7 @@ void SotaUptaneClient::finalizeAfterReboot() { std::vector updates; unsigned int ecus_count = 0; if (uptaneOfflineIteration(&updates, &ecus_count)) { - const Uptane::EcuSerial &ecu_serial = uptane_manifest.getPrimaryEcuSerial(); + const Uptane::EcuSerial &ecu_serial = primaryEcuSerial(); std::vector installed_versions; boost::optional pending_target; @@ -124,7 +124,7 @@ void SotaUptaneClient::finalizeAfterReboot() { data::InstallationResult SotaUptaneClient::PackageInstallSetResult(const Uptane::Target &target) { data::InstallationResult result; - Uptane::EcuSerial ecu_serial = uptane_manifest.getPrimaryEcuSerial(); + Uptane::EcuSerial ecu_serial = primaryEcuSerial(); // This is to recover more gracefully if the install process was interrupted // but ends up booting the new version anyway (e.g: ostree finished @@ -195,36 +195,32 @@ void SotaUptaneClient::reportAktualizrConfiguration() { Json::Value SotaUptaneClient::AssembleManifest() { Json::Value manifest; // signed top-level - Uptane::EcuSerial primary_ecu_serial = uptane_manifest.getPrimaryEcuSerial(); + Uptane::EcuSerial primary_ecu_serial = primaryEcuSerial(); manifest["primary_ecu_serial"] = primary_ecu_serial.ToString(); // first part: report current version/state of all ecus Json::Value version_manifest; - Json::Value primary_ecu_version = package_manager_->getManifest(primary_ecu_serial); + Json::Value primary_manifest = uptane_manifest->assembleManifest(package_manager_->getCurrent()); std::vector> ecu_cnt; + std::string report_counter; if (!storage->loadEcuReportCounter(&ecu_cnt) || (ecu_cnt.size() == 0)) { LOG_ERROR << "No ECU version report counter, please check the database!"; + // should we send manifest at all in this case ? } else { - primary_ecu_version["report_counter"] = std::to_string(ecu_cnt[0].second + 1); + report_counter = std::to_string(ecu_cnt[0].second + 1); storage->saveEcuReportCounter(ecu_cnt[0].first, ecu_cnt[0].second + 1); } - version_manifest[primary_ecu_serial.ToString()] = uptane_manifest.signManifest(primary_ecu_version); + version_manifest[primary_ecu_serial.ToString()] = uptane_manifest->sign(primary_manifest, report_counter); for (auto it = secondaries.begin(); it != secondaries.end(); it++) { - Json::Value secmanifest = it->second->getManifest(); - if (secmanifest.isMember("signatures") && secmanifest.isMember("signed")) { - const auto public_key = it->second->getPublicKey(); - const std::string canonical = Utils::jsonToCanonicalStr(secmanifest["signed"]); - const bool verified = public_key.VerifySignature(secmanifest["signatures"][0]["sig"].asString(), canonical); - - if (verified) { - version_manifest[it->first.ToString()] = secmanifest; - } else { - LOG_ERROR << "Secondary manifest verification failed, manifest: " << secmanifest; - } + Uptane::Manifest secmanifest = it->second->getManifest(); + + if (secmanifest.verifySignature(it->second->getPublicKey())) { + version_manifest[it->first.ToString()] = secmanifest; } else { - LOG_ERROR << "Secondary manifest is corrupted or not signed, manifest: " << secmanifest; + // should we report to BE about it ? + LOG_ERROR << "Secondary manifest is corrupted or not signed, or signature is invalid manifest: " << secmanifest; } } manifest["ecu_version_manifests"] = version_manifest; @@ -268,8 +264,8 @@ bool SotaUptaneClient::hasPendingUpdates() const { return storage->hasPendingIns void SotaUptaneClient::initialize() { LOG_DEBUG << "Checking if device is provisioned..."; - KeyManager keys(storage, config.keymanagerConfig()); - Initializer initializer(config.provision, storage, http, keys, secondaries); + auto keys = std::make_shared(storage, config.keymanagerConfig()); + Initializer initializer(config.provision, storage, http, *keys, secondaries); if (!initializer.isSuccessful()) { throw std::runtime_error("Fatal error during provisioning or ECU device registration."); @@ -280,7 +276,8 @@ void SotaUptaneClient::initialize() { throw std::runtime_error("Unable to load ECU serials after device registration."); } - uptane_manifest.setPrimaryEcuSerialHwId(serials[0]); + uptane_manifest = std::make_shared(keys, serials[0].first); + primary_ecu_serial_ = serials[0].first; hw_ids.insert(serials[0]); verifySecondaries(); @@ -395,7 +392,7 @@ void SotaUptaneClient::computeDeviceInstallationResult(data::InstallationResult bool SotaUptaneClient::getNewTargets(std::vector *new_targets, unsigned int *ecus_count) { std::vector targets = director_repo.getTargets().targets; - Uptane::EcuSerial primary_ecu_serial = uptane_manifest.getPrimaryEcuSerial(); + const Uptane::EcuSerial &primary_ecu_serial = primaryEcuSerial(); if (ecus_count != nullptr) { *ecus_count = 0; } @@ -583,7 +580,7 @@ std::pair SotaUptaneClient::downloadImage(const Uptane::Ta report_queue->enqueue(std_::make_unique(ecu.first, correlation_id)); } - Uptane::EcuSerial primary_ecu_serial = uptane_manifest.getPrimaryEcuSerial(); + const Uptane::EcuSerial &primary_ecu_serial = primaryEcuSerial(); bool success = false; if (target.IsForEcu(primary_ecu_serial) || !target.IsOstree()) { @@ -710,42 +707,13 @@ result::UpdateCheck SotaUptaneClient::fetchMeta() { reportNetworkInfo(); if (hasPendingUpdates()) { - LOG_INFO << "An update is pending. Check if pending ECUs has been already updated"; - - std::vector> pending_ecus; - storage->getPendingEcus(&pending_ecus); - const auto &primary_ecu_serial = uptane_manifest.getPrimaryEcuSerial(); - - for (const auto &pending_ecu : pending_ecus) { - if (primary_ecu_serial == pending_ecu.first) { - continue; - } - auto &sec = secondaries[pending_ecu.first]; - auto manifest = sec->getManifest(); - const std::string man_body = Utils::jsonToCanonicalStr(manifest["signed"]); - auto current_ecu_hash = manifest["signed"]["installed_image"]["fileinfo"]["hashes"]["sha256"]; - LOG_INFO << "Current secondary hash: " << current_ecu_hash; - LOG_INFO << "Pending secondary hash: " << pending_ecu.second.HashString(); - - if (pending_ecu.second == Uptane::Hash(Uptane::Hash::Type::kSha256, current_ecu_hash.asString())) { - LOG_INFO << "The pending update has been installed: " << current_ecu_hash; - boost::optional pending_version; - if (storage->loadInstalledVersions(pending_ecu.first.ToString(), nullptr, &pending_version)) { - storage->saveEcuInstallationResult(pending_ecu.first, - data::InstallationResult(data::ResultCode::Numeric::kOk, "")); - - storage->saveInstalledVersion(pending_ecu.first.ToString(), *pending_version, - InstalledVersionUpdateMode::kCurrent); - - report_queue->enqueue(std_::make_unique( - pending_ecu.first, director_repo.getCorrelationId(), true)); - computeDeviceInstallationResult(nullptr, director_repo.getCorrelationId()); - } - } - } + // if there are some pending updates check if the secondaries' pending updates have been applied + LOG_INFO << "The current update is pending. Check if pending ECUs has been already updated"; + checkAndUpdatePendingSecondaries(); } if (hasPendingUpdates()) { + // if there are still some pending updates just return, don't check for new updates // no need in update checking if there are some pending updates LOG_INFO << "An update is pending. Skipping check for update until installation is complete."; return result::UpdateCheck({}, 0, result::UpdateStatus::kError, Json::nullValue, @@ -881,11 +849,16 @@ result::Install SotaUptaneClient::uptaneInstall(const std::vectorverifyTarget(update) != TargetStatus::kGood) { result.dev_report = {false, data::ResultCode::Numeric::kInternalError, ""}; storage->storeDeviceInstallationResult(result.dev_report, "Downloaded target is invalid", correlation_id); @@ -937,12 +910,11 @@ result::Install SotaUptaneClient::uptaneInstall(const std::vectorsaveEcuInstallationResult(primary_ecu_serial, install_res); // TODO: distinguish this case from regular failure for local and remote // event reporting - report_queue->enqueue(std_::make_unique(uptane_manifest.getPrimaryEcuSerial(), - correlation_id, false)); - sendEvent(uptane_manifest.getPrimaryEcuSerial(), false); + report_queue->enqueue( + std_::make_unique(primaryEcuSerial(), correlation_id, false)); + sendEvent(primaryEcuSerial(), false); } - result.ecu_reports.emplace(result.ecu_reports.begin(), primary_update, uptane_manifest.getPrimaryEcuSerial(), - install_res); + result.ecu_reports.emplace(result.ecu_reports.begin(), primary_update, primaryEcuSerial(), install_res); // TODO: other updates for primary } else { LOG_INFO << "No update to install on primary"; @@ -1014,7 +986,7 @@ bool SotaUptaneClient::putManifestSimple(const Json::Value &custom) { if (custom != Json::nullValue) { manifest["custom"] = custom; } - auto signed_manifest = uptane_manifest.signManifest(manifest); + auto signed_manifest = uptane_manifest->sign(manifest); HttpResponse response = http->put(config.uptane.director_server + "/manifest", signed_manifest); if (response.isOk()) { if (!connected) { @@ -1048,13 +1020,12 @@ void SotaUptaneClient::verifySecondaries() { std::vector misconfigured_ecus; std::vector found(serials.size(), false); - SerialCompare primary_comp(uptane_manifest.getPrimaryEcuSerial()); + SerialCompare primary_comp(primaryEcuSerial()); EcuSerials::const_iterator store_it; store_it = std::find_if(serials.cbegin(), serials.cend(), primary_comp); if (store_it == serials.cend()) { - LOG_ERROR << "Primary ECU serial " << uptane_manifest.getPrimaryEcuSerial() << " not found in storage!"; - misconfigured_ecus.emplace_back(uptane_manifest.getPrimaryEcuSerial(), Uptane::HardwareIdentifier(""), - EcuState::kOld); + LOG_ERROR << "Primary ECU serial " << primaryEcuSerial() << " not found in storage!"; + misconfigured_ecus.emplace_back(primaryEcuSerial(), Uptane::HardwareIdentifier(""), EcuState::kOld); } else { found[static_cast(std::distance(serials.cbegin(), store_it))] = true; } @@ -1174,19 +1145,34 @@ void SotaUptaneClient::sendMetadataToEcus(const std::vector &tar } std::future SotaUptaneClient::sendFirmwareAsync(Uptane::SecondaryInterface &secondary, - const std::shared_ptr &data, - const std::string &filename) { - auto f = [this, &secondary, data, filename]() { + const Uptane::Target &target) { + auto f = [this, &secondary, target]() { const std::string &correlation_id = director_repo.getCorrelationId(); + sendEvent(secondary.getSerial()); report_queue->enqueue(std_::make_unique(secondary.getSerial(), correlation_id)); - bool send_firmware_result = secondary.sendFirmware(data); + std::string data_to_send; + bool send_firmware_result = false; + + if (target.IsOstree()) { + // empty firmware means OSTree secondaries: pack credentials instead + data_to_send = secondaryTreehubCredentials(); + } else { + std::stringstream sstr; + sstr << *storage->openTargetFile(target); + data_to_send = sstr.str(); + } + + if (!data_to_send.empty()) { + send_firmware_result = secondary.sendFirmware(data_to_send); + } + data::ResultCode::Numeric result = send_firmware_result ? data::ResultCode::Numeric::kOk : data::ResultCode::Numeric::kInstallFailed; if (send_firmware_result) { - result = secondary.install(filename); + result = secondary.install(target.filename()); } if (result == data::ResultCode::Numeric::kNeedCompletion) { @@ -1195,6 +1181,7 @@ std::future SotaUptaneClient::sendFirmwareAsync(Uptan report_queue->enqueue(std_::make_unique( secondary.getSerial(), correlation_id, result == data::ResultCode::Numeric::kOk)); } + sendEvent(secondary.getSerial(), result == data::ResultCode::Numeric::kOk); return result; }; @@ -1206,7 +1193,7 @@ std::vector SotaUptaneClient::sendImagesToEcus(const std::vector reports; std::vector>> firmwareFutures; - Uptane::EcuSerial primary_ecu_serial = uptane_manifest.getPrimaryEcuSerial(); + const Uptane::EcuSerial &primary_ecu_serial = primaryEcuSerial(); // target images should already have been downloaded to metadata_path/targets/ for (auto targets_it = targets.cbegin(); targets_it != targets.cend(); ++targets_it) { for (auto ecus_it = targets_it->ecus().cbegin(); ecus_it != targets_it->ecus().cend(); ++ecus_it) { @@ -1222,40 +1209,32 @@ std::vector SotaUptaneClient::sendImagesToEcus(const last_exception = Uptane::BadEcuId(targets_it->filename()); continue; } - Uptane::SecondaryInterface &sec = *f->second; - if (targets_it->IsOstree()) { - // empty firmware means OSTree secondaries: pack credentials instead - const std::string creds_archive = secondaryTreehubCredentials(); - if (creds_archive.empty()) { - continue; - } - firmwareFutures.emplace_back( - result::Install::EcuReport(*targets_it, ecu_serial, data::InstallationResult()), - sendFirmwareAsync(sec, std::make_shared(creds_archive), (*targets_it).filename())); - } else { - std::stringstream sstr; - sstr << *storage->openTargetFile(*targets_it); - const std::string fw = sstr.str(); - firmwareFutures.emplace_back( - result::Install::EcuReport(*targets_it, ecu_serial, data::InstallationResult()), - sendFirmwareAsync(sec, std::make_shared(fw), (*targets_it).filename())); - } + Uptane::SecondaryInterface &sec = *f->second; + firmwareFutures.emplace_back(result::Install::EcuReport(*targets_it, ecu_serial, data::InstallationResult()), + sendFirmwareAsync(sec, *targets_it)); } } for (auto &f : firmwareFutures) { - // failure + data::ResultCode::Numeric fut_result = data::ResultCode::Numeric::kUnknown; + + fut_result = f.second.get(); + f.first.install_res = data::InstallationResult(fut_result, ""); + if (fiu_fail((std::string("secondary_install_") + f.first.serial.ToString()).c_str()) != 0) { - f.first.install_res = data::InstallationResult( - data::ResultCode(data::ResultCode::Numeric::kInstallFailed, fault_injection_last_info()), ""); - storage->saveEcuInstallationResult(f.first.serial, f.first.install_res); - reports.push_back(f.first); - continue; + // consider changing this approach of the fault injection, since the current approach impacts the non-test code + // flow here as well as it doesn't test the installation failure on secondary from an end-to-end perspective as it + // injects an error on the middle of the control flow that would have happened if an installation error had + // happened in case of the virtual or the ip-secondary or any other secondary, e.g. add a mock secondary that + // returns an error to sendFirmware/install request we might consider passing the installation description message + // from Secondary, not just bool and/or data::ResultCode::Numeric + + // fault injection + fut_result = data::ResultCode::Numeric::kInstallFailed; + f.first.install_res = data::InstallationResult(data::ResultCode(fut_result, fault_injection_last_info()), ""); } - auto fut_result = f.second.get(); - f.first.install_res = data::InstallationResult(fut_result, ""); if (fut_result == data::ResultCode::Numeric::kOk) { storage->saveInstalledVersion(f.first.serial.ToString(), f.first.update, InstalledVersionUpdateMode::kCurrent); } else if (fut_result == data::ResultCode::Numeric::kNeedCompletion) { @@ -1299,3 +1278,39 @@ std::string SotaUptaneClient::secondaryTreehubCredentials() const { Uptane::LazyTargetsList SotaUptaneClient::allTargets() const { return Uptane::LazyTargetsList(images_repo, storage, uptane_fetcher); } + +void SotaUptaneClient::checkAndUpdatePendingSecondaries() { + // TODO: think of another alternatives to inform Primary about installation result on Secondary ECUs (reboot case) + std::vector> pending_ecus; + storage->getPendingEcus(&pending_ecus); + + for (const auto &pending_ecu : pending_ecus) { + if (primaryEcuSerial() == pending_ecu.first) { + continue; + } + auto &sec = secondaries[pending_ecu.first]; + const auto &manifest = sec->getManifest(); + if (!manifest.verifySignature(sec->getPublicKey())) { + LOG_ERROR << "Invalid signature of the manifest reported by secondary: " + << " serial: " << pending_ecu.first << " manifest: " << manifest; + // what should we do in this case ? + continue; + } + auto current_ecu_hash = manifest.installedImageHash(); + if (pending_ecu.second == current_ecu_hash) { + LOG_INFO << "The pending update " << current_ecu_hash << " has been installed on " << pending_ecu.first; + boost::optional pending_version; + if (storage->loadInstalledVersions(pending_ecu.first.ToString(), nullptr, &pending_version)) { + storage->saveEcuInstallationResult(pending_ecu.first, + data::InstallationResult(data::ResultCode::Numeric::kOk, "")); + + storage->saveInstalledVersion(pending_ecu.first.ToString(), *pending_version, + InstalledVersionUpdateMode::kCurrent); + + report_queue->enqueue(std_::make_unique( + pending_ecu.first, director_repo.getCorrelationId(), true)); + computeDeviceInstallationResult(nullptr, director_repo.getCorrelationId()); + } + } + } +} diff --git a/src/libaktualizr/primary/sotauptaneclient.h b/src/libaktualizr/primary/sotauptaneclient.h index 67174cdf44..89a80564d9 100644 --- a/src/libaktualizr/primary/sotauptaneclient.h +++ b/src/libaktualizr/primary/sotauptaneclient.h @@ -26,26 +26,26 @@ #include "uptane/fetcher.h" #include "uptane/imagesrepository.h" #include "uptane/iterator.h" +#include "uptane/manifest.h" #include "uptane/secondaryinterface.h" class SotaUptaneClient { public: - SotaUptaneClient(Config &config_in, const std::shared_ptr &storage_in, - std::shared_ptr http_in, std::shared_ptr events_channel_in) + SotaUptaneClient(Config &config_in, std::shared_ptr &storage_in, std::shared_ptr http_in, + std::shared_ptr events_channel_in) : config(config_in), - uptane_manifest(config, storage_in), storage(storage_in), http(std::move(http_in)), package_manager_(PackageManagerFactory::makePackageManager(config.pacman, config.bootloader, storage, http)), uptane_fetcher(new Uptane::Fetcher(config, http)), report_queue(new ReportQueue(config, http)), - events_channel(std::move(events_channel_in)) {} + events_channel(std::move(events_channel_in)), + primary_ecu_serial_{Uptane::EcuSerial::Unknown()} {} - SotaUptaneClient(Config &config_in, const std::shared_ptr &storage_in, - std::shared_ptr http_in) + SotaUptaneClient(Config &config_in, std::shared_ptr &storage_in, std::shared_ptr http_in) : SotaUptaneClient(config_in, storage_in, std::move(http_in), nullptr) {} - SotaUptaneClient(Config &config_in, const std::shared_ptr &storage_in) + SotaUptaneClient(Config &config_in, std::shared_ptr storage_in) : SotaUptaneClient(config_in, storage_in, std::make_shared()) {} void initialize(); @@ -129,8 +129,7 @@ class SotaUptaneClient { void verifySecondaries(); void sendMetadataToEcus(const std::vector &targets); std::future sendFirmwareAsync(Uptane::SecondaryInterface &secondary, - const std::shared_ptr &data, - const std::string &filename); + const Uptane::Target &target); std::vector sendImagesToEcus(const std::vector &targets); bool putManifestSimple(const Json::Value &custom = Json::nullValue); @@ -144,7 +143,8 @@ class SotaUptaneClient { std::unique_ptr findTargetHelper(const Uptane::Targets &cur_targets, const Uptane::Target &queried_target, int level, bool terminating, bool offline); - + void checkAndUpdatePendingSecondaries(); + const Uptane::EcuSerial &primaryEcuSerial() const { return primary_ecu_serial_; } template void sendEvent(Args &&... args) { std::shared_ptr event = std::make_shared(std::forward(args)...); @@ -158,7 +158,7 @@ class SotaUptaneClient { Config &config; Uptane::DirectorRepository director_repo; Uptane::ImagesRepository images_repo; - Uptane::Manifest uptane_manifest; + Uptane::ManifestIssuer::Ptr uptane_manifest; std::shared_ptr storage; std::shared_ptr http; std::shared_ptr package_manager_; @@ -173,6 +173,7 @@ class SotaUptaneClient { // ecu_serial => secondary* std::map> secondaries; std::mutex download_mutex; + mutable Uptane::EcuSerial primary_ecu_serial_; }; class TargetCompare { diff --git a/src/libaktualizr/uptane/CMakeLists.txt b/src/libaktualizr/uptane/CMakeLists.txt index 825d403b63..07d5d82894 100644 --- a/src/libaktualizr/uptane/CMakeLists.txt +++ b/src/libaktualizr/uptane/CMakeLists.txt @@ -7,7 +7,8 @@ set(SOURCES tuf.cc uptanerepository.cc directorrepository.cc - imagesrepository.cc) + imagesrepository.cc + manifest.cc) set(HEADERS exceptions.h @@ -17,7 +18,8 @@ set(HEADERS tuf.h uptanerepository.h directorrepository.h - imagesrepository.h) + imagesrepository.h + manifest.h) add_library(uptane OBJECT ${SOURCES}) diff --git a/src/libaktualizr/uptane/directorrepository.cc b/src/libaktualizr/uptane/directorrepository.cc index 487fafe668..b9ff98e272 100644 --- a/src/libaktualizr/uptane/directorrepository.cc +++ b/src/libaktualizr/uptane/directorrepository.cc @@ -142,7 +142,7 @@ void DirectorRepository::dropTargets(INvStorage& storage) { bool DirectorRepository::matchTargetsWithImageTargets(const Uptane::Targets& image_targets) const { // step 10 of https://uptane.github.io/papers/ieee-isto-6100.1.0.0.uptane-standard.html#rfc.section.5.4.4.2 // TBD: no delegation support, consider reusing of findTargetInDelegationTree() - // that needs to be moved into a common place to be resued by Primary and Secondary + // TODO: reuse this verification at Primary, require some refactoring of Primary/sotauptaneclient const auto& image_target_array = image_targets.targets; const auto& director_target_array = targets.targets; diff --git a/src/libaktualizr/uptane/imagesrepository.h b/src/libaktualizr/uptane/imagesrepository.h index 8ad4a33af7..572465a982 100644 --- a/src/libaktualizr/uptane/imagesrepository.h +++ b/src/libaktualizr/uptane/imagesrepository.h @@ -1,9 +1,6 @@ #ifndef IMAGES_REPOSITORY_H_ #define IMAGES_REPOSITORY_H_ -#include -#include - #include "uptanerepository.h" namespace Uptane { @@ -15,28 +12,29 @@ class ImagesRepository : public RepositoryCommon { ImagesRepository() : RepositoryCommon(RepositoryType::Image()) {} Exception getLastException() const { return last_exception; } - - static std::shared_ptr verifyDelegation(const std::string& delegation_raw, const Uptane::Role& role, - const Targets& parent_target); std::shared_ptr getTargets() const { return targets; } bool verifyRoleHashes(const std::string& role_data, const Uptane::Role& role) const; int getRoleVersion(const Uptane::Role& role) const; int64_t getRoleSize(const Uptane::Role& role) const; - - bool checkMetaOffline(INvStorage& storage); + static std::shared_ptr verifyDelegation(const std::string& delegation_raw, const Uptane::Role& role, + const Targets& parent_target); bool updateMeta(INvStorage& storage, const IMetadataFetcher& fetcher) override; + bool checkMetaOffline(INvStorage& storage); private: - void resetMeta(); - bool verifyTargets(const std::string& targets_raw); bool targetsExpired() { return targets->isExpired(TimeStamp::Now()); } - bool verifyTimestamp(const std::string& timestamp_raw); bool timestampExpired() { return timestamp.isExpired(TimeStamp::Now()); } - bool verifySnapshot(const std::string& snapshot_raw); bool snapshotExpired() { return snapshot.isExpired(TimeStamp::Now()); } int64_t snapshotSize() { return timestamp.snapshot_size(); } + void resetMeta(); + bool verifyTimestamp(const std::string& timestamp_raw); + bool fetchSnapshot(INvStorage& storage, const IMetadataFetcher& fetcher, int local_version); + bool verifySnapshot(const std::string& snapshot_raw); + bool fetchTargets(INvStorage& storage, const IMetadataFetcher& fetcher, int local_version); + bool verifyTargets(const std::string& targets_raw); + private: std::shared_ptr targets; Uptane::TimestampMeta timestamp; diff --git a/src/libaktualizr/uptane/isotpsecondary.cc b/src/libaktualizr/uptane/isotpsecondary.cc index 27c8db483d..4a28675e60 100644 --- a/src/libaktualizr/uptane/isotpsecondary.cc +++ b/src/libaktualizr/uptane/isotpsecondary.cc @@ -35,7 +35,7 @@ namespace Uptane { IsoTpSecondary::IsoTpSecondary(const std::string& can_iface, uint16_t can_id) : conn(can_iface, LIBUPTINY_ISOTP_PRIMARY_CANID, can_id) {} -EcuSerial IsoTpSecondary::getSerial() { +EcuSerial IsoTpSecondary::getSerial() const { std::string out; std::string in; @@ -50,7 +50,7 @@ EcuSerial IsoTpSecondary::getSerial() { return EcuSerial(in.substr(1)); } -HardwareIdentifier IsoTpSecondary::getHwId() { +HardwareIdentifier IsoTpSecondary::getHwId() const { std::string out; std::string in; @@ -65,7 +65,7 @@ HardwareIdentifier IsoTpSecondary::getHwId() { return HardwareIdentifier(in.substr(1)); } -PublicKey IsoTpSecondary::getPublicKey() { +PublicKey IsoTpSecondary::getPublicKey() const { std::string out; std::string in; @@ -80,22 +80,22 @@ PublicKey IsoTpSecondary::getPublicKey() { return PublicKey(boost::algorithm::hex(in.substr(1)), KeyType::kED25519); } -Json::Value IsoTpSecondary::getManifest() { +Uptane::Manifest IsoTpSecondary::getManifest() const { std::string out; std::string in; out += static_cast(IsoTpUptaneMesType::kGetManifest); if (!conn.SendRecv(out, &in)) { - return Json::nullValue; + return Json::Value(Json::nullValue); } if (in[0] != static_cast(IsoTpUptaneMesType::kGetManifestResp)) { - return Json::nullValue; + return Json::Value(Json::nullValue); } return Utils::parseJSON(in.substr(1)); } -int IsoTpSecondary::getRootVersion(bool director) { +int IsoTpSecondary::getRootVersion(bool director) const { if (!director) { return 0; } @@ -137,8 +137,8 @@ bool IsoTpSecondary::putMetadata(const RawMetaPack& meta_pack) { return conn.Send(out); } -bool IsoTpSecondary::sendFirmware(const std::shared_ptr& data) { - size_t num_chunks = 1 + (data->length() - 1) / kChunkSize; +bool IsoTpSecondary::sendFirmware(const std::string& data) { + size_t num_chunks = 1 + (data.length() - 1) / kChunkSize; if (num_chunks > 127) { return false; @@ -151,9 +151,9 @@ bool IsoTpSecondary::sendFirmware(const std::shared_ptr& data) { out += static_cast(num_chunks); out += static_cast(i + 1); if (i == num_chunks - 1) { - out += data->substr(static_cast(i * kChunkSize)); + out += data.substr(static_cast(i * kChunkSize)); } else { - out += data->substr(static_cast(i * kChunkSize), static_cast(kChunkSize)); + out += data.substr(static_cast(i * kChunkSize), static_cast(kChunkSize)); } if (!conn.SendRecv(out, &in)) { return false; diff --git a/src/libaktualizr/uptane/isotpsecondary.h b/src/libaktualizr/uptane/isotpsecondary.h index 0bb59121c3..8a47d1413c 100644 --- a/src/libaktualizr/uptane/isotpsecondary.h +++ b/src/libaktualizr/uptane/isotpsecondary.h @@ -10,17 +10,17 @@ class IsoTpSecondary : public SecondaryInterface { public: explicit IsoTpSecondary(const std::string& can_iface, uint16_t can_id); - EcuSerial getSerial() override; - HardwareIdentifier getHwId() override; - PublicKey getPublicKey() override; + EcuSerial getSerial() const override; + HardwareIdentifier getHwId() const override; + PublicKey getPublicKey() const override; bool putMetadata(const RawMetaPack& meta_pack) override; - int getRootVersion(bool director) override; + int getRootVersion(bool director) const override; bool putRoot(const std::string& root, bool director) override; - bool sendFirmware(const std::shared_ptr& data) override; - Json::Value getManifest() override; + bool sendFirmware(const std::string& data) override; + Uptane::Manifest getManifest() const override; private: - IsoTpSendRecv conn; + mutable IsoTpSendRecv conn; }; } // namespace Uptane #endif // UPTANE_ISOTPSECONDARY_H_ diff --git a/src/libaktualizr/uptane/manifest.cc b/src/libaktualizr/uptane/manifest.cc new file mode 100644 index 0000000000..001eccc03d --- /dev/null +++ b/src/libaktualizr/uptane/manifest.cc @@ -0,0 +1,75 @@ +#include "manifest.h" + +#include "crypto/keymanager.h" + +namespace Uptane { + +Hash Manifest::installedImageHash() const { + // TODO: proper verification of the required fields + return Uptane::Hash(Uptane::Hash::Type::kSha256, + (*this)["signed"]["installed_image"]["fileinfo"]["hashes"]["sha256"].asString()); +} + +std::string Manifest::signature() const { + // TODO: proper verification of the required fields + return (*this)["signatures"][0]["sig"].asString(); +} + +std::string Manifest::signedBody() const { + // TODO: proper verification of the required fields + return Utils::jsonToCanonicalStr((*this)["signed"]); +} + +bool Manifest::verifySignature(PublicKey &&pub_key) const { + if (!(isMember("signatures") && isMember("signed"))) { + LOG_ERROR << "Missing either signature or the signing body/subject: " << *this; + return false; + } + + return pub_key.VerifySignature(signature(), signedBody()); +} + +Manifest ManifestIssuer::sign(const Manifest &manifest, const std::string &report_counter) const { + Manifest manifest_to_sign = manifest; + if (!report_counter.empty()) { + manifest_to_sign["report_counter"] = report_counter; + } + return key_mngr_->signTuf(manifest_to_sign); +} + +Manifest ManifestIssuer::assembleManifest(const InstalledImageInfo &installed_image_info, + const Uptane::EcuSerial &ecu_serial) { + Json::Value installed_image; + installed_image["filepath"] = installed_image_info.name; + installed_image["fileinfo"]["length"] = Json::UInt64(installed_image_info.len); + installed_image["fileinfo"]["hashes"]["sha256"] = installed_image_info.hash; + + Json::Value unsigned_ecu_version; + unsigned_ecu_version["attacks_detected"] = ""; + unsigned_ecu_version["installed_image"] = installed_image; + unsigned_ecu_version["ecu_serial"] = ecu_serial.ToString(); + unsigned_ecu_version["previous_timeserver_time"] = "1970-01-01T00:00:00Z"; + unsigned_ecu_version["timeserver_time"] = "1970-01-01T00:00:00Z"; + return unsigned_ecu_version; +} + +Hash ManifestIssuer::generateVersionHash(const std::string &data) { return Hash::generate(Hash::Type::kSha256, data); } + +std::string ManifestIssuer::generateVersionHashStr(const std::string &data) { + // think of unifying a hash case,we use both lower and upper cases + return boost::algorithm::to_lower_copy(generateVersionHash(data).HashString()); +} + +Manifest ManifestIssuer::assembleManifest(const InstalledImageInfo &installed_image_info) const { + return assembleManifest(installed_image_info, ecu_serial_); +} + +Manifest ManifestIssuer::assembleManifest(const Uptane::Target &target) const { + return assembleManifest(target.getTargetImageInfo()); +} + +Manifest ManifestIssuer::assembleAndSignManifest(const InstalledImageInfo &installed_image_info) const { + return key_mngr_->signTuf(assembleManifest(installed_image_info)); +} + +} // namespace Uptane diff --git a/src/libaktualizr/uptane/manifest.h b/src/libaktualizr/uptane/manifest.h new file mode 100644 index 0000000000..0f5a20b79e --- /dev/null +++ b/src/libaktualizr/uptane/manifest.h @@ -0,0 +1,50 @@ +#ifndef AKTUALIZR_UPTANE_MANIFEST_H +#define AKTUALIZR_UPTANE_MANIFEST_H + +#include "json/json.h" +#include "tuf.h" + +#include + +class KeyManager; + +namespace Uptane { + +class Manifest : public Json::Value { + public: + Manifest(const Json::Value &value = Json::Value()) : Json::Value(value) {} + + public: + Hash installedImageHash() const; + std::string signature() const; + std::string signedBody() const; + bool verifySignature(PublicKey &&pub_key) const; +}; + +class ManifestIssuer { + public: + using Ptr = std::shared_ptr; + + public: + ManifestIssuer(std::shared_ptr &key_mngr, Uptane::EcuSerial ecu_serial) + : ecu_serial_(std::move(ecu_serial)), key_mngr_(key_mngr) {} + + static Manifest assembleManifest(const InstalledImageInfo &installed_image_info, const Uptane::EcuSerial &ecu_serial); + static Hash generateVersionHash(const std::string &data); + static std::string generateVersionHashStr(const std::string &data); + + Manifest sign(const Manifest &manifest, const std::string &report_counter = "") const; + + Manifest assembleManifest(const InstalledImageInfo &installed_image_info) const; + Manifest assembleManifest(const Uptane::Target &target) const; + + Manifest assembleAndSignManifest(const InstalledImageInfo &installed_image_info) const; + + private: + const Uptane::EcuSerial ecu_serial_; + std::shared_ptr key_mngr_; +}; + +} // namespace Uptane + +#endif // AKTUALIZR_UPTANE_MANIFEST_H diff --git a/src/libaktualizr/uptane/secondaryinterface.h b/src/libaktualizr/uptane/secondaryinterface.h index 7bd28b3e39..abf4d6a744 100644 --- a/src/libaktualizr/uptane/secondaryinterface.h +++ b/src/libaktualizr/uptane/secondaryinterface.h @@ -1,38 +1,41 @@ #ifndef UPTANE_SECONDARYINTERFACE_H #define UPTANE_SECONDARYINTERFACE_H -#include #include #include "json/json.h" - +#include "uptane/manifest.h" #include "uptane/tuf.h" namespace Uptane { class SecondaryInterface { public: - SecondaryInterface() = default; - virtual ~SecondaryInterface() = default; + using Ptr = std::shared_ptr; - SecondaryInterface(const SecondaryInterface&) = delete; - SecondaryInterface& operator=(const SecondaryInterface&) = delete; - - // getSerial(), getHwId() and getPublicKey() can be moved to seperate interface - // since their usage pattern differ from the following methods' one - virtual EcuSerial getSerial() = 0; - virtual Uptane::HardwareIdentifier getHwId() = 0; - virtual PublicKey getPublicKey() = 0; + public: + virtual EcuSerial getSerial() const = 0; + virtual Uptane::HardwareIdentifier getHwId() const = 0; + virtual PublicKey getPublicKey() const = 0; - virtual Json::Value getManifest() = 0; + virtual Uptane::Manifest getManifest() const = 0; virtual bool putMetadata(const RawMetaPack& meta_pack) = 0; - virtual int32_t getRootVersion(bool director) = 0; - virtual bool putRoot(const std::string& root, bool director) = 0; - // FIXME: Instead of std::string we should use StorageTargetRHandle - virtual bool sendFirmware(const std::shared_ptr& data) = 0; + virtual int32_t getRootVersion(bool director) const = 0; + virtual bool putRoot(const std::string& root, bool director) = 0; + virtual bool sendFirmware(const std::string& data) = 0; virtual data::ResultCode::Numeric install(const std::string& target_name) = 0; + + virtual ~SecondaryInterface() = default; + + public: + // make children non-copyable + SecondaryInterface(const SecondaryInterface&) = delete; + SecondaryInterface& operator=(const SecondaryInterface&) = delete; + + protected: + SecondaryInterface() = default; }; } // namespace Uptane diff --git a/src/libaktualizr/uptane/tuf.h b/src/libaktualizr/uptane/tuf.h index af90567768..4a3ae1e27f 100644 --- a/src/libaktualizr/uptane/tuf.h +++ b/src/libaktualizr/uptane/tuf.h @@ -128,6 +128,15 @@ class Version { friend std::ostream &operator<<(std::ostream &os, const Version &v); }; +struct InstalledImageInfo { + InstalledImageInfo() : name{""}, len{0} {} + InstalledImageInfo(std::string name_in, uint64_t len_in, std::string hash_in) + : name(std::move(name_in)), len(len_in), hash(std::move(hash_in)) {} + std::string name; + uint64_t len; + std::string hash; +}; + std::ostream &operator<<(std::ostream &os, const Version &v); class HardwareIdentifier { @@ -265,12 +274,14 @@ class Target { * root commit object. */ bool IsOstree() const; + std::string type() const { return type_; } // Comparison is usually not meaningful. Use MatchTarget instead. bool operator==(const Target &t2) = delete; bool MatchTarget(const Target &t2) const; Json::Value toDebugJson() const; friend std::ostream &operator<<(std::ostream &os, const Target &t); + InstalledImageInfo getTargetImageInfo() const { return {filename(), length(), sha256Hash()}; } private: bool valid{true}; diff --git a/src/libaktualizr/uptane/uptane_ci_test.cc b/src/libaktualizr/uptane/uptane_ci_test.cc index bac3300052..c604c0da91 100644 --- a/src/libaktualizr/uptane/uptane_ci_test.cc +++ b/src/libaktualizr/uptane/uptane_ci_test.cc @@ -34,8 +34,6 @@ TEST(UptaneCI, ProvisionAndPutManifest) { config.postUpdateValues(); // re-run copy of urls auto storage = INvStorage::newStorage(config.storage); - Uptane::Manifest uptane_manifest{config, storage}; - auto sota_client = std_::make_unique(config, storage); EXPECT_NO_THROW(sota_client->initialize()); EXPECT_TRUE(sota_client->putManifestSimple()); diff --git a/src/libaktualizr/uptane/uptane_test.cc b/src/libaktualizr/uptane/uptane_test.cc index 380af038a1..bd7a4655f5 100644 --- a/src/libaktualizr/uptane/uptane_test.cc +++ b/src/libaktualizr/uptane/uptane_test.cc @@ -526,24 +526,24 @@ class SecondaryInterfaceMock : public Uptane::SecondaryInterface { manifest_["signed"] = manifest_unsigned; manifest_["signatures"].append(signature); } - PublicKey getPublicKey() override { return public_key_; } + PublicKey getPublicKey() const override { return public_key_; } - Uptane::HardwareIdentifier getHwId() override { return Uptane::HardwareIdentifier(sconfig.ecu_hardware_id); } - Uptane::EcuSerial getSerial() override { + Uptane::HardwareIdentifier getHwId() const override { return Uptane::HardwareIdentifier(sconfig.ecu_hardware_id); } + Uptane::EcuSerial getSerial() const override { if (!sconfig.ecu_serial.empty()) { return Uptane::EcuSerial(sconfig.ecu_serial); } return Uptane::EcuSerial(public_key_.KeyId()); } - Json::Value getManifest() override { return manifest_; } + Uptane::Manifest getManifest() const override { return manifest_; } MOCK_METHOD1(putMetadataMock, bool(const Uptane::RawMetaPack &)); - MOCK_METHOD1(getRootVersionMock, int32_t(bool)); + MOCK_CONST_METHOD1(getRootVersionMock, int32_t(bool)); bool putMetadata(const Uptane::RawMetaPack &meta_pack) override { return putMetadataMock(meta_pack); } - int32_t getRootVersion(bool director) override { return getRootVersionMock(director); } + int32_t getRootVersion(bool director) const override { return getRootVersionMock(director); } bool putRoot(const std::string &, bool) override { return true; } - bool sendFirmware(const std::shared_ptr &) override { return true; } + bool sendFirmware(const std::string &) override { return true; } virtual data::ResultCode::Numeric install(const std::string &) override { return data::ResultCode::Numeric::kOk; } PublicKey public_key_; Json::Value manifest_; diff --git a/src/libaktualizr/uptane/uptanerepository.cc b/src/libaktualizr/uptane/uptanerepository.cc index b9727861a3..25a9fd73a1 100644 --- a/src/libaktualizr/uptane/uptanerepository.cc +++ b/src/libaktualizr/uptane/uptanerepository.cc @@ -19,18 +19,18 @@ namespace Uptane { -bool RepositoryCommon::initRoot(const std::string& root_raw) { +bool RepositoryCommon::initRoot(const std::string &root_raw) { try { root = Root(type, Utils::parseJSON(root_raw)); // initialization and format check root = Root(type, Utils::parseJSON(root_raw), root); // signature verification against itself - } catch (const std::exception& e) { + } catch (const std::exception &e) { LOG_ERROR << "Loading initial root failed: " << e.what(); return false; } return true; } -bool RepositoryCommon::verifyRoot(const std::string& root_raw) { +bool RepositoryCommon::verifyRoot(const std::string &root_raw) { try { int prev_version = rootVersion(); // 5.4.4.3.2.3. Version N+1 of the Root metadata file MUST have been signed @@ -46,7 +46,7 @@ bool RepositoryCommon::verifyRoot(const std::string& root_raw) { LOG_ERROR << "Version in root metadata doesn't match the expected value"; return false; } - } catch (const std::exception& e) { + } catch (const std::exception &e) { LOG_ERROR << "Signature verification for root metadata failed: " << e.what(); return false; } @@ -55,7 +55,7 @@ bool RepositoryCommon::verifyRoot(const std::string& root_raw) { void RepositoryCommon::resetRoot() { root = Root(Root::Policy::kAcceptAll); } -bool RepositoryCommon::updateRoot(INvStorage& storage, const IMetadataFetcher& fetcher, +bool RepositoryCommon::updateRoot(INvStorage &storage, const IMetadataFetcher &fetcher, const RepositoryType repo_type) { // 5.4.4.3.1. Load the previous Root metadata file. { @@ -107,9 +107,4 @@ bool RepositoryCommon::updateRoot(INvStorage& storage, const IMetadataFetcher& f return !rootExpired(); } -Json::Value Manifest::signManifest(const Json::Value& manifest_unsigned) const { - Json::Value manifest = keys_.signTuf(manifest_unsigned); - return manifest; -} - } // namespace Uptane diff --git a/src/libaktualizr/uptane/uptanerepository.h b/src/libaktualizr/uptane/uptanerepository.h index 12395db2f9..c0f69176f4 100644 --- a/src/libaktualizr/uptane/uptanerepository.h +++ b/src/libaktualizr/uptane/uptanerepository.h @@ -1,40 +1,13 @@ #ifndef UPTANE_REPOSITORY_H_ #define UPTANE_REPOSITORY_H_ -#include - #include "json/json.h" +#include "uptane/fetcher.h" -#include "config/config.h" -#include "crypto/crypto.h" -#include "crypto/keymanager.h" -#include "fetcher.h" -#include "logging/logging.h" -#include "storage/invstorage.h" +class INvStorage; namespace Uptane { -class Manifest { - public: - Manifest(const Config &config_in, std::shared_ptr storage_in) - : storage_{std::move(storage_in)}, keys_(storage_, config_in.keymanagerConfig()) {} - - Json::Value signManifest(const Json::Value &manifest_unsigned) const; - - void setPrimaryEcuSerialHwId(const std::pair &serials) { - primary_ecu_serial = serials.first; - primary_hardware_id = serials.second; - } - - EcuSerial getPrimaryEcuSerial() const { return primary_ecu_serial; } - - private: - Uptane::EcuSerial primary_ecu_serial{Uptane::EcuSerial::Unknown()}; - Uptane::HardwareIdentifier primary_hardware_id{Uptane::HardwareIdentifier::Unknown()}; - std::shared_ptr storage_; - KeyManager keys_; -}; - class RepositoryCommon { public: RepositoryCommon(RepositoryType type_in) : type{type_in} {} @@ -54,6 +27,7 @@ class RepositoryCommon { Root root; RepositoryType type; }; + } // namespace Uptane #endif diff --git a/src/libaktualizr/utilities/CMakeLists.txt b/src/libaktualizr/utilities/CMakeLists.txt index b03c99b1d7..fda70c09e8 100644 --- a/src/libaktualizr/utilities/CMakeLists.txt +++ b/src/libaktualizr/utilities/CMakeLists.txt @@ -2,7 +2,6 @@ set(SOURCES aktualizr_version.cc apiqueue.cc dequeue_buffer.cc sig_handler.cc - sockaddr_io.cc timer.cc types.cc utils.cc) @@ -14,7 +13,6 @@ set(HEADERS apiqueue.h exceptions.h fault_injection.h sig_handler.h - sockaddr_io.h timer.h types.h utils.h diff --git a/src/libaktualizr/utilities/sockaddr_io.cc b/src/libaktualizr/utilities/sockaddr_io.cc deleted file mode 100644 index 7e7e582a07..0000000000 --- a/src/libaktualizr/utilities/sockaddr_io.cc +++ /dev/null @@ -1,7 +0,0 @@ -#include "utilities/sockaddr_io.h" -#include "utilities/utils.h" - -std::ostream &operator<<(std::ostream &os, const sockaddr_storage &saddr) { - os << Utils::ipDisplayName(saddr) << ":" << Utils::ipPort(saddr); - return os; -} \ No newline at end of file diff --git a/src/libaktualizr/utilities/sockaddr_io.h b/src/libaktualizr/utilities/sockaddr_io.h deleted file mode 100644 index b1635ad9ec..0000000000 --- a/src/libaktualizr/utilities/sockaddr_io.h +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef UTILITIES_SOCKADDR_IO_H_ -#define UTILITIES_SOCKADDR_IO_H_ - -#include -#include - -std::ostream &operator<<(std::ostream &os, const sockaddr_storage &saddr); - -#endif // UTILITIES_SOCKADDR_IO_H_ \ No newline at end of file diff --git a/src/libaktualizr/utilities/utils.cc b/src/libaktualizr/utilities/utils.cc index 65252577c4..520fba3e23 100644 --- a/src/libaktualizr/utilities/utils.cc +++ b/src/libaktualizr/utilities/utils.cc @@ -275,7 +275,8 @@ std::string Utils::genPrettyName() { std::string Utils::readFile(const boost::filesystem::path &filename, const bool trim) { boost::filesystem::path tmpFilename = filename; tmpFilename += ".new"; - + // that's kind of dangerous to include a specific use-case business logic into a generic function used by many use + // cases if (boost::filesystem::exists(tmpFilename)) { LOG_WARNING << tmpFilename << " was found on FS, removing"; boost::filesystem::remove(tmpFilename); @@ -847,24 +848,21 @@ bool operator<(const sockaddr_storage &left, const sockaddr_storage &right) { return (res < 0); } -Socket::Socket(const std::string &ip, uint16_t port) : sock_address_{} { - memset(&sock_address_, 0, sizeof(sock_address_)); - sock_address_.sin_family = AF_INET; - inet_pton(AF_INET, ip.c_str(), &(sock_address_.sin_addr)); - sock_address_.sin_port = htons(port); - +Socket::Socket() { socket_fd_ = socket(AF_INET, SOCK_STREAM, 0); - if (socket_fd_ < 0) { + if (-1 == socket_fd_) { throw std::system_error(errno, std::system_category(), "socket"); } } -Socket::~Socket() { - shutdown(socket_fd_, SHUT_RDWR); - ::close(socket_fd_); +Socket::~Socket() { ::close(socket_fd_); } + +std::string Socket::toString() { + auto saddr = Utils::ipGetSockaddr(socket_fd_); + return Utils::ipDisplayName(saddr) + ":" + std::to_string(Utils::ipPort(saddr)); } -int Socket::bind(in_port_t port, bool reuse) { +void Socket::bind(in_port_t port, bool reuse) { sockaddr_in sa{}; memset(&sa, 0, sizeof(sa)); sa.sin_family = AF_INET; @@ -872,15 +870,45 @@ int Socket::bind(in_port_t port, bool reuse) { sa.sin_addr.s_addr = htonl(INADDR_ANY); int reuseaddr = reuse ? 1 : 0; - if (setsockopt(socket_fd_, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)) < 0) { - return errno; + if (-1 == setsockopt(socket_fd_, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr))) { + throw std::system_error(errno, std::system_category(), "socket"); } - return ::bind(socket_fd_, reinterpret_cast(&sa), sizeof(sa)); + if (-1 == ::bind(socket_fd_, reinterpret_cast(&sa), sizeof(sa))) { + throw std::system_error(errno, std::system_category(), "socket"); + } } -int Socket::connect() { - return ::connect(socket_fd_, reinterpret_cast(&sock_address_), sizeof(sock_address_)); +ListenSocket::ListenSocket(in_port_t port) : _port(port) { + bind(port); + if (_port == 0) { + // ephemeral port was bound, find out its real port number + auto ephemeral_port = Utils::ipPort(Utils::ipGetSockaddr(socket_fd_)); + if (-1 != ephemeral_port) { + _port = static_cast(ephemeral_port); + } + } +} + +ConnectionSocket::ConnectionSocket(const std::string &ip, in_port_t port, in_port_t bind_port) + : remote_sock_address_{} { + memset(&remote_sock_address_, 0, sizeof(remote_sock_address_)); + remote_sock_address_.sin_family = AF_INET; + if (-1 == inet_pton(AF_INET, ip.c_str(), &(remote_sock_address_.sin_addr))) { + throw std::system_error(errno, std::system_category(), "socket"); + } + remote_sock_address_.sin_port = htons(port); + + if (bind_port > 0) { + bind(bind_port); + } +} + +ConnectionSocket::~ConnectionSocket() { ::shutdown(socket_fd_, SHUT_RDWR); } + +int ConnectionSocket::connect() { + return ::connect(socket_fd_, reinterpret_cast(&remote_sock_address_), + sizeof(remote_sock_address_)); } CurlEasyWrapper::CurlEasyWrapper() { diff --git a/src/libaktualizr/utilities/utils.h b/src/libaktualizr/utilities/utils.h index c6bf73f930..9abf5c0074 100644 --- a/src/libaktualizr/utilities/utils.h +++ b/src/libaktualizr/utilities/utils.h @@ -122,29 +122,45 @@ class BasedPath { template using StructGuard = std::unique_ptr; -// helper object for RAII socket management -struct SocketCloser { - void operator()(const int *ptr) const { - close(*ptr); - delete ptr; - } -}; - -using SocketHandle = std::unique_ptr; bool operator<(const sockaddr_storage &left, const sockaddr_storage &right); // required by std::map class Socket { public: - Socket(const std::string &ip, uint16_t port); - ~Socket(); + Socket(); + virtual ~Socket() = 0; + + Socket(const Socket &) = delete; + Socket &operator=(const Socket &) = delete; + + int &operator*() { return socket_fd_; } + std::string toString(); + + protected: + void bind(in_port_t port, bool reuse = true); + + protected: + int socket_fd_; +}; + +class ConnectionSocket : public Socket { + public: + ConnectionSocket(const std::string &ip, in_port_t port, in_port_t bind_port = 0); + ~ConnectionSocket() override; - int bind(in_port_t port, bool reuse = true); + public: int connect(); - int getFD() { return socket_fd_; } private: - struct sockaddr_in sock_address_; - int socket_fd_; + struct sockaddr_in remote_sock_address_; +}; + +class ListenSocket : public Socket { + public: + ListenSocket(in_port_t port); + in_port_t port() const { return _port; } + + private: + in_port_t _port; }; // wrapper for curl handles diff --git a/src/libaktualizr/utilities/utils_test.cc b/src/libaktualizr/utilities/utils_test.cc index 6368a6465c..8fc9015bf5 100644 --- a/src/libaktualizr/utilities/utils_test.cc +++ b/src/libaktualizr/utilities/utils_test.cc @@ -309,33 +309,6 @@ TEST(Utils, writeFileJson) { EXPECT_EQ(result_json["key"].asString(), val["key"].asString()); } -TEST(Utils, ipUtils) { - int fd = socket(AF_INET6, SOCK_STREAM, 0); - - EXPECT_NE(fd, -1); - SocketHandle hdl(new int(fd)); - - sockaddr_in6 sa{}; - - memset(&sa, 0, sizeof(sa)); - sa.sin6_family = AF_INET6; - sa.sin6_port = htons(0); - sa.sin6_addr = IN6ADDR_ANY_INIT; - - int reuseaddr = 1; - if (setsockopt(*hdl, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)) < 0) { - throw std::runtime_error("setsockopt(SO_REUSEADDR) failed"); - } - - EXPECT_NE(bind(*hdl, reinterpret_cast(&sa), sizeof(sa)), -1); - - sockaddr_storage ss{}; - EXPECT_NO_THROW(ss = Utils::ipGetSockaddr(*hdl)); - - EXPECT_NE(Utils::ipDisplayName(ss), "unknown"); - EXPECT_NE(Utils::ipPort(ss), -1); -} - TEST(Utils, shell) { std::string out; int statuscode = Utils::shell("ls /", &out); diff --git a/src/virtual_secondary/CMakeLists.txt b/src/virtual_secondary/CMakeLists.txt index 2017fe7547..5f1cb3beff 100644 --- a/src/virtual_secondary/CMakeLists.txt +++ b/src/virtual_secondary/CMakeLists.txt @@ -6,6 +6,8 @@ set(TARGET virtual_secondary) add_library(${TARGET} STATIC ${SOURCES} + $ + $ ) target_include_directories(${TARGET} PUBLIC ${PROJECT_SOURCE_DIR}/src/virtual_secondary) diff --git a/src/virtual_secondary/managedsecondary.cc b/src/virtual_secondary/managedsecondary.cc index 241cdf8433..594d87e5bc 100644 --- a/src/virtual_secondary/managedsecondary.cc +++ b/src/virtual_secondary/managedsecondary.cc @@ -9,6 +9,8 @@ #include "crypto/crypto.h" #include "logging/logging.h" +#include "uptane/manifest.h" +#include "uptane/uptanerepository.h" #include "utilities/exceptions.h" namespace Primary { @@ -108,7 +110,7 @@ bool ManagedSecondary::putMetadata(const Uptane::RawMetaPack &meta_pack) { return true; } -int ManagedSecondary::getRootVersion(const bool director) { +int ManagedSecondary::getRootVersion(const bool director) const { if (director) { return current_meta.director_root.version(); } @@ -137,7 +139,7 @@ bool ManagedSecondary::putRoot(const std::string &root, const bool director) { return true; } -bool ManagedSecondary::sendFirmware(const std::shared_ptr &data) { +bool ManagedSecondary::sendFirmware(const std::string &data) { std::lock_guard l(install_mutex); if (expected_target_name.empty()) { @@ -147,7 +149,7 @@ bool ManagedSecondary::sendFirmware(const std::shared_ptr &data) { return false; } - if (data->size() > static_cast(expected_target_length)) { + if (data.size() > static_cast(expected_target_length)) { detected_attack = "overflow"; return false; } @@ -155,13 +157,13 @@ bool ManagedSecondary::sendFirmware(const std::shared_ptr &data) { std::vector::const_iterator it; for (it = expected_target_hashes.begin(); it != expected_target_hashes.end(); it++) { if (it->TypeString() == "sha256") { - if (boost::algorithm::to_lower_copy(boost::algorithm::hex(Crypto::sha256digest(*data))) != + if (boost::algorithm::to_lower_copy(boost::algorithm::hex(Crypto::sha256digest(data))) != boost::algorithm::to_lower_copy(it->HashString())) { detected_attack = "wrong_hash"; return false; } } else if (it->TypeString() == "sha512") { - if (boost::algorithm::to_lower_copy(boost::algorithm::hex(Crypto::sha512digest(*data))) != + if (boost::algorithm::to_lower_copy(boost::algorithm::hex(Crypto::sha512digest(data))) != boost::algorithm::to_lower_copy(it->HashString())) { detected_attack = "wrong_hash"; return false; @@ -169,7 +171,7 @@ bool ManagedSecondary::sendFirmware(const std::shared_ptr &data) { } } detected_attack = ""; - const bool result = storeFirmware(expected_target_name, *data); + const bool result = storeFirmware(expected_target_name, data); return result; } @@ -178,28 +180,14 @@ data::ResultCode::Numeric ManagedSecondary::install(const std::string &target_na return data::ResultCode::Numeric::kOk; } -Json::Value ManagedSecondary::getManifest() { - std::string hash; - std::string targetname; - size_t target_len; - if (!getFirmwareInfo(&targetname, target_len, &hash)) { - return Json::nullValue; +Uptane::Manifest ManagedSecondary::getManifest() const { + Uptane::InstalledImageInfo firmware_info; + if (!getFirmwareInfo(firmware_info)) { + return Json::Value(Json::nullValue); } - Json::Value manifest; - - // package manager will generate this part in future - Json::Value installed_image; - installed_image["filepath"] = targetname; - - installed_image["fileinfo"]["hashes"]["sha256"] = hash; - installed_image["fileinfo"]["length"] = static_cast(target_len); - + Json::Value manifest = Uptane::ManifestIssuer::assembleManifest(firmware_info, getSerial()); manifest["attacks_detected"] = detected_attack; - manifest["installed_image"] = installed_image; - manifest["ecu_serial"] = getSerial().ToString(); - manifest["previous_timeserver_time"] = "1970-01-01T00:00:00Z"; - manifest["timeserver_time"] = "1970-01-01T00:00:00Z"; Json::Value signed_ecu_version; diff --git a/src/virtual_secondary/managedsecondary.h b/src/virtual_secondary/managedsecondary.h index 62f27eb653..f8f2f08fd0 100644 --- a/src/virtual_secondary/managedsecondary.h +++ b/src/virtual_secondary/managedsecondary.h @@ -45,21 +45,22 @@ class ManagedSecondary : public Uptane::SecondaryInterface { void Initialize(); - Uptane::EcuSerial getSerial() override { + Uptane::EcuSerial getSerial() const override { if (!sconfig.ecu_serial.empty()) { return Uptane::EcuSerial(sconfig.ecu_serial); } return Uptane::EcuSerial(public_key_.KeyId()); } - Uptane::HardwareIdentifier getHwId() override { return Uptane::HardwareIdentifier(sconfig.ecu_hardware_id); } - PublicKey getPublicKey() override { return public_key_; } + Uptane::HardwareIdentifier getHwId() const override { return Uptane::HardwareIdentifier(sconfig.ecu_hardware_id); } + PublicKey getPublicKey() const override { return public_key_; } bool putMetadata(const Uptane::RawMetaPack& meta_pack) override; - int getRootVersion(bool director) override; + int getRootVersion(bool director) const override; bool putRoot(const std::string& root, bool director) override; - bool sendFirmware(const std::shared_ptr& data) override; + bool sendFirmware(const std::string& data) override; data::ResultCode::Numeric install(const std::string& target_name) override; - Json::Value getManifest() override; + + Uptane::Manifest getManifest() const override; bool loadKeys(std::string* pub_key, std::string* priv_key); @@ -80,7 +81,7 @@ class ManagedSecondary : public Uptane::SecondaryInterface { std::mutex install_mutex; virtual bool storeFirmware(const std::string& target_name, const std::string& content) = 0; - virtual bool getFirmwareInfo(std::string* target_name, size_t& target_len, std::string* sha256hash) = 0; + virtual bool getFirmwareInfo(Uptane::InstalledImageInfo& firmware_info) const = 0; void storeKeys(const std::string& pub_key, const std::string& priv_key); void rawToMeta(); diff --git a/src/virtual_secondary/partialverificationsecondary.cc b/src/virtual_secondary/partialverificationsecondary.cc index 03053efffc..9952e1cceb 100644 --- a/src/virtual_secondary/partialverificationsecondary.cc +++ b/src/virtual_secondary/partialverificationsecondary.cc @@ -57,12 +57,12 @@ bool PartialVerificationSecondary::putMetadata(const RawMetaPack &meta) { return true; } -Json::Value PartialVerificationSecondary::getManifest() { +Uptane::Manifest PartialVerificationSecondary::getManifest() const { throw NotImplementedException(); return Json::Value(); } -int PartialVerificationSecondary::getRootVersion(bool director) { +int PartialVerificationSecondary::getRootVersion(bool director) const { (void)director; throw NotImplementedException(); return 0; @@ -76,7 +76,7 @@ bool PartialVerificationSecondary::putRoot(const std::string &root, bool directo return false; } -bool PartialVerificationSecondary::sendFirmware(const std::shared_ptr &data) { +bool PartialVerificationSecondary::sendFirmware(const std::string &data) { (void)data; throw NotImplementedException(); } diff --git a/src/virtual_secondary/partialverificationsecondary.h b/src/virtual_secondary/partialverificationsecondary.h index 98776fe5cb..2773e94276 100644 --- a/src/virtual_secondary/partialverificationsecondary.h +++ b/src/virtual_secondary/partialverificationsecondary.h @@ -30,22 +30,22 @@ class PartialVerificationSecondary : public SecondaryInterface { public: explicit PartialVerificationSecondary(Primary::PartialVerificationSecondaryConfig sconfig_in); - EcuSerial getSerial() override { + EcuSerial getSerial() const override { if (!sconfig.ecu_serial.empty()) { return Uptane::EcuSerial(sconfig.ecu_serial); } return Uptane::EcuSerial(public_key_.KeyId()); } - Uptane::HardwareIdentifier getHwId() override { return Uptane::HardwareIdentifier(sconfig.ecu_hardware_id); } - PublicKey getPublicKey() override { return public_key_; } + Uptane::HardwareIdentifier getHwId() const override { return Uptane::HardwareIdentifier(sconfig.ecu_hardware_id); } + PublicKey getPublicKey() const override { return public_key_; } bool putMetadata(const RawMetaPack& meta) override; - int getRootVersion(bool director) override; + int getRootVersion(bool director) const override; bool putRoot(const std::string& root, bool director) override; - bool sendFirmware(const std::shared_ptr& data) override; + bool sendFirmware(const std::string& data) override; data::ResultCode::Numeric install(const std::string& target_name) override; - Json::Value getManifest() override; + Uptane::Manifest getManifest() const override; private: void storeKeys(const std::string& public_key, const std::string& private_key); diff --git a/src/virtual_secondary/virtualsecondary.cc b/src/virtual_secondary/virtualsecondary.cc index 37174d81d3..5e31ca9c0c 100644 --- a/src/virtual_secondary/virtualsecondary.cc +++ b/src/virtual_secondary/virtualsecondary.cc @@ -70,26 +70,37 @@ VirtualSecondary::VirtualSecondary(Primary::VirtualSecondaryConfig sconfig_in) bool VirtualSecondary::storeFirmware(const std::string& target_name, const std::string& content) { if (fiu_fail((std::string("secondary_install_") + getSerial().ToString()).c_str()) != 0) { + // consider changing this approach of the fault injection, since the current approach impacts the non-test code flow + // here as well as it doesn't test the installation failure on secondary from an end-to-end perspective as it + // injects an error on the middle of the control flow that would have happened if an installation error had happened + // in case of the virtual or the ip-secondary or any other secondary, e.g. add a mock secondary that returns an + // error to sendFirmware/install request we might consider passing the installation description message from + // Secondary, not just bool and/or data::ResultCode::Numeric return false; } + + // TODO: it does not make much sense to read, pass via a function parameter to Virtual secondary + // and store the file that has been already downloaded by Primary + // Primary should apply ECU (primary, virtual, secondary) specific verification, download and installation logic in + // the first place Utils::writeFile(sconfig.target_name_path, target_name); Utils::writeFile(sconfig.firmware_path, content); sync(); return true; } -bool VirtualSecondary::getFirmwareInfo(std::string* target_name, size_t& target_len, std::string* sha256hash) { +bool VirtualSecondary::getFirmwareInfo(Uptane::InstalledImageInfo& firmware_info) const { std::string content; if (!boost::filesystem::exists(sconfig.target_name_path) || !boost::filesystem::exists(sconfig.firmware_path)) { - *target_name = std::string("noimage"); + firmware_info.name = std::string("noimage"); content = ""; } else { - *target_name = Utils::readFile(sconfig.target_name_path.string()); + firmware_info.name = Utils::readFile(sconfig.target_name_path.string()); content = Utils::readFile(sconfig.firmware_path.string()); } - *sha256hash = boost::algorithm::to_lower_copy(boost::algorithm::hex(Crypto::sha256digest(content))); - target_len = content.size(); + firmware_info.hash = Uptane::ManifestIssuer::generateVersionHashStr(content); + firmware_info.len = content.size(); return true; } diff --git a/src/virtual_secondary/virtualsecondary.h b/src/virtual_secondary/virtualsecondary.h index 4db18eb868..68ff553721 100644 --- a/src/virtual_secondary/virtualsecondary.h +++ b/src/virtual_secondary/virtualsecondary.h @@ -27,7 +27,7 @@ class VirtualSecondary : public ManagedSecondary { private: bool storeFirmware(const std::string& target_name, const std::string& content) override; - bool getFirmwareInfo(std::string* target_name, size_t& target_len, std::string* sha256hash) override; + bool getFirmwareInfo(Uptane::InstalledImageInfo& firmware_info) const override; }; } // namespace Primary diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8edc66a3a1..812208eaff 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -189,7 +189,7 @@ set_tests_properties(test_log_negative add_test(NAME test_ip_secondary COMMAND ${PROJECT_SOURCE_DIR}/tests/ipsecondary_test.py - --build-dir ${PROJECT_BINARY_DIR} --src-dir ${PROJECT_SOURCE_DIR}) + --build-dir ${PROJECT_BINARY_DIR} --src-dir ${PROJECT_SOURCE_DIR} --ostree ${BUILD_OSTREE}) add_test(NAME test_backend_failure COMMAND ${PROJECT_SOURCE_DIR}/tests/test_backend_failure.py diff --git a/tests/aktualizr.supp b/tests/aktualizr.supp index 51aae5c554..d2554f850e 100644 --- a/tests/aktualizr.supp +++ b/tests/aktualizr.supp @@ -90,3 +90,19 @@ fun:ostree_sysroot_simple_write_deployment ... } +{ + asn1c ber_decode issues + Memcheck:Cond + ... + fun:CHOICE_decode_ber + fun:ber_decode + ... +} +{ + asn1c ber_decode issues + Memcheck:Value8 + ... + fun:CHOICE_decode_ber + fun:ber_decode + ... +} diff --git a/tests/ipsecondary_test.py b/tests/ipsecondary_test.py index cbbe1c8ee7..4f87357b34 100755 --- a/tests/ipsecondary_test.py +++ b/tests/ipsecondary_test.py @@ -189,6 +189,8 @@ def test_primary_timeout_after_device_is_registered(uptane_repo, secondary, aktu parser = argparse.ArgumentParser(description='Test IP Secondary') parser.add_argument('-b', '--build-dir', help='build directory', default='build') parser.add_argument('-s', '--src-dir', help='source directory', default='.') + parser.add_argument('-o', '--ostree', help='ostree support', default='OFF') + input_params = parser.parse_args() KeyStore.base_dir = path.abspath(input_params.src_dir) @@ -196,14 +198,16 @@ def test_primary_timeout_after_device_is_registered(uptane_repo, secondary, aktu chdir(input_params.build_dir) test_suite = [ - test_secondary_ostree_update, - test_secondary_update, - test_secondary_update_if_secondary_starts_first, - test_secondary_update_if_primary_starts_first, - test_primary_timeout_during_first_run, - test_primary_timeout_after_device_is_registered + test_secondary_update, + test_secondary_update_if_secondary_starts_first, + test_secondary_update_if_primary_starts_first, + test_primary_timeout_during_first_run, + test_primary_timeout_after_device_is_registered ] + if input_params.ostree == 'ON': + test_suite.append(test_secondary_ostree_update) + test_suite_run_result = True for test in test_suite: logger.info('>>> Running {}...'.format(test.__name__)) diff --git a/tests/test_fixtures.py b/tests/test_fixtures.py index 832eeb69d2..509449762e 100644 --- a/tests/test_fixtures.py +++ b/tests/test_fixtures.py @@ -57,7 +57,7 @@ def __init__(self, aktualizr_primary_exe, aktualizr_info_exe, id, secondary_cfg_file=self._secondary_config_file, director=director.base_url if director else '', image_repo=image_repo.base_url if image_repo else '', - pacmam_type='ostree' if treehub and sysroot else 'fake', + pacman_type='ostree' if treehub and sysroot else 'fake', ostree_sysroot=sysroot.path if sysroot else '', treehub_server=treehub.base_url if treehub else '', sentinel_dir=self._storage_dir.name, @@ -92,7 +92,7 @@ def __init__(self, aktualizr_primary_exe, aktualizr_info_exe, id, sqldb_path = "{db_path}" [pacman] - type = "{pacmam_type}" + type = "{pacman_type}" sysroot = "{ostree_sysroot}" ostree_server = "{treehub_server}" os = "dummy-os" @@ -297,7 +297,7 @@ def __init__(self, aktualizr_secondary_exe, id, port=9050, primary_port=9040, port=self.port, primary_port=self.primary_port, storage_dir=self._storage_dir.name, db_path=path.join(self._storage_dir.name, 'db.sql'), - pacmam_type='ostree' if treehub and sysroot else 'fake', + pacman_type='ostree' if treehub and sysroot else 'fake', ostree_sysroot=sysroot.path if sysroot else '', treehub_server=treehub.base_url if treehub else '', sentinel_dir=self._storage_dir.name, @@ -327,7 +327,7 @@ def __init__(self, aktualizr_secondary_exe, id, port=9050, primary_port=9040, sqldb_path = "{db_path}" [pacman] - type = "{pacmam_type}" + type = "{pacman_type}" sysroot = "{ostree_sysroot}" ostree_server = "{treehub_server}" os = "dummy-os" diff --git a/tests/test_update.py b/tests/test_update.py index f2fb11c102..23e01e89ba 100755 --- a/tests/test_update.py +++ b/tests/test_update.py @@ -21,10 +21,10 @@ while aktualizr/primary is configured with ostree package manager """ @with_uptane_backend(start_generic_server=True) +@with_secondary(start=True) @with_director() @with_treehub() @with_sysroot() -@with_secondary(start=True) @with_aktualizr(start=False, run_mode='once', output_logs=True) def test_primary_ostree_secondary_fake_updates(uptane_repo, secondary, aktualizr, director, uptane_server, sysroot, treehub): diff --git a/tests/test_utils.cc b/tests/test_utils.cc index e7dce5eb95..cc2ee2d1c7 100644 --- a/tests/test_utils.cc +++ b/tests/test_utils.cc @@ -19,7 +19,7 @@ #include "logging/logging.h" -std::string TestUtils::getFreePort() { +in_port_t TestUtils::getFreePortAsInt() { int s = socket(AF_INET, SOCK_STREAM, 0); if (s == -1) { std::cout << "socket() failed: " << errno; @@ -42,9 +42,11 @@ std::string TestUtils::getFreePort() { throw std::runtime_error("getsockname failed"); } close(s); - return std::to_string(ntohs(sa.sin_port)); + return sa.sin_port; } +std::string TestUtils::getFreePort() { return std::to_string(ntohs(getFreePortAsInt())); } + void TestUtils::writePathToConfig(const boost::filesystem::path &toml_in, const boost::filesystem::path &toml_out, const boost::filesystem::path &storage_path) { // Append our temp_dir path as storage.path to the config file. This is a hack diff --git a/tests/test_utils.h b/tests/test_utils.h index d548924700..8972900a4e 100644 --- a/tests/test_utils.h +++ b/tests/test_utils.h @@ -10,6 +10,7 @@ struct TestUtils { static std::string getFreePort(); + static in_port_t getFreePortAsInt(); static void writePathToConfig(const boost::filesystem::path &toml_in, const boost::filesystem::path &toml_out, const boost::filesystem::path &storage_path); static void waitForServer(const std::string &address); diff --git a/tests/uptane_vector_tests.cc b/tests/uptane_vector_tests.cc index 0c187e4d70..230f8eb41b 100644 --- a/tests/uptane_vector_tests.cc +++ b/tests/uptane_vector_tests.cc @@ -96,11 +96,11 @@ TEST_P(UptaneVector, Test) { logger_set_threshold(boost::log::trivial::trace); auto storage = INvStorage::newStorage(config.storage); - Uptane::Manifest uptane_manifest{config, storage}; auto uptane_client = std_::make_unique(config, storage); Uptane::EcuSerial ecu_serial(config.provision.primary_ecu_serial); Uptane::HardwareIdentifier hw_id(config.provision.primary_ecu_hardware_id); uptane_client->hw_ids.insert(std::make_pair(ecu_serial, hw_id)); + uptane_client->primary_ecu_serial_ = ecu_serial; Uptane::EcuMap ecu_map{{ecu_serial, hw_id}}; Uptane::Target target("test_filename", ecu_map, {{Uptane::Hash::Type::kSha256, "sha256"}}, 1, ""); storage->saveInstalledVersion(ecu_serial.ToString(), target, InstalledVersionUpdateMode::kCurrent);