Skip to content

Commit

Permalink
Enable verifying packages signatures
Browse files Browse the repository at this point in the history
  • Loading branch information
Hind-M committed Feb 20, 2024
1 parent 00219a5 commit ad86ef1
Show file tree
Hide file tree
Showing 25 changed files with 464 additions and 89 deletions.
2 changes: 2 additions & 0 deletions libmamba/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ set(
${LIBMAMBA_SOURCE_DIR}/core/package_fetcher.cpp
${LIBMAMBA_SOURCE_DIR}/core/package_paths.cpp
${LIBMAMBA_SOURCE_DIR}/core/query.cpp
${LIBMAMBA_SOURCE_DIR}/core/repo_checker_store.cpp
${LIBMAMBA_SOURCE_DIR}/core/run.cpp
${LIBMAMBA_SOURCE_DIR}/core/shell_init.cpp
${LIBMAMBA_SOURCE_DIR}/core/subdirdata.cpp
Expand Down Expand Up @@ -352,6 +353,7 @@ set(
${LIBMAMBA_INCLUDE_DIR}/mamba/core/progress_bar.hpp
${LIBMAMBA_INCLUDE_DIR}/mamba/core/pinning.hpp
${LIBMAMBA_INCLUDE_DIR}/mamba/core/query.hpp
${LIBMAMBA_INCLUDE_DIR}/mamba/core/repo_checker_store.hpp
${LIBMAMBA_INCLUDE_DIR}/mamba/core/run.hpp
${LIBMAMBA_INCLUDE_DIR}/mamba/core/shell_init.hpp
${LIBMAMBA_INCLUDE_DIR}/mamba/core/subdirdata.hpp
Expand Down
9 changes: 8 additions & 1 deletion libmamba/ext/solv-cpp/include/solv-cpp/solvable.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ namespace solv
auto md5() const -> std::string_view;
auto noarch() const -> std::string_view;
auto sha256() const -> std::string_view;
auto signatures() const -> std::string_view;
auto size() const -> std::size_t;
auto timestamp() const -> std::size_t;

Expand Down Expand Up @@ -190,7 +191,7 @@ namespace solv
void set_noarch(const std::string& str) const;

/**
* Set the sha256 has of the solvable file..
* Set the sha256 hash of the solvable file.
*
* This is not used by libsolv and is purely for data storing.
*
Expand All @@ -200,6 +201,12 @@ namespace solv
void set_sha256(raw_str_view str) const;
void set_sha256(const std::string& str) const;

/**
* Set the signatures of the solvable file.
*/
void set_signatures(raw_str_view str) const;
void set_signatures(const std::string& str) const;

/**
* Set the size of the solvable size.
*
Expand Down
20 changes: 20 additions & 0 deletions libmamba/ext/solv-cpp/src/solvable.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,26 @@ namespace solv
return set_sha256(str.c_str());
}

auto ObjSolvableViewConst::signatures() const -> std::string_view
{
// NOTE This returns the package signatures json object alongside other package info
// in the following format:
// {"info":{},"signatures":{"public_key":{"signature":"metadata_signature"}}}
return ptr_to_strview(
::solvable_lookup_str(const_cast<::Solvable*>(raw()), SOLVABLE_SIGNATUREDATA)
);
}

void ObjSolvableView::set_signatures(raw_str_view str) const
{
::solvable_set_str(raw(), SOLVABLE_SIGNATUREDATA, str);
}

void ObjSolvableView::set_signatures(const std::string& str) const
{
return set_signatures(str.c_str());
}

auto ObjSolvableViewConst::size() const -> std::size_t
{
return ::solvable_lookup_num(const_cast<::Solvable*>(raw()), SOLVABLE_DOWNLOADSIZE, 0);
Expand Down
8 changes: 8 additions & 0 deletions libmamba/include/mamba/core/context.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ namespace mamba
VerificationLevel safety_checks = VerificationLevel::Warn;
bool extra_safety_checks = false;
bool verify_artifacts = false;
// Leave this empty?
// Need to populate from server? from config?.... to think about it (TODO)
// if we just specify "channel0" it becomes "https://conda.anaconda.org/channel0" ...
// TODO test with multiple channels in there and check behavior: like uncommenting
// conda-forge as first channel
std::vector<std::string> trusted_channels = {
/*"conda-forge", */ "http://127.0.0.1:8000/get/channel0"
};
};


Expand Down
46 changes: 46 additions & 0 deletions libmamba/include/mamba/core/repo_checker_store.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) 2024, QuantStack and Mamba Contributors
//
// Distributed under the terms of the BSD 3-Clause License.
//
// The full license is in the file LICENSE, distributed with this software.

#ifndef MAMBA_CORE_REPO_CHECKER_STORE_HPP
#define MAMBA_CORE_REPO_CHECKER_STORE_HPP

#include <utility>
#include <vector>

#include "mamba/specs/channel.hpp"
#include "mamba/validation/repo_checker.hpp"

namespace mamba
{
class Context;
class ChannelContext;
class MultiPackageCache;

class RepoCheckerStore
{
public:

using Channel = specs::Channel;
using RepoChecker = validation::RepoChecker;
using repo_checker_list = std::vector<std::pair<Channel, RepoChecker>>;

[[nodiscard]] static auto
make(const Context& ctx, ChannelContext& cc, MultiPackageCache& caches) -> RepoCheckerStore;

explicit RepoCheckerStore(repo_checker_list checkers);

[[nodiscard]] auto find_checker(const Channel& chan) const -> const RepoChecker*;

[[nodiscard]] auto contains_checker(const Channel& chan) const -> bool;

[[nodiscard]] auto at_checker(const Channel& chan) const -> const RepoChecker&;

private:

repo_checker_list m_repo_checkers = {};
};
}
#endif
1 change: 1 addition & 0 deletions libmamba/include/mamba/solver/libsolv/database.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ namespace mamba::solver::libsolv
std::string_view url,
PipAsPythonDependency add = PipAsPythonDependency::No,
UseOnlyTarBz2 only_tar = UseOnlyTarBz2::No,
bool verify_packages = false,
RepodataParser parser = RepodataParser::Mamba
) -> expected_t<RepoInfo>;

Expand Down
11 changes: 11 additions & 0 deletions libmamba/include/mamba/validation/errors.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,5 +142,16 @@ namespace mamba::validation
index_error();
~index_error() override = default;
};

/**
* Error raised when the given signatures of a package are empty/invalid.
*/
class signatures_error : public trust_error
{
public:

signatures_error();
~signatures_error() override = default;
};
}
#endif
21 changes: 16 additions & 5 deletions libmamba/include/mamba/validation/repo_checker.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include <memory>
#include <string>
#include <string_view>

#include <nlohmann/json_fwd.hpp>

Expand Down Expand Up @@ -41,14 +42,23 @@ namespace mamba::validation
* @param ref_path Path to the reference directory, hosting trusted root metadata
* @param cache_path Path to the cache directory
*/
RepoChecker(Context& context, std::string base_url, fs::u8path ref_path, fs::u8path cache_path = "");
RepoChecker(
const Context& context,
std::string base_url,
fs::u8path ref_path,
fs::u8path cache_path = ""
);
RepoChecker(RepoChecker&&) noexcept;
~RepoChecker();

auto operator=(RepoChecker&&) noexcept -> RepoChecker&;

// Forwarding to a ``RepoIndexChecker`` implementation
void verify_index(const nlohmann::json& j) const;
void verify_index(const fs::u8path& p) const;
void
verify_package(const nlohmann::json& signed_data, const nlohmann::json& signatures) const;
void verify_package(const nlohmann::json& signed_data, std::string_view signatures) const;

void generate_index_checker();

Expand All @@ -58,20 +68,21 @@ namespace mamba::validation

private:

std::unique_ptr<RepoIndexChecker> p_index_checker;
std::reference_wrapper<const Context> m_context;

std::string m_base_url;
std::size_t m_root_version = 0;
fs::u8path m_ref_path;
fs::u8path m_cache_path;
Context& m_context;

std::size_t m_root_version;

auto initial_trusted_root() -> fs::u8path;
auto ref_root() -> fs::u8path;
auto cached_root() -> fs::u8path;

void persist_file(const fs::u8path& file_path);

std::unique_ptr<RepoIndexChecker> p_index_checker;

auto get_root_role(const TimeRef& time_reference) -> std::unique_ptr<RootRole>;
};
}
Expand Down
2 changes: 1 addition & 1 deletion libmamba/include/mamba/validation/update_framework.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ namespace mamba::validation
auto possible_update_files() -> std::vector<fs::u8path>;

virtual auto build_index_checker(
Context& context,
const Context& context,
const TimeRef& time_reference,
const std::string& url,
const fs::u8path& cache_path
Expand Down
4 changes: 2 additions & 2 deletions libmamba/include/mamba/validation/update_framework_v0_6.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ namespace mamba::validation::v0_6
* Return a ``RepoIndexChecker`` implementation (derived class) from repository base URL.
*/
auto build_index_checker(
Context& context,
const Context& context,
const TimeRef& time_reference,
const std::string& url,
const fs::u8path& cache_path
Expand Down Expand Up @@ -131,7 +131,7 @@ namespace mamba::validation::v0_6
* Return a ``RepoIndexChecker`` implementation (derived class) from repository base URL.
*/
auto build_index_checker(
Context& context,
const Context& context,
const TimeRef& time_reference,
const std::string& url,
const fs::u8path& cache_path
Expand Down
2 changes: 1 addition & 1 deletion libmamba/include/mamba/validation/update_framework_v1.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ namespace mamba::validation::v1
[[nodiscard]] auto self_keys() const -> RoleFullKeys override;

auto build_index_checker(
Context& context,
const Context& context,
const TimeRef& time_reference,
const std::string& url,
const fs::u8path& cache_path
Expand Down
9 changes: 7 additions & 2 deletions libmamba/src/api/configuration.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1224,9 +1224,14 @@ namespace mamba

insert(Configurable("experimental_repodata_parsing", &m_context.experimental_repodata_parsing)
.group("Basic")
.description("Enable experimental parsing of repodata.json using nl::json")
.description( //
"Enable experimental parsing of `repodata.json` using simdjson.\n"
"Default is `true`. `false` means libsolv is used.\n"
"This feature may be still under active development and not stable yet.\n"
)
.set_rc_configurable()
.set_env_var_names());
.set_env_var_names()
.set_post_merge_hook(detail::experimental_hook));

insert(Configurable("debug", &m_context.debug)
.group("Basic")
Expand Down
1 change: 1 addition & 0 deletions libmamba/src/core/package_database_loader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ namespace mamba
util::rsplit(subdir.metadata().url(), "/", 1).front(),
add_pip,
static_cast<solver::libsolv::UseOnlyTarBz2>(ctx.use_only_tar_bz2),
ctx.validation_params.verify_artifacts,
json_parser
);
}
Expand Down
83 changes: 83 additions & 0 deletions libmamba/src/core/repo_checker_store.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright (c) 2024, QuantStack and Mamba Contributors
//
// Distributed under the terms of the BSD 3-Clause License.
//
// The full license is in the file LICENSE, distributed with this software.

#include "mamba/core/channel_context.hpp"
#include "mamba/core/context.hpp"
#include "mamba/core/output.hpp"
#include "mamba/core/package_cache.hpp"
#include "mamba/core/repo_checker_store.hpp"
#include "mamba/core/subdirdata.hpp"

namespace mamba
{

auto RepoCheckerStore::make(const Context& ctx, ChannelContext& cc, MultiPackageCache& caches)
-> RepoCheckerStore
{
if (!ctx.validation_params.verify_artifacts)
{
return RepoCheckerStore({});
}

auto repo_checkers = repo_checker_list();
repo_checkers.reserve(ctx.validation_params.trusted_channels.size());
for (const auto& location : ctx.validation_params.trusted_channels)
{
for (auto& chan : cc.make_channel(location))
{
// Parametrization
auto url = chan.url().str(specs::CondaURL::Credentials::Show);
auto url_id = cache_name_from_url(url);
// TODO make these configurable?
auto ref_path = ctx.prefix_params.root_prefix / "etc" / "trusted-repos" / url_id;
auto cache_path = caches.first_writable_path() / "cache" / url_id;

LOG_INFO << "Creating RepoChecker with base_url: " << url
<< ", ref_path: " << ref_path << ", and cache_path: " << cache_path;

auto checker = RepoChecker(ctx, std::move(url), std::move(ref_path), cache_path);

// Initialization
fs::create_directories(checker.cache_path());
checker.generate_index_checker();

repo_checkers.emplace_back(std::move(chan), std::move(checker));
}
}
return RepoCheckerStore(std::move(repo_checkers));
}

RepoCheckerStore::RepoCheckerStore(repo_checker_list checkers)
: m_repo_checkers(std::move(checkers))
{
}

auto RepoCheckerStore::find_checker(const Channel& chan) const -> const RepoChecker*
{
for (auto& [candidate_chan, checker] : m_repo_checkers)
{
if (candidate_chan.contains_equivalent(chan))
{
return &checker;
}
}
return nullptr;
}

auto RepoCheckerStore::contains_checker(const Channel& chan) const -> bool
{
return find_checker(chan) != nullptr;
}

auto RepoCheckerStore::at_checker(const Channel& chan) const -> const RepoChecker&
{
if (auto ptr = find_checker(chan))
{
return *ptr;
}
throw std::range_error("Checker not found");
}
}
Loading

0 comments on commit ad86ef1

Please sign in to comment.