Skip to content
This repository has been archived by the owner on May 21, 2024. It is now read-only.

Commit

Permalink
Refactor delegations to hopefuly be more secure
Browse files Browse the repository at this point in the history
Also, this enables nested delegations

Signed-off-by: Anton Gerasimov <[email protected]>
  • Loading branch information
Anton Gerasimov committed Feb 8, 2019
1 parent 34e4183 commit 252be62
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 86 deletions.
111 changes: 102 additions & 9 deletions src/libaktualizr/primary/sotauptaneclient.cc
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "sotauptaneclient.h"

#include <fnmatch.h>
#include <unistd.h>
#include <memory>
#include <utility>
Expand Down Expand Up @@ -569,7 +570,7 @@ bool SotaUptaneClient::updateImagesMeta() {
local_version = -1;
}

if (!images_repo.verifyTargets(images_targets, targets_role)) {
if (!images_repo.verifyTargets(images_targets)) {
last_exception = images_repo.getLastException();
return false;
}
Expand All @@ -580,14 +581,14 @@ bool SotaUptaneClient::updateImagesMeta() {
storage->storeNonRoot(images_targets, Uptane::RepositoryType::Image(), targets_role);
}

if (images_repo.targetsExpired("targets")) {
if (images_repo.targetsExpired()) {
last_exception = Uptane::ExpiredMetadata("repo", "targets");
return false;
}
}

// Update Images delegated Targets Metadata
for (const std::string &delegation : images_repo.delegations("targets")) {
/* for (const std::string &delegation : images_repo.delegations("targets")) {
std::string images_delegated;
Uptane::Role delegated_role = Uptane::Role::Delegation(delegation);
Expand Down Expand Up @@ -620,7 +621,7 @@ bool SotaUptaneClient::updateImagesMeta() {
last_exception = Uptane::ExpiredMetadata("repo", delegation);
return false;
}
}
}*/

return true;
}
Expand Down Expand Up @@ -732,19 +733,19 @@ bool SotaUptaneClient::checkImagesMetaOffline() {
return false;
}

if (!images_repo.verifyTargets(images_targets, targets_role)) {
if (!images_repo.verifyTargets(images_targets)) {
last_exception = images_repo.getLastException();
return false;
}

if (images_repo.targetsExpired("targets")) {
if (images_repo.targetsExpired()) {
last_exception = Uptane::ExpiredMetadata("repo", "targets");
return false;
}
}

// Update Images delegated Targets Metadata
for (const std::string &delegation : images_repo.delegations("targets")) {
/*for (const std::string &delegation : images_repo.delegations("targets")) {
std::string images_delegated;
Uptane::Role delegated_role = Uptane::Role::Delegation(delegation);
if (!storage->loadDelegation(&images_delegated, delegated_role)) {
Expand All @@ -760,7 +761,7 @@ bool SotaUptaneClient::checkImagesMetaOffline() {
last_exception = Uptane::ExpiredMetadata("repo", delegation);
return false;
}
}
}*/

return true;
}
Expand Down Expand Up @@ -841,14 +842,106 @@ bool SotaUptaneClient::getNewTargets(std::vector<Uptane::Target> *new_targets, u
return true;
}

std::unique_ptr<Uptane::Target> SotaUptaneClient::findTargetHelper(const Uptane::Targets &cur_targets,
const Uptane::Target &queried_target, int level,
bool terminating) {
auto it = std::find(cur_targets.targets.cbegin(), cur_targets.targets.cend(), queried_target);
if (it != cur_targets.targets.cend()) {
return std_::make_unique<Uptane::Target>(*it);
}

if (terminating || level >= Uptane::kDelegationsMaxDepth) {
return std::unique_ptr<Uptane::Target>(nullptr);
}

for (const auto &delegate_name : cur_targets.delegated_role_names_) {
Uptane::Role delegate_role = Uptane::Role::Delegation(delegate_name);
auto patterns = cur_targets.paths_for_role_.find(delegate_role);

if (patterns == cur_targets.paths_for_role_.end()) {
continue;
}

bool match = false;

for (const auto &pattern : patterns->second) {
if (fnmatch(pattern.c_str(), queried_target.filename().c_str(), 0) == 0) {
match = true;
break;
}
}

if (!match) {
continue;
}

// Target name matches one of the patterns

std::string delegation_meta;
std::shared_ptr<Uptane::Targets> delegation;

if (storage->loadDelegation(&delegation_meta, delegate_role)) {
delegation = Uptane::ImagesRepository::verifyDelegation(delegation_meta, delegate_role, cur_targets);
if (delegation == nullptr) {
storage->deleteDelegation(delegate_role);
delegation_meta.clear();
}
}

if (delegation == nullptr) {
if (!uptane_fetcher->fetchLatestRole(&delegation_meta, Uptane::kMaxImagesTargetsSize,
Uptane::RepositoryType::Image(), delegate_role)) {
throw std::runtime_error(std::string("Couldn't fetch ") +
delegate_role.ToString()); // TODO: connectivity exception
}

delegation = Uptane::ImagesRepository::verifyDelegation(delegation_meta, delegate_role, cur_targets);

if (delegation == nullptr) {
throw Uptane::SecurityException("images", "Delegation verification failed");
}

storage->storeDelegation(delegation_meta, delegate_role);
}

if (delegation->isExpired(TimeStamp::Now())) {
continue;
}

auto is_terminating = cur_targets.terminating_role_.find(delegate_role);

if (is_terminating == cur_targets.terminating_role_.end()) {
throw Uptane::Exception("images", "Inconsistent delegations");
}

auto found_target = findTargetHelper(*delegation, queried_target, level + 1, is_terminating->second);

if (found_target != nullptr) {
return found_target;
}
}

return std::unique_ptr<Uptane::Target>(nullptr);
}

std::unique_ptr<Uptane::Target> SotaUptaneClient::findTargetInDelegationTree(const Uptane::Target &target) {
auto toplevel_targets = images_repo.getTrustedTargets();

if (toplevel_targets == nullptr) {
return std::unique_ptr<Uptane::Target>(nullptr);
}

return findTargetHelper(*toplevel_targets, target, 0, false);
}

result::Download SotaUptaneClient::downloadImages(const std::vector<Uptane::Target> &targets) {
// Uptane step 4 - download all the images and verify them against the metadata (for OSTree - pull without
// deploying)
std::lock_guard<std::mutex> guard(download_mutex);
result::Download result;
std::vector<Uptane::Target> downloaded_targets;
for (auto it = targets.cbegin(); it != targets.cend(); ++it) {
auto images_target = images_repo.getTarget(*it);
auto images_target = findTargetInDelegationTree(*it);
if (images_target == nullptr) {
last_exception = Uptane::TargetHashMismatch(it->filename());
LOG_ERROR << "No matching target in images targets metadata for " << *it;
Expand Down
3 changes: 3 additions & 0 deletions src/libaktualizr/primary/sotauptaneclient.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,9 @@ class SotaUptaneClient {
bool checkImagesMetaOffline();
bool checkDirectorMetaOffline();
void computeDeviceInstallationResult(data::InstallationResult *result, const std::string &correlation_id);
std::unique_ptr<Uptane::Target> findTargetInDelegationTree(const Uptane::Target &target);
std::unique_ptr<Uptane::Target> findTargetHelper(const Uptane::Targets &cur_targets,
const Uptane::Target &queried_target, int level, bool terminating);

template <class T, class... Args>
void sendEvent(Args &&... args) {
Expand Down
1 change: 1 addition & 0 deletions src/libaktualizr/storage/invstorage.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ class INvStorage {
virtual void clearMetadata() = 0;
virtual void storeDelegation(const std::string& data, Uptane::Role role) = 0;
virtual bool loadDelegation(std::string* data, Uptane::Role role) = 0;
virtual void deleteDelegation(Uptane::Role role) = 0;
virtual void clearDelegations() = 0;

virtual void storeDeviceId(const std::string& device_id) = 0;
Expand Down
7 changes: 7 additions & 0 deletions src/libaktualizr/storage/sqlstorage.cc
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,13 @@ bool SQLStorage::loadDelegation(std::string* data, const Uptane::Role role) {
return true;
}

void SQLStorage::deleteDelegation(const Uptane::Role role) {
SQLite3Guard db = dbConnection();

auto statement = db.prepareStatement<std::string>("DELETE FROM delegations WHERE role_name=?;", role.ToString());
statement.step();
}

void SQLStorage::clearDelegations() {
SQLite3Guard db = dbConnection();

Expand Down
1 change: 1 addition & 0 deletions src/libaktualizr/storage/sqlstorage.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ class SQLStorage : public SQLStorageBase, public INvStorage {
void clearMetadata() override;
void storeDelegation(const std::string& data, Uptane::Role role) override;
bool loadDelegation(std::string* data, Uptane::Role role) override;
void deleteDelegation(Uptane::Role role) override;
void clearDelegations() override;

void storeDeviceId(const std::string& device_id) override;
Expand Down
114 changes: 42 additions & 72 deletions src/libaktualizr/uptane/imagesrepository.cc
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
#include "imagesrepository.h"

#include <fnmatch.h>

namespace Uptane {

void ImagesRepository::resetMeta() {
resetRoot();
targets.clear();
targets.reset();
snapshot = Snapshot();
timestamp = TimestampMeta();
}
Expand Down Expand Up @@ -65,54 +63,42 @@ bool ImagesRepository::verifySnapshot(const std::string& snapshot_raw) {
return true;
}

bool ImagesRepository::verifyTargets(const std::string& targets_raw, const Uptane::Role& role) {
bool ImagesRepository::verifyTargets(const std::string& targets_raw) {
try {
const Json::Value targets_json = Utils::parseJSON(targets_raw);
const std::string canonical = Utils::jsonToCanonicalStr(targets_json);
// Delegated targets do not have their hashes stored in their parent.
if (role == Uptane::Role::Targets()) {
bool hash_exists = false;
for (const auto& it : snapshot.targets_hashes()) {
switch (it.type()) {
case Hash::Type::kSha256:
if (Hash(Hash::Type::kSha256, boost::algorithm::hex(Crypto::sha256digest(canonical))) != it) {
LOG_ERROR << "Hash verification for targets metadata failed";
return false;
}
hash_exists = true;
break;
case Hash::Type::kSha512:
if (Hash(Hash::Type::kSha512, boost::algorithm::hex(Crypto::sha512digest(canonical))) != it) {
LOG_ERROR << "Hash verification for targets metadata failed";
return false;
}
hash_exists = true;
break;
default:
break;
}
}
if (!hash_exists) {
LOG_ERROR << "No hash found for targets.json";
return false;
bool hash_exists = false;
for (const auto& it : snapshot.targets_hashes()) {
switch (it.type()) {
case Hash::Type::kSha256:
if (Hash(Hash::Type::kSha256, boost::algorithm::hex(Crypto::sha256digest(canonical))) != it) {
LOG_ERROR << "Hash verification for targets metadata failed";
return false;
}
hash_exists = true;
break;
case Hash::Type::kSha512:
if (Hash(Hash::Type::kSha512, boost::algorithm::hex(Crypto::sha512digest(canonical))) != it) {
LOG_ERROR << "Hash verification for targets metadata failed";
return false;
}
hash_exists = true;
break;
default:
break;
}
}
if (!hash_exists) {
LOG_ERROR << "No hash found for targets.json";
return false;
}

// Verify the signature:
std::shared_ptr<MetaWithKeys> signer;
if (role == Uptane::Role::Targets()) {
signer = std::make_shared<MetaWithKeys>(root);
} else {
// TODO: support nested delegations here. This currently assumes that all
// delegated targets are signed by the top-level targets.
signer = std::make_shared<MetaWithKeys>(targets["targets"]);
}
targets[role.ToString()] = Targets(RepositoryType::Image(), role, targets_json, signer);
auto signer = std::make_shared<MetaWithKeys>(root);
targets = std::make_shared<Uptane::Targets>(
Targets(RepositoryType::Image(), Uptane::Role::Targets(), targets_json, signer));

// Only compare targets version in snapshot metadata for top-level
// targets.json. Delegated target metadata versions are not tracked outside
// of their own metadata.
if (role == Uptane::Role::Targets() && targets[role.ToString()].version() != snapshot.targets_version()) {
if (targets->version() != snapshot.targets_version()) {
return false;
}
} catch (const Exception& e) {
Expand All @@ -123,37 +109,21 @@ bool ImagesRepository::verifyTargets(const std::string& targets_raw, const Uptan
return true;
}

std::unique_ptr<Uptane::Target> ImagesRepository::getTarget(const Uptane::Target& director_target) {
// Search for the target in the top-level targets metadata.
Uptane::Targets toplevel = targets["targets"];
auto it = std::find(toplevel.targets.cbegin(), toplevel.targets.cend(), director_target);
if (it != toplevel.targets.cend()) {
return std_::make_unique<Uptane::Target>(*it);
}
std::shared_ptr<Uptane::Targets> ImagesRepository::verifyDelegation(const std::string& delegation_raw,
const Uptane::Role& role,
const Targets& parent_target) {
try {
const Json::Value delegation_json = Utils::parseJSON(delegation_raw);
const std::string canonical = Utils::jsonToCanonicalStr(delegation_json);

// Check if the target matches any of the delegation paths.
for (const auto& delegate_name : toplevel.delegated_role_names_) {
Role delegate_role = Role::Delegation(delegate_name);
std::vector<std::string> patterns = toplevel.paths_for_role_[delegate_role];
for (const auto& pattern : patterns) {
if (fnmatch(pattern.c_str(), director_target.filename().c_str(), 0) != 0) {
continue;
}
// Match! Check delegation.
Uptane::Targets delegate = targets[delegate_name];
auto d_it = std::find(delegate.targets.cbegin(), delegate.targets.cend(), director_target);
if (d_it != delegate.targets.cend()) {
return std_::make_unique<Uptane::Target>(*d_it);
}
// TODO: recurse here if the delegation is non-terminating. For now,
// assume that all delegations are terminating.
if (!toplevel.terminating_role_[delegate_role]) {
LOG_ERROR << "Nested delegations are not currently supported.";
}
return std::unique_ptr<Uptane::Target>(nullptr);
}
// Verify the signature:
auto signer = std::make_shared<MetaWithKeys>(parent_target);
return std::make_shared<Uptane::Targets>(Targets(RepositoryType::Image(), role, delegation_json, signer));
} catch (const Exception& e) {
LOG_ERROR << "Signature verification for images targets metadata failed";
}
return std::unique_ptr<Uptane::Target>(nullptr);

return std::shared_ptr<Uptane::Targets>(nullptr);
}

} // namespace Uptane
Loading

0 comments on commit 252be62

Please sign in to comment.