mirror of https://github.com/mamba-org/mamba.git
Split validate.[ch]pp (#3041)
* Split validation errors * Split validation keys * Fix header include * Split validation versioned namespaces * Trailing return types * Split update framework implementation * Finish validation split * Fix missing header
This commit is contained in:
parent
3cb488bb61
commit
c529199eef
|
@ -178,6 +178,14 @@ set(
|
|||
${LIBMAMBA_SOURCE_DIR}/specs/repo_data.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/version.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/specs/version_spec.cpp
|
||||
# Artifacts validation
|
||||
${LIBMAMBA_SOURCE_DIR}/validation/tools.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/validation/errors.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/validation/keys.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/validation/update_framework.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/validation/update_framework_v0_6.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/validation/update_framework_v1.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/validation/repo_checker.cpp
|
||||
# C++ wrapping of libcurl
|
||||
${LIBMAMBA_SOURCE_DIR}/core/curl.cpp
|
||||
# C++ wrapping of compression libs (zstd and bzlib)
|
||||
|
@ -220,7 +228,6 @@ set(
|
|||
${LIBMAMBA_SOURCE_DIR}/core/util.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/core/fsutil.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/core/util_os.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/core/validate.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/core/virtual_packages.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/core/env_lockfile.cpp
|
||||
${LIBMAMBA_SOURCE_DIR}/core/execution.cpp
|
||||
|
@ -283,6 +290,14 @@ set(
|
|||
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/repo_data.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/version.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/specs/version_spec.hpp
|
||||
# Artifacts validation
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/validation/tools.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/validation/errors.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/validation/keys.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/validation/update_framework.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/validation/update_framework_v0_6.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/validation/update_framework_v1.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/validation/repo_checker.hpp
|
||||
# Core API (low-level)
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/core/activation.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/core/channel_context.hpp
|
||||
|
@ -323,7 +338,6 @@ set(
|
|||
${LIBMAMBA_INCLUDE_DIR}/mamba/core/util_os.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/core/util_random.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/core/util_scope.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/core/validate.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/core/virtual_packages.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/core/env_lockfile.hpp
|
||||
${LIBMAMBA_INCLUDE_DIR}/mamba/core/tasksync.hpp
|
||||
|
|
|
@ -1,807 +0,0 @@
|
|||
// Copyright (c) 2019, 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_VALIDATE_HPP
|
||||
#define MAMBA_CORE_VALIDATE_HPP
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "mamba/core/timeref.hpp"
|
||||
#include "mamba/core/util.hpp"
|
||||
#include "mamba/fs/filesystem.hpp"
|
||||
|
||||
namespace mamba::validation
|
||||
{
|
||||
[[nodiscard]] auto sha256sum(const fs::u8path& path) -> std::string_view;
|
||||
|
||||
[[nodiscard]] auto md5sum(const fs::u8path& path) -> std::string_view;
|
||||
|
||||
bool file_size(const fs::u8path& path, std::uintmax_t validation);
|
||||
|
||||
inline constexpr std::size_t MAMBA_SHA256_SIZE_HEX = 64;
|
||||
inline constexpr std::size_t MAMBA_SHA256_SIZE_BYTES = 32;
|
||||
inline constexpr std::size_t MAMBA_MD5_SIZE_HEX = 32;
|
||||
inline constexpr std::size_t MAMBA_MD5_SIZE_BYTES = 16;
|
||||
inline constexpr std::size_t MAMBA_ED25519_KEYSIZE_HEX = 64;
|
||||
inline constexpr std::size_t MAMBA_ED25519_KEYSIZE_BYTES = 32;
|
||||
inline constexpr std::size_t MAMBA_ED25519_SIGSIZE_HEX = 128;
|
||||
inline constexpr std::size_t MAMBA_ED25519_SIGSIZE_BYTES = 64;
|
||||
|
||||
int generate_ed25519_keypair(std::byte* pk, std::byte* sk);
|
||||
std::pair<std::array<std::byte, MAMBA_ED25519_KEYSIZE_BYTES>, std::array<std::byte, MAMBA_ED25519_KEYSIZE_BYTES>>
|
||||
generate_ed25519_keypair();
|
||||
std::pair<std::string, std::string> generate_ed25519_keypair_hex();
|
||||
|
||||
int sign(const std::string& data, const std::byte* sk, std::byte* signature);
|
||||
int sign(const std::string& data, const std::string& sk, std::string& signature);
|
||||
|
||||
std::array<std::byte, MAMBA_ED25519_SIGSIZE_BYTES>
|
||||
ed25519_sig_hex_to_bytes(const std::string& sig_hex, int& error_code) noexcept;
|
||||
|
||||
std::array<std::byte, MAMBA_ED25519_KEYSIZE_BYTES>
|
||||
ed25519_key_hex_to_bytes(const std::string& key_hex, int& error_code) noexcept;
|
||||
|
||||
int
|
||||
verify(const std::byte* data, std::size_t data_len, const std::byte* pk, const std::byte* signature);
|
||||
int verify(const std::string& data, const std::byte* pk, const std::byte* signature);
|
||||
int verify(const std::string& data, const std::string& pk_hex, const std::string& signature_hex);
|
||||
|
||||
/**
|
||||
* Verify a GPG/PGP signature against the hash of the binary data and
|
||||
* the additional trailer added in V4 signature.
|
||||
* See RFC4880, section 5.2.4 https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.4
|
||||
* This method assumes hash function to be SHA-256
|
||||
*/
|
||||
int verify_gpg_hashed_msg(const std::byte* data, const std::byte* pk, const std::byte* signature);
|
||||
int
|
||||
verify_gpg_hashed_msg(const std::string& data, const std::byte* pk, const std::byte* signature);
|
||||
int
|
||||
verify_gpg_hashed_msg(const std::string& data, const std::string& pk, const std::string& signature);
|
||||
|
||||
/**
|
||||
* Verify a GPG/PGP signature against the binary data and
|
||||
* the additional trailer added in V4 signature.
|
||||
* See RFC4880, section 5.2.4 https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.4
|
||||
* This method assumes hash function to be SHA-256
|
||||
*/
|
||||
int verify_gpg(
|
||||
const std::string& data,
|
||||
const std::string& gpg_v4_trailer,
|
||||
const std::string& pk,
|
||||
const std::string& signature
|
||||
);
|
||||
|
||||
/**
|
||||
* Base class for artifact/package verification error.
|
||||
*/
|
||||
class trust_error : public std::exception
|
||||
{
|
||||
public:
|
||||
|
||||
trust_error(std::string_view message);
|
||||
~trust_error() override = default;
|
||||
[[nodiscard]] auto what() const noexcept -> const char* override;
|
||||
|
||||
private:
|
||||
|
||||
std::string m_message;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Error raised when a threshold of signatures is
|
||||
* not met. This can be due to wrong signatures,
|
||||
* wrong or missing public keys.
|
||||
*/
|
||||
class threshold_error : public trust_error
|
||||
{
|
||||
public:
|
||||
|
||||
threshold_error();
|
||||
~threshold_error() override = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Error raised when wrong metadata are spotted
|
||||
* in a role file.
|
||||
*/
|
||||
class role_metadata_error : public trust_error
|
||||
{
|
||||
public:
|
||||
|
||||
role_metadata_error();
|
||||
~role_metadata_error() override = default;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Error raised when a wrong file name is
|
||||
* detected for role metadata.
|
||||
*/
|
||||
class role_file_error : public trust_error
|
||||
{
|
||||
public:
|
||||
|
||||
role_file_error();
|
||||
~role_file_error() override = default;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Error raised when a possible rollback
|
||||
* attack is detected.
|
||||
*/
|
||||
class rollback_error : public trust_error
|
||||
{
|
||||
public:
|
||||
|
||||
rollback_error();
|
||||
~rollback_error() override = default;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Error raised when a possible freeze
|
||||
* attack is detected.
|
||||
*/
|
||||
class freeze_error : public trust_error
|
||||
{
|
||||
public:
|
||||
|
||||
freeze_error();
|
||||
~freeze_error() override = default;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Error raised when a spec version is either
|
||||
* wrong/invalid or not supported by the client.
|
||||
*/
|
||||
class spec_version_error : public trust_error
|
||||
{
|
||||
public:
|
||||
|
||||
spec_version_error();
|
||||
~spec_version_error() override = default;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Error raised when a role metadata file
|
||||
* fetching process fails.
|
||||
*/
|
||||
class fetching_error : public trust_error
|
||||
{
|
||||
public:
|
||||
|
||||
fetching_error();
|
||||
~fetching_error() override = default;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Error raised when signatures threshold
|
||||
* is not met for a package.
|
||||
*/
|
||||
class package_error : public trust_error
|
||||
{
|
||||
public:
|
||||
|
||||
package_error();
|
||||
~package_error() override = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Error raised when signatures threshold
|
||||
* is not met for a trust role.
|
||||
*/
|
||||
class role_error : public trust_error
|
||||
{
|
||||
public:
|
||||
|
||||
role_error();
|
||||
~role_error() override = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Error raised when an invalid package
|
||||
* index is met.
|
||||
*/
|
||||
class index_error : public trust_error
|
||||
{
|
||||
public:
|
||||
|
||||
index_error();
|
||||
~index_error() override = default;
|
||||
};
|
||||
|
||||
void check_timestamp_metadata_format(const std::string& ts);
|
||||
|
||||
/**
|
||||
* Representation of the public part of a
|
||||
* cryptographic key pair.
|
||||
*/
|
||||
struct Key
|
||||
{
|
||||
std::string keytype = "";
|
||||
std::string scheme = "";
|
||||
std::string keyval = "";
|
||||
|
||||
static Key from_ed25519(std::string keyval)
|
||||
{
|
||||
return { "ed25519", "ed25519", keyval };
|
||||
}
|
||||
};
|
||||
|
||||
void to_json(nlohmann::json& j, const Key& k);
|
||||
void from_json(const nlohmann::json& j, Key& k);
|
||||
|
||||
|
||||
/**
|
||||
* Representation of a role signature.
|
||||
* Optional 'pgp_trailer' will trigger special
|
||||
* handling during verification to conform to
|
||||
* OpenPGP RFC4880.
|
||||
*/
|
||||
struct RoleSignature
|
||||
{
|
||||
std::string keyid = "";
|
||||
std::string sig = "";
|
||||
std::string pgp_trailer = "";
|
||||
};
|
||||
|
||||
void to_json(nlohmann::json& j, const RoleSignature& rs);
|
||||
void from_json(const nlohmann::json& j, RoleSignature& rs);
|
||||
|
||||
bool operator<(const RoleSignature& rs1, const RoleSignature& rs2);
|
||||
|
||||
|
||||
/**
|
||||
* Store key IDs and threshold for a role.
|
||||
* Key ID can be a hash of Key, or just
|
||||
* its public key value.
|
||||
*/
|
||||
struct RoleKeys
|
||||
{
|
||||
std::vector<std::string> keyids;
|
||||
std::size_t threshold;
|
||||
};
|
||||
|
||||
void to_json(nlohmann::json& j, const RoleKeys& rk);
|
||||
void from_json(const nlohmann::json& j, RoleKeys& rk);
|
||||
|
||||
|
||||
/**
|
||||
* Store key values and threshold for role.
|
||||
* Assumes key scheme/type is `ed25519`.
|
||||
*/
|
||||
struct RolePubKeys
|
||||
{
|
||||
std::vector<std::string> pubkeys;
|
||||
std::size_t threshold;
|
||||
|
||||
RoleKeys to_role_keys() const;
|
||||
};
|
||||
|
||||
void to_json(nlohmann::json& j, const RolePubKeys& rk);
|
||||
void from_json(const nlohmann::json& j, RolePubKeys& rk);
|
||||
|
||||
|
||||
/**
|
||||
* Store full keys and threshold for role.
|
||||
*/
|
||||
struct RoleFullKeys
|
||||
{
|
||||
RoleFullKeys() = default;
|
||||
RoleFullKeys(const std::map<std::string, Key>& keys_, const std::size_t& threshold_);
|
||||
|
||||
std::map<std::string, Key> keys;
|
||||
std::size_t threshold;
|
||||
|
||||
std::map<std::string, Key> to_keys() const;
|
||||
RoleKeys to_roles() const;
|
||||
};
|
||||
|
||||
void to_json(nlohmann::json& j, const RoleFullKeys& r);
|
||||
void from_json(const nlohmann::json& j, RoleFullKeys& r);
|
||||
|
||||
|
||||
/**
|
||||
* Base class for spec implementations.
|
||||
*/
|
||||
class SpecBase
|
||||
{
|
||||
public:
|
||||
|
||||
virtual ~SpecBase() = default;
|
||||
|
||||
std::string version_str() const;
|
||||
|
||||
virtual std::string canonicalize(const nlohmann::json& j) const;
|
||||
|
||||
std::string compatible_prefix() const;
|
||||
std::vector<std::string> upgrade_prefix() const;
|
||||
|
||||
bool is_compatible(const fs::u8path& p) const;
|
||||
bool is_compatible(const nlohmann::json& j) const;
|
||||
bool is_compatible(const std::string& version) const;
|
||||
|
||||
bool is_upgrade(const nlohmann::json& j) const;
|
||||
bool is_upgrade(const std::string& version) const;
|
||||
|
||||
virtual bool upgradable() const;
|
||||
|
||||
virtual std::string json_key() const = 0;
|
||||
virtual std::string expiration_json_key() const = 0;
|
||||
|
||||
virtual std::set<RoleSignature> signatures(const nlohmann::json& j) const = 0;
|
||||
|
||||
protected:
|
||||
|
||||
SpecBase(const std::string& spec_version);
|
||||
SpecBase() = delete;
|
||||
|
||||
std::string get_json_value(const nlohmann::json& j) const;
|
||||
|
||||
private:
|
||||
|
||||
std::string m_spec_version;
|
||||
};
|
||||
|
||||
bool operator==(const SpecBase& sv1, const SpecBase& sv2);
|
||||
bool operator!=(const SpecBase& sv1, const SpecBase& sv2);
|
||||
|
||||
|
||||
/**
|
||||
* Base class for role implementation.
|
||||
*/
|
||||
class RoleBase
|
||||
{
|
||||
public:
|
||||
|
||||
RoleBase(const std::string& type, std::shared_ptr<SpecBase> sv);
|
||||
|
||||
virtual ~RoleBase() = 0;
|
||||
|
||||
std::string type() const;
|
||||
SpecBase& spec_version() const;
|
||||
std::size_t version() const;
|
||||
std::string file_ext() const;
|
||||
std::string expires() const;
|
||||
|
||||
bool expired(const TimeRef& time_reference) const;
|
||||
|
||||
std::set<std::string> roles() const;
|
||||
std::set<RoleSignature> signatures(const nlohmann::json& j) const;
|
||||
|
||||
virtual RoleFullKeys self_keys() const = 0;
|
||||
std::map<std::string, RoleFullKeys> all_keys() const;
|
||||
|
||||
friend void to_json(nlohmann::json& j, const RoleBase* r);
|
||||
friend void from_json(const nlohmann::json& j, RoleBase* r);
|
||||
|
||||
protected:
|
||||
|
||||
nlohmann::json read_json_file(const fs::u8path& p, bool update = false) const;
|
||||
|
||||
/**
|
||||
* Check that a threshold of valid signatures is met
|
||||
* for the signed metadata of a role, using another
|
||||
* role keys (possibly the same).
|
||||
* Both signed and signatures metadata are contained
|
||||
* in 'data'.
|
||||
*/
|
||||
void check_role_signatures(const nlohmann::json& data, const RoleBase& role);
|
||||
/**
|
||||
* Check that a threshold of valid signatures is met
|
||||
* for the signed metadata, using a set of keys.
|
||||
*/
|
||||
void check_signatures(
|
||||
const std::string& signed_data,
|
||||
const std::set<RoleSignature>& signatures,
|
||||
const RoleFullKeys& keyring
|
||||
) const;
|
||||
|
||||
void set_spec_version(std::shared_ptr<SpecBase> sv);
|
||||
void set_expiration(const std::string& expires);
|
||||
|
||||
// Forwarding to spec implementation
|
||||
std::string canonicalize(const nlohmann::json& j) const;
|
||||
// Return the spec implementation
|
||||
std::shared_ptr<SpecBase> spec_impl() const;
|
||||
|
||||
// Mandatory roles defined by the current role
|
||||
virtual std::set<std::string> mandatory_defined_roles() const;
|
||||
// Optional roles defined by the current role
|
||||
virtual std::set<std::string> optionally_defined_roles() const;
|
||||
|
||||
// Check role
|
||||
void check_expiration_format() const;
|
||||
void check_defined_roles(bool allow_any = false) const;
|
||||
|
||||
std::map<std::string, RoleFullKeys> m_defined_roles;
|
||||
|
||||
private:
|
||||
|
||||
std::string m_internal_type;
|
||||
std::string m_type;
|
||||
std::shared_ptr<SpecBase> p_spec;
|
||||
std::size_t m_version = 1;
|
||||
std::string m_expires;
|
||||
std::string m_ext = "json";
|
||||
};
|
||||
|
||||
// Forward declaration of RepoIndexChecker.
|
||||
class RepoIndexChecker;
|
||||
|
||||
/**
|
||||
* 'root' role interface.
|
||||
*/
|
||||
class RootRole : public RoleBase
|
||||
{
|
||||
public:
|
||||
|
||||
virtual ~RootRole() = default;
|
||||
|
||||
std::unique_ptr<RootRole> update(fs::u8path path);
|
||||
std::unique_ptr<RootRole> update(nlohmann::json j);
|
||||
|
||||
std::vector<fs::u8path> possible_update_files();
|
||||
|
||||
virtual std::unique_ptr<RepoIndexChecker>
|
||||
build_index_checker(Context& context, const TimeRef& time_reference, const std::string& url, const fs::u8path& cache_path) const = 0;
|
||||
|
||||
protected:
|
||||
|
||||
RootRole(std::shared_ptr<SpecBase> spec);
|
||||
|
||||
private:
|
||||
|
||||
virtual std::unique_ptr<RootRole> create_update(const nlohmann::json& j) = 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Interface that performs validity checks
|
||||
* on a repository packages index.
|
||||
*/
|
||||
class RepoIndexChecker
|
||||
{
|
||||
public:
|
||||
|
||||
virtual ~RepoIndexChecker() = default;
|
||||
virtual void verify_index(const nlohmann::json& j) const = 0;
|
||||
virtual void verify_index(const fs::u8path& p) const = 0;
|
||||
virtual void verify_package(const nlohmann::json& signed_data, const nlohmann::json& signatures) const = 0;
|
||||
|
||||
protected:
|
||||
|
||||
RepoIndexChecker() = default;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Perform security check against a repository
|
||||
* package index using cryptographic signatures.
|
||||
* Relies on multiple roles defined in TUF specification.
|
||||
*/
|
||||
class RepoChecker
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param base_url Repository base URL
|
||||
* @param ref_path Path to the reference directory, hosting trusted root metadata
|
||||
* @param cache_path Path to the cache directory
|
||||
*/
|
||||
RepoChecker(
|
||||
Context& context,
|
||||
const std::string& base_url,
|
||||
const fs::u8path& ref_path,
|
||||
const fs::u8path& cache_path = ""
|
||||
);
|
||||
|
||||
// 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 generate_index_checker();
|
||||
|
||||
const fs::u8path& cache_path();
|
||||
|
||||
std::size_t root_version();
|
||||
|
||||
private:
|
||||
|
||||
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;
|
||||
|
||||
fs::u8path initial_trusted_root();
|
||||
fs::u8path ref_root();
|
||||
fs::u8path cached_root();
|
||||
|
||||
void persist_file(const fs::u8path& file_path);
|
||||
|
||||
std::unique_ptr<RepoIndexChecker> p_index_checker;
|
||||
|
||||
std::unique_ptr<RootRole> get_root_role(const TimeRef& time_reference);
|
||||
};
|
||||
|
||||
|
||||
namespace v1
|
||||
{
|
||||
/**
|
||||
* TUF v1 specific implementation.
|
||||
*/
|
||||
class SpecImpl final : public SpecBase
|
||||
{
|
||||
public:
|
||||
|
||||
SpecImpl(const std::string& sv = "1.0.17");
|
||||
|
||||
std::string json_key() const override;
|
||||
std::string expiration_json_key() const override;
|
||||
|
||||
std::set<RoleSignature> signatures(const nlohmann::json& j) const override;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 'root' role implementation.
|
||||
* TUF v1.0.17 §2.1.1
|
||||
* https://theupdateframework.github.io/specification/latest/#root
|
||||
*/
|
||||
class RootImpl final : public RootRole
|
||||
{
|
||||
public:
|
||||
|
||||
RootImpl(const fs::u8path& p);
|
||||
RootImpl(const nlohmann::json& j);
|
||||
|
||||
RoleFullKeys self_keys() const override;
|
||||
|
||||
std::unique_ptr<RepoIndexChecker> build_index_checker(
|
||||
Context& context,
|
||||
const TimeRef& time_reference,
|
||||
const std::string& url,
|
||||
const fs::u8path& cache_path
|
||||
) const override;
|
||||
|
||||
friend void to_json(nlohmann::json& j, const RootImpl& r);
|
||||
friend void from_json(const nlohmann::json& j, RootImpl& r);
|
||||
|
||||
private:
|
||||
|
||||
RootImpl() = delete;
|
||||
|
||||
void load_from_json(const nlohmann::json& j);
|
||||
|
||||
std::unique_ptr<RootRole> create_update(const nlohmann::json& j) override;
|
||||
|
||||
std::set<std::string> mandatory_defined_roles() const override;
|
||||
std::set<std::string> optionally_defined_roles() const override;
|
||||
|
||||
void
|
||||
set_defined_roles(std::map<std::string, Key> keys, std::map<std::string, RoleKeys> roles);
|
||||
};
|
||||
}
|
||||
|
||||
namespace v06
|
||||
{
|
||||
/**
|
||||
* ``conda-content-trust`` v0.6.0 specific implementation.
|
||||
* This is a variation of TUF specification.
|
||||
*/
|
||||
class SpecImpl final : public SpecBase
|
||||
{
|
||||
public:
|
||||
|
||||
SpecImpl(const std::string& sv = "0.6.0");
|
||||
|
||||
std::string json_key() const override;
|
||||
std::string expiration_json_key() const override;
|
||||
|
||||
std::set<RoleSignature> signatures(const nlohmann::json& j) const override;
|
||||
|
||||
std::string canonicalize(const nlohmann::json& j) const override;
|
||||
bool upgradable() const override;
|
||||
};
|
||||
|
||||
class V06RoleBaseExtension
|
||||
{
|
||||
public:
|
||||
|
||||
void set_timestamp(const std::string& ts);
|
||||
|
||||
std::string timestamp() const;
|
||||
|
||||
protected:
|
||||
|
||||
std::string m_timestamp;
|
||||
|
||||
void check_timestamp_format() const;
|
||||
};
|
||||
|
||||
|
||||
// Forward declaration of KeyMgrRole.
|
||||
class KeyMgrRole;
|
||||
|
||||
/**
|
||||
* 'root' role implementation.
|
||||
*/
|
||||
class RootImpl final
|
||||
: public RootRole
|
||||
, public V06RoleBaseExtension
|
||||
{
|
||||
public:
|
||||
|
||||
RootImpl(const fs::u8path& p);
|
||||
RootImpl(const nlohmann::json& j);
|
||||
RootImpl(const std::string& json_str);
|
||||
|
||||
/**
|
||||
* Return a ``RepoIndexChecker`` implementation (derived class)
|
||||
* from repository base URL.
|
||||
*/
|
||||
std::unique_ptr<RepoIndexChecker> build_index_checker(
|
||||
Context& context,
|
||||
const TimeRef& time_reference,
|
||||
const std::string& url,
|
||||
const fs::u8path& cache_path
|
||||
) const override;
|
||||
|
||||
RoleFullKeys self_keys() const override;
|
||||
|
||||
nlohmann::json upgraded_signable() const;
|
||||
RoleSignature
|
||||
upgraded_signature(const nlohmann::json& j, const std::string& pk, const std::byte* sk) const;
|
||||
|
||||
KeyMgrRole create_key_mgr(const fs::u8path& p) const;
|
||||
KeyMgrRole create_key_mgr(const nlohmann::json& j) const;
|
||||
|
||||
friend void to_json(nlohmann::json& j, const RootImpl& r);
|
||||
friend void from_json(const nlohmann::json& j, RootImpl& r);
|
||||
|
||||
private:
|
||||
|
||||
RootImpl() = delete;
|
||||
|
||||
void load_from_json(const nlohmann::json& j);
|
||||
|
||||
std::unique_ptr<RootRole> create_update(const nlohmann::json& j) override;
|
||||
|
||||
std::set<std::string> mandatory_defined_roles() const override;
|
||||
std::set<std::string> optionally_defined_roles() const override;
|
||||
|
||||
void set_defined_roles(std::map<std::string, RolePubKeys> keys);
|
||||
};
|
||||
|
||||
|
||||
// Forward declaration of KeyMgrRole.
|
||||
class PkgMgrRole;
|
||||
|
||||
/**
|
||||
* 'key_mgr' role implementation.
|
||||
*/
|
||||
class KeyMgrRole final
|
||||
: public RoleBase
|
||||
, public V06RoleBaseExtension
|
||||
{
|
||||
public:
|
||||
|
||||
KeyMgrRole(const fs::u8path& p, const RoleFullKeys& keys, const std::shared_ptr<SpecBase> spec);
|
||||
KeyMgrRole(
|
||||
const nlohmann::json& j,
|
||||
const RoleFullKeys& keys,
|
||||
const std::shared_ptr<SpecBase> spec
|
||||
);
|
||||
KeyMgrRole(
|
||||
const std::string& json_str,
|
||||
const RoleFullKeys& keys,
|
||||
const std::shared_ptr<SpecBase> spec
|
||||
);
|
||||
|
||||
// std::set<std::string> roles() const override;
|
||||
RoleFullKeys self_keys() const override;
|
||||
|
||||
PkgMgrRole create_pkg_mgr(const fs::u8path& p) const;
|
||||
PkgMgrRole create_pkg_mgr(const nlohmann::json& j) const;
|
||||
|
||||
/**
|
||||
* Return a ``RepoIndexChecker`` implementation (derived class)
|
||||
* from repository base URL.
|
||||
*/
|
||||
std::unique_ptr<RepoIndexChecker> build_index_checker(
|
||||
Context& context,
|
||||
const TimeRef& time_reference,
|
||||
const std::string& url,
|
||||
const fs::u8path& cache_path
|
||||
) const;
|
||||
|
||||
friend void to_json(nlohmann::json& j, const KeyMgrRole& r);
|
||||
friend void from_json(const nlohmann::json& j, KeyMgrRole& r);
|
||||
|
||||
private:
|
||||
|
||||
KeyMgrRole() = delete;
|
||||
|
||||
void load_from_json(const nlohmann::json& j);
|
||||
|
||||
RoleFullKeys m_keys;
|
||||
std::map<std::string, RolePubKeys> m_delegations;
|
||||
|
||||
std::set<std::string> mandatory_defined_roles() const override;
|
||||
std::set<std::string> optionally_defined_roles() const override;
|
||||
|
||||
void set_defined_roles(std::map<std::string, RolePubKeys> keys);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 'pkg_mgr' role implementation.
|
||||
* This role inherits from ``RepoIndexChecker`` and
|
||||
* will be used by ``RepoChecker`` to perform the
|
||||
* repository index verification.
|
||||
*/
|
||||
class PkgMgrRole final
|
||||
: public RoleBase
|
||||
, public V06RoleBaseExtension
|
||||
, public RepoIndexChecker
|
||||
{
|
||||
public:
|
||||
|
||||
PkgMgrRole(const RoleFullKeys& keys, const std::shared_ptr<SpecBase> spec);
|
||||
PkgMgrRole(const fs::u8path& p, const RoleFullKeys& keys, const std::shared_ptr<SpecBase> spec);
|
||||
PkgMgrRole(
|
||||
const nlohmann::json& j,
|
||||
const RoleFullKeys& keys,
|
||||
const std::shared_ptr<SpecBase> spec
|
||||
);
|
||||
PkgMgrRole(
|
||||
const std::string& json_str,
|
||||
const RoleFullKeys& keys,
|
||||
const std::shared_ptr<SpecBase> spec
|
||||
);
|
||||
|
||||
void verify_index(const fs::u8path& p) const override;
|
||||
void verify_index(const nlohmann::json& j) const override;
|
||||
void verify_package(const nlohmann::json& signed_data, const nlohmann::json& signatures)
|
||||
const override;
|
||||
|
||||
friend void to_json(nlohmann::json& j, const PkgMgrRole& r);
|
||||
friend void from_json(const nlohmann::json& j, PkgMgrRole& r);
|
||||
|
||||
private:
|
||||
|
||||
PkgMgrRole() = delete;
|
||||
|
||||
void load_from_json(const nlohmann::json& j);
|
||||
|
||||
RoleFullKeys self_keys() const override;
|
||||
std::set<RoleSignature> pkg_signatures(const nlohmann::json& j) const;
|
||||
void
|
||||
check_pkg_signatures(const nlohmann::json& signed_data, const nlohmann::json& signatures) const;
|
||||
|
||||
void set_defined_roles(std::map<std::string, RolePubKeys> keys);
|
||||
|
||||
RoleFullKeys m_keys;
|
||||
|
||||
friend class KeyMgrRole;
|
||||
};
|
||||
}
|
||||
} // namespace validate
|
||||
|
||||
#endif // MAMBA_VALIDATE_HPP
|
|
@ -0,0 +1,146 @@
|
|||
// Copyright (c) 2023, 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_VALIDATION_ERRORS_HPP
|
||||
#define MAMBA_VALIDATION_ERRORS_HPP
|
||||
|
||||
#include <exception>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace mamba::validation
|
||||
{
|
||||
/**
|
||||
* Base class for artifact/package verification error.
|
||||
*/
|
||||
class trust_error : public std::exception
|
||||
{
|
||||
public:
|
||||
|
||||
trust_error(std::string_view message);
|
||||
~trust_error() override = default;
|
||||
[[nodiscard]] auto what() const noexcept -> const char* override;
|
||||
|
||||
private:
|
||||
|
||||
std::string m_message;
|
||||
};
|
||||
|
||||
/**
|
||||
* Error raised when a threshold of signatures is not met.
|
||||
*
|
||||
* This can be due to wrong signatures, wrong or missing public keys.
|
||||
*/
|
||||
class threshold_error : public trust_error
|
||||
{
|
||||
public:
|
||||
|
||||
threshold_error();
|
||||
~threshold_error() override = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Error raised when wrong metadata are spotted
|
||||
* in a role file.
|
||||
*/
|
||||
class role_metadata_error : public trust_error
|
||||
{
|
||||
public:
|
||||
|
||||
role_metadata_error();
|
||||
~role_metadata_error() override = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Error raised when a wrong file name is
|
||||
* detected for role metadata.
|
||||
*/
|
||||
class role_file_error : public trust_error
|
||||
{
|
||||
public:
|
||||
|
||||
role_file_error();
|
||||
~role_file_error() override = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Error raised when a possible rollback attack is detected.
|
||||
*/
|
||||
class rollback_error : public trust_error
|
||||
{
|
||||
public:
|
||||
|
||||
rollback_error();
|
||||
~rollback_error() override = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Error raised when a possible freeze attack is detected.
|
||||
*/
|
||||
class freeze_error : public trust_error
|
||||
{
|
||||
public:
|
||||
|
||||
freeze_error();
|
||||
~freeze_error() override = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Error raised when a spec version is either wrong/invalid or not supported by the client.
|
||||
*/
|
||||
class spec_version_error : public trust_error
|
||||
{
|
||||
public:
|
||||
|
||||
spec_version_error();
|
||||
~spec_version_error() override = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Error raised when a role metadata file fetching process fails.
|
||||
*/
|
||||
class fetching_error : public trust_error
|
||||
{
|
||||
public:
|
||||
|
||||
fetching_error();
|
||||
~fetching_error() override = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Error raised when signatures threshold is not met for a package.
|
||||
*/
|
||||
class package_error : public trust_error
|
||||
{
|
||||
public:
|
||||
|
||||
package_error();
|
||||
~package_error() override = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Error raised when signatures threshold is not met for a trust role.
|
||||
*/
|
||||
class role_error : public trust_error
|
||||
{
|
||||
public:
|
||||
|
||||
role_error();
|
||||
~role_error() override = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Error raised when an invalid package index is met.
|
||||
*/
|
||||
class index_error : public trust_error
|
||||
{
|
||||
public:
|
||||
|
||||
index_error();
|
||||
~index_error() override = default;
|
||||
};
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,100 @@
|
|||
// Copyright (c) 2019, 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_VALIDATION_UPDATE_FRAMEWORK_ROLES_HPP
|
||||
#define MAMBA_VALIDATION_UPDATE_FRAMEWORK_ROLES_HPP
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
|
||||
namespace mamba::validation
|
||||
{
|
||||
/**
|
||||
* Representation of the public part of a cryptographic key pair.
|
||||
*/
|
||||
struct Key
|
||||
{
|
||||
std::string keytype = "";
|
||||
std::string scheme = "";
|
||||
std::string keyval = "";
|
||||
|
||||
[[nodiscard]] static auto from_ed25519(std::string keyval) -> Key;
|
||||
};
|
||||
|
||||
void to_json(nlohmann::json& j, const Key& k);
|
||||
void from_json(const nlohmann::json& j, Key& k);
|
||||
|
||||
|
||||
/**
|
||||
* Representation of a role signature.
|
||||
*
|
||||
* Optional 'pgp_trailer' will trigger special handling during verification to conform to
|
||||
* OpenPGP RFC4880.
|
||||
*/
|
||||
struct RoleSignature
|
||||
{
|
||||
std::string keyid = "";
|
||||
std::string sig = "";
|
||||
std::string pgp_trailer = "";
|
||||
};
|
||||
|
||||
void to_json(nlohmann::json& j, const RoleSignature& rs);
|
||||
void from_json(const nlohmann::json& j, RoleSignature& rs);
|
||||
|
||||
[[nodiscard]] auto operator<(const RoleSignature& rs1, const RoleSignature& rs2) -> bool;
|
||||
|
||||
|
||||
/**
|
||||
* Store key IDs and threshold for a role.
|
||||
*
|
||||
* Key ID can be a hash of Key, or just its public key value.
|
||||
*/
|
||||
struct RoleKeys
|
||||
{
|
||||
std::vector<std::string> keyids;
|
||||
std::size_t threshold;
|
||||
};
|
||||
|
||||
void to_json(nlohmann::json& j, const RoleKeys& rk);
|
||||
void from_json(const nlohmann::json& j, RoleKeys& rk);
|
||||
|
||||
|
||||
/**
|
||||
* Store key values and threshold for role. Assumes key scheme/type is `ed25519`.
|
||||
*/
|
||||
struct RolePubKeys
|
||||
{
|
||||
std::vector<std::string> pubkeys;
|
||||
std::size_t threshold;
|
||||
|
||||
[[nodiscard]] auto to_role_keys() const -> RoleKeys;
|
||||
};
|
||||
|
||||
void to_json(nlohmann::json& j, const RolePubKeys& rk);
|
||||
void from_json(const nlohmann::json& j, RolePubKeys& rk);
|
||||
|
||||
|
||||
/**
|
||||
* Store full keys and threshold for role.
|
||||
*/
|
||||
struct RoleFullKeys
|
||||
{
|
||||
RoleFullKeys() = default;
|
||||
RoleFullKeys(const std::map<std::string, Key>& keys_, const std::size_t& threshold_);
|
||||
|
||||
std::map<std::string, Key> keys;
|
||||
std::size_t threshold;
|
||||
|
||||
[[nodiscard]] auto to_keys() const -> std::map<std::string, Key>;
|
||||
[[nodiscard]] auto to_roles() const -> RoleKeys;
|
||||
};
|
||||
|
||||
void to_json(nlohmann::json& j, const RoleFullKeys& r);
|
||||
void from_json(const nlohmann::json& j, RoleFullKeys& r);
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,77 @@
|
|||
// Copyright (c) 2023, 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_VALIDATION_REPO_CHECKER_HPP
|
||||
#define MAMBA_VALIDATION_REPO_CHECKER_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
|
||||
#include "mamba/fs/filesystem.hpp"
|
||||
namespace mamba
|
||||
{
|
||||
class Context;
|
||||
}
|
||||
|
||||
namespace mamba::validation
|
||||
{
|
||||
class RepoIndexChecker;
|
||||
class RootRole;
|
||||
class TimeRef;
|
||||
|
||||
/**
|
||||
* Perform security check against a repository package index using cryptographic signatures.
|
||||
*
|
||||
* Relies on multiple roles defined in The Update Framework specification.
|
||||
*/
|
||||
class RepoChecker
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param base_url Repository base URL
|
||||
* @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();
|
||||
|
||||
// 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 generate_index_checker();
|
||||
|
||||
auto cache_path() -> const fs::u8path&;
|
||||
|
||||
auto root_version() -> std::size_t;
|
||||
|
||||
private:
|
||||
|
||||
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;
|
||||
|
||||
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>;
|
||||
};
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,88 @@
|
|||
// Copyright (c) 2023, 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_VALIDATION_TOOLS_HPP
|
||||
#define MAMBA_VALIDATION_TOOLS_HPP
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
namespace mamba::fs
|
||||
{
|
||||
class u8path;
|
||||
}
|
||||
|
||||
namespace mamba::validation
|
||||
{
|
||||
[[nodiscard]] auto sha256sum(const fs::u8path& path) -> std::string_view;
|
||||
|
||||
[[nodiscard]] auto md5sum(const fs::u8path& path) -> std::string_view;
|
||||
|
||||
auto file_size(const fs::u8path& path, std::uintmax_t validation) -> bool;
|
||||
|
||||
inline constexpr std::size_t MAMBA_SHA256_SIZE_HEX = 64;
|
||||
inline constexpr std::size_t MAMBA_SHA256_SIZE_BYTES = 32;
|
||||
inline constexpr std::size_t MAMBA_MD5_SIZE_HEX = 32;
|
||||
inline constexpr std::size_t MAMBA_MD5_SIZE_BYTES = 16;
|
||||
inline constexpr std::size_t MAMBA_ED25519_KEYSIZE_HEX = 64;
|
||||
inline constexpr std::size_t MAMBA_ED25519_KEYSIZE_BYTES = 32;
|
||||
inline constexpr std::size_t MAMBA_ED25519_SIGSIZE_HEX = 128;
|
||||
inline constexpr std::size_t MAMBA_ED25519_SIGSIZE_BYTES = 64;
|
||||
|
||||
auto generate_ed25519_keypair(std::byte* pk, std::byte* sk) -> int;
|
||||
auto generate_ed25519_keypair() -> std::pair<
|
||||
std::array<std::byte, MAMBA_ED25519_KEYSIZE_BYTES>,
|
||||
std::array<std::byte, MAMBA_ED25519_KEYSIZE_BYTES>>;
|
||||
auto generate_ed25519_keypair_hex() -> std::pair<std::string, std::string>;
|
||||
|
||||
auto sign(const std::string& data, const std::byte* sk, std::byte* signature) -> int;
|
||||
auto sign(const std::string& data, const std::string& sk, std::string& signature) -> int;
|
||||
|
||||
auto ed25519_sig_hex_to_bytes(const std::string& sig_hex, int& error_code) noexcept
|
||||
-> std::array<std::byte, MAMBA_ED25519_SIGSIZE_BYTES>;
|
||||
|
||||
auto ed25519_key_hex_to_bytes(const std::string& key_hex, int& error_code) noexcept
|
||||
-> std::array<std::byte, MAMBA_ED25519_KEYSIZE_BYTES>;
|
||||
|
||||
auto
|
||||
verify(const std::byte* data, std::size_t data_len, const std::byte* pk, const std::byte* signature)
|
||||
-> int;
|
||||
auto verify(const std::string& data, const std::byte* pk, const std::byte* signature) -> int;
|
||||
auto verify(const std::string& data, const std::string& pk_hex, const std::string& signature_hex)
|
||||
-> int;
|
||||
|
||||
/**
|
||||
* Verify a GPG/PGP signature against the hash of the binary data and
|
||||
* the additional trailer added in V4 signature.
|
||||
* See RFC4880, section 5.2.4 https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.4
|
||||
* This method assumes hash function to be SHA-256
|
||||
*/
|
||||
auto verify_gpg_hashed_msg(const std::byte* data, const std::byte* pk, const std::byte* signature)
|
||||
-> int;
|
||||
auto
|
||||
verify_gpg_hashed_msg(const std::string& data, const std::byte* pk, const std::byte* signature)
|
||||
-> int;
|
||||
auto
|
||||
verify_gpg_hashed_msg(const std::string& data, const std::string& pk, const std::string& signature)
|
||||
-> int;
|
||||
|
||||
/**
|
||||
* Verify a GPG/PGP signature against the binary data and
|
||||
* the additional trailer added in V4 signature.
|
||||
* See RFC4880, section 5.2.4 https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.4
|
||||
* This method assumes hash function to be SHA-256
|
||||
*/
|
||||
auto verify_gpg(
|
||||
const std::string& data,
|
||||
const std::string& gpg_v4_trailer,
|
||||
const std::string& pk,
|
||||
const std::string& signature
|
||||
) -> int;
|
||||
|
||||
void check_timestamp_metadata_format(const std::string& ts);
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,214 @@
|
|||
// Copyright (c) 2019, 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_VALIDATION_UPDATE_FRAMEWORK_HPP
|
||||
#define MAMBA_VALIDATION_UPDATE_FRAMEWORK_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
|
||||
#include "mamba/core/timeref.hpp"
|
||||
#include "mamba/validation/keys.hpp"
|
||||
|
||||
namespace mamba
|
||||
{
|
||||
class Context;
|
||||
|
||||
namespace fs
|
||||
{
|
||||
class u8path;
|
||||
}
|
||||
}
|
||||
|
||||
namespace mamba::validation
|
||||
{
|
||||
class RepoIndexChecker;
|
||||
|
||||
/**
|
||||
* Base class for spec implementations.
|
||||
*/
|
||||
class SpecBase
|
||||
{
|
||||
public:
|
||||
|
||||
virtual ~SpecBase() = default;
|
||||
|
||||
[[nodiscard]] auto version_str() const -> std::string;
|
||||
|
||||
[[nodiscard]] virtual auto canonicalize(const nlohmann::json& j) const -> std::string;
|
||||
|
||||
[[nodiscard]] auto compatible_prefix() const -> std::string;
|
||||
[[nodiscard]] auto upgrade_prefix() const -> std::vector<std::string>;
|
||||
|
||||
[[nodiscard]] auto is_compatible(const fs::u8path& p) const -> bool;
|
||||
[[nodiscard]] auto is_compatible(const nlohmann::json& j) const -> bool;
|
||||
[[nodiscard]] auto is_compatible(const std::string& version) const -> bool;
|
||||
|
||||
[[nodiscard]] auto is_upgrade(const nlohmann::json& j) const -> bool;
|
||||
[[nodiscard]] auto is_upgrade(const std::string& version) const -> bool;
|
||||
|
||||
[[nodiscard]] virtual auto upgradable() const -> bool;
|
||||
|
||||
[[nodiscard]] virtual auto json_key() const -> std::string = 0;
|
||||
[[nodiscard]] virtual auto expiration_json_key() const -> std::string = 0;
|
||||
|
||||
[[nodiscard]] virtual auto signatures(const nlohmann::json& j) const
|
||||
-> std::set<RoleSignature>
|
||||
= 0;
|
||||
|
||||
protected:
|
||||
|
||||
SpecBase(std::string spec_version);
|
||||
|
||||
[[nodiscard]] auto get_json_value(const nlohmann::json& j) const -> std::string;
|
||||
|
||||
private:
|
||||
|
||||
std::string m_spec_version;
|
||||
};
|
||||
|
||||
auto operator==(const SpecBase& sv1, const SpecBase& sv2) -> bool;
|
||||
auto operator!=(const SpecBase& sv1, const SpecBase& sv2) -> bool;
|
||||
|
||||
|
||||
/**
|
||||
* Base class for role implementation.
|
||||
*/
|
||||
class RoleBase
|
||||
{
|
||||
public:
|
||||
|
||||
RoleBase(std::string type, std::shared_ptr<SpecBase> sv);
|
||||
|
||||
virtual ~RoleBase() = 0;
|
||||
|
||||
[[nodiscard]] auto type() const -> std::string;
|
||||
[[nodiscard]] auto spec_version() const -> SpecBase&;
|
||||
[[nodiscard]] auto version() const -> std::size_t;
|
||||
[[nodiscard]] auto file_ext() const -> std::string;
|
||||
[[nodiscard]] auto expires() const -> std::string;
|
||||
|
||||
[[nodiscard]] auto expired(const TimeRef& time_reference) const -> bool;
|
||||
|
||||
[[nodiscard]] auto roles() const -> std::set<std::string>;
|
||||
[[nodiscard]] auto signatures(const nlohmann::json& j) const -> std::set<RoleSignature>;
|
||||
|
||||
[[nodiscard]] virtual auto self_keys() const -> RoleFullKeys = 0;
|
||||
[[nodiscard]] auto all_keys() const -> std::map<std::string, RoleFullKeys>;
|
||||
|
||||
protected:
|
||||
|
||||
[[nodiscard]] auto read_json_file(const fs::u8path& p, bool update = false) const
|
||||
-> nlohmann::json;
|
||||
|
||||
/**
|
||||
* Check that a threshold of valid signatures is met
|
||||
* for the signed metadata of a role, using another
|
||||
* role keys (possibly the same).
|
||||
* Both signed and signatures metadata are contained
|
||||
* in 'data'.
|
||||
*/
|
||||
void check_role_signatures(const nlohmann::json& data, const RoleBase& role);
|
||||
/**
|
||||
* Check that a threshold of valid signatures is met
|
||||
* for the signed metadata, using a set of keys.
|
||||
*/
|
||||
void check_signatures(
|
||||
const std::string& signed_data,
|
||||
const std::set<RoleSignature>& signatures,
|
||||
const RoleFullKeys& keyring
|
||||
) const;
|
||||
|
||||
void set_spec_version(std::shared_ptr<SpecBase> sv);
|
||||
void set_expiration(const std::string& expires);
|
||||
|
||||
// Forwarding to spec implementation
|
||||
[[nodiscard]] auto canonicalize(const nlohmann::json& j) const -> std::string;
|
||||
// Return the spec implementation
|
||||
[[nodiscard]] auto spec_impl() const -> std::shared_ptr<SpecBase>;
|
||||
|
||||
// Mandatory roles defined by the current role
|
||||
[[nodiscard]] virtual auto mandatory_defined_roles() const -> std::set<std::string>;
|
||||
// Optional roles defined by the current role
|
||||
[[nodiscard]] virtual auto optionally_defined_roles() const -> std::set<std::string>;
|
||||
|
||||
// Check role
|
||||
void check_expiration_format() const;
|
||||
void check_defined_roles(bool allow_any = false) const;
|
||||
|
||||
std::map<std::string, RoleFullKeys> m_defined_roles;
|
||||
|
||||
private:
|
||||
|
||||
std::string m_internal_type;
|
||||
std::string m_type;
|
||||
std::shared_ptr<SpecBase> p_spec;
|
||||
std::size_t m_version = 1;
|
||||
std::string m_expires;
|
||||
std::string m_ext = "json";
|
||||
|
||||
friend void to_json(nlohmann::json& j, const RoleBase& r);
|
||||
friend void from_json(const nlohmann::json& j, RoleBase& r);
|
||||
};
|
||||
|
||||
void to_json(nlohmann::json& j, const RoleBase& role);
|
||||
void from_json(const nlohmann::json& j, RoleBase& role);
|
||||
|
||||
/**
|
||||
* 'root' role interface.
|
||||
*/
|
||||
class RootRole : public RoleBase
|
||||
{
|
||||
public:
|
||||
|
||||
~RootRole() override = default;
|
||||
|
||||
auto update(fs::u8path path) -> std::unique_ptr<RootRole>;
|
||||
auto update(nlohmann::json j) -> std::unique_ptr<RootRole>;
|
||||
|
||||
auto possible_update_files() -> std::vector<fs::u8path>;
|
||||
|
||||
virtual auto build_index_checker(
|
||||
Context& context,
|
||||
const TimeRef& time_reference,
|
||||
const std::string& url,
|
||||
const fs::u8path& cache_path
|
||||
) const -> std::unique_ptr<RepoIndexChecker>
|
||||
= 0;
|
||||
|
||||
protected:
|
||||
|
||||
RootRole(std::shared_ptr<SpecBase> spec);
|
||||
|
||||
private:
|
||||
|
||||
virtual auto create_update(const nlohmann::json& j) -> std::unique_ptr<RootRole> = 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Interface that performs validity checks
|
||||
* on a repository packages index.
|
||||
*/
|
||||
class RepoIndexChecker
|
||||
{
|
||||
public:
|
||||
|
||||
virtual ~RepoIndexChecker() = default;
|
||||
virtual void verify_index(const nlohmann::json& j) const = 0;
|
||||
virtual void verify_index(const fs::u8path& p) const = 0;
|
||||
virtual void verify_package(const nlohmann::json& signed_data, const nlohmann::json& signatures) const = 0;
|
||||
|
||||
protected:
|
||||
|
||||
RepoIndexChecker() = default;
|
||||
};
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,200 @@
|
|||
// Copyright (c) 2023, 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_VALIDATION_UPDATE_FRAMEWORK_V0_6_HPP
|
||||
#define MAMBA_VALIDATION_UPDATE_FRAMEWORK_V0_6_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
|
||||
#include "mamba/validation/keys.hpp"
|
||||
#include "mamba/validation/update_framework.hpp"
|
||||
|
||||
namespace mamba::validation::v0_6
|
||||
{
|
||||
/**
|
||||
* The Update Frameworkd ``conda-content-trust`` v0.6.0 specific implementation.
|
||||
*
|
||||
* This is a variation of TUF specification.
|
||||
*/
|
||||
class SpecImpl final : public SpecBase
|
||||
{
|
||||
public:
|
||||
|
||||
SpecImpl(std::string sv = "0.6.0");
|
||||
|
||||
[[nodiscard]] auto json_key() const -> std::string override;
|
||||
[[nodiscard]] auto expiration_json_key() const -> std::string override;
|
||||
|
||||
[[nodiscard]] auto signatures(const nlohmann::json& j) const
|
||||
-> std::set<RoleSignature> override;
|
||||
|
||||
[[nodiscard]] auto canonicalize(const nlohmann::json& j) const -> std::string override;
|
||||
[[nodiscard]] auto upgradable() const -> bool override;
|
||||
};
|
||||
|
||||
|
||||
class V06RoleBaseExtension
|
||||
{
|
||||
public:
|
||||
|
||||
void set_timestamp(const std::string& ts);
|
||||
|
||||
[[nodiscard]] auto timestamp() const -> std::string;
|
||||
|
||||
protected:
|
||||
|
||||
std::string m_timestamp;
|
||||
|
||||
void check_timestamp_format() const;
|
||||
};
|
||||
|
||||
// Forward declaration of KeyMgrRole.
|
||||
class KeyMgrRole;
|
||||
|
||||
/**
|
||||
* 'root' role implementation.
|
||||
*/
|
||||
class RootImpl final
|
||||
: public RootRole
|
||||
, public V06RoleBaseExtension
|
||||
{
|
||||
public:
|
||||
|
||||
RootImpl(const fs::u8path& p);
|
||||
RootImpl(const nlohmann::json& j);
|
||||
RootImpl(const std::string& json_str);
|
||||
|
||||
/**
|
||||
* Return a ``RepoIndexChecker`` implementation (derived class) from repository base URL.
|
||||
*/
|
||||
auto build_index_checker(
|
||||
Context& context,
|
||||
const TimeRef& time_reference,
|
||||
const std::string& url,
|
||||
const fs::u8path& cache_path
|
||||
) const -> std::unique_ptr<RepoIndexChecker> override;
|
||||
|
||||
[[nodiscard]] auto self_keys() const -> RoleFullKeys override;
|
||||
|
||||
[[nodiscard]] auto upgraded_signable() const -> nlohmann::json;
|
||||
auto
|
||||
upgraded_signature(const nlohmann::json& j, const std::string& pk, const std::byte* sk) const
|
||||
-> RoleSignature;
|
||||
|
||||
[[nodiscard]] auto create_key_mgr(const fs::u8path& p) const -> KeyMgrRole;
|
||||
[[nodiscard]] auto create_key_mgr(const nlohmann::json& j) const -> KeyMgrRole;
|
||||
|
||||
friend void to_json(nlohmann::json& j, const RootImpl& r);
|
||||
friend void from_json(const nlohmann::json& j, RootImpl& r);
|
||||
|
||||
private:
|
||||
|
||||
void load_from_json(const nlohmann::json& j);
|
||||
|
||||
auto create_update(const nlohmann::json& j) -> std::unique_ptr<RootRole> override;
|
||||
|
||||
[[nodiscard]] auto mandatory_defined_roles() const -> std::set<std::string> override;
|
||||
[[nodiscard]] auto optionally_defined_roles() const -> std::set<std::string> override;
|
||||
|
||||
void set_defined_roles(std::map<std::string, RolePubKeys> keys);
|
||||
};
|
||||
|
||||
|
||||
class PkgMgrRole;
|
||||
|
||||
/**
|
||||
* The Update Framework 'key_mgr' role implementation.
|
||||
*/
|
||||
class KeyMgrRole final
|
||||
: public RoleBase
|
||||
, public V06RoleBaseExtension
|
||||
{
|
||||
public:
|
||||
|
||||
KeyMgrRole(const fs::u8path& p, RoleFullKeys keys, const std::shared_ptr<SpecBase> spec);
|
||||
KeyMgrRole(const nlohmann::json& j, RoleFullKeys keys, const std::shared_ptr<SpecBase> spec);
|
||||
KeyMgrRole(const std::string& json_str, RoleFullKeys keys, const std::shared_ptr<SpecBase> spec);
|
||||
|
||||
// std::set<std::string> roles() const override;
|
||||
[[nodiscard]] auto self_keys() const -> RoleFullKeys override;
|
||||
|
||||
[[nodiscard]] auto create_pkg_mgr(const fs::u8path& p) const -> PkgMgrRole;
|
||||
[[nodiscard]] auto create_pkg_mgr(const nlohmann::json& j) const -> PkgMgrRole;
|
||||
|
||||
/**
|
||||
* Return a ``RepoIndexChecker`` implementation (derived class) from repository base URL.
|
||||
*/
|
||||
auto build_index_checker(
|
||||
Context& context,
|
||||
const TimeRef& time_reference,
|
||||
const std::string& url,
|
||||
const fs::u8path& cache_path
|
||||
) const -> std::unique_ptr<RepoIndexChecker>;
|
||||
|
||||
friend void to_json(nlohmann::json& j, const KeyMgrRole& r);
|
||||
friend void from_json(const nlohmann::json& j, KeyMgrRole& r);
|
||||
|
||||
private:
|
||||
|
||||
void load_from_json(const nlohmann::json& j);
|
||||
|
||||
RoleFullKeys m_keys;
|
||||
std::map<std::string, RolePubKeys> m_delegations;
|
||||
|
||||
[[nodiscard]] auto mandatory_defined_roles() const -> std::set<std::string> override;
|
||||
[[nodiscard]] auto optionally_defined_roles() const -> std::set<std::string> override;
|
||||
|
||||
void set_defined_roles(std::map<std::string, RolePubKeys> keys);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* The Update Framework 'pkg_mgr' role implementation.
|
||||
*
|
||||
* This role inherits from ``RepoIndexChecker`` and will be used by ``RepoChecker`` to
|
||||
* perform the repository index verification.
|
||||
*/
|
||||
class PkgMgrRole final
|
||||
: public RoleBase
|
||||
, public V06RoleBaseExtension
|
||||
, public RepoIndexChecker
|
||||
{
|
||||
public:
|
||||
|
||||
PkgMgrRole(RoleFullKeys keys, const std::shared_ptr<SpecBase> spec);
|
||||
PkgMgrRole(const fs::u8path& p, RoleFullKeys keys, const std::shared_ptr<SpecBase> spec);
|
||||
PkgMgrRole(const nlohmann::json& j, RoleFullKeys keys, const std::shared_ptr<SpecBase> spec);
|
||||
PkgMgrRole(const std::string& json_str, RoleFullKeys keys, const std::shared_ptr<SpecBase> spec);
|
||||
|
||||
void verify_index(const fs::u8path& p) const override;
|
||||
void verify_index(const nlohmann::json& j) const override;
|
||||
void
|
||||
verify_package(const nlohmann::json& signed_data, const nlohmann::json& signatures) const override;
|
||||
|
||||
friend void to_json(nlohmann::json& j, const PkgMgrRole& r);
|
||||
friend void from_json(const nlohmann::json& j, PkgMgrRole& r);
|
||||
|
||||
private:
|
||||
|
||||
void load_from_json(const nlohmann::json& j);
|
||||
|
||||
[[nodiscard]] auto self_keys() const -> RoleFullKeys override;
|
||||
[[nodiscard]] auto pkg_signatures(const nlohmann::json& j) const -> std::set<RoleSignature>;
|
||||
void
|
||||
check_pkg_signatures(const nlohmann::json& signed_data, const nlohmann::json& signatures) const;
|
||||
|
||||
void set_defined_roles(std::map<std::string, RolePubKeys> keys);
|
||||
|
||||
RoleFullKeys m_keys;
|
||||
|
||||
friend class KeyMgrRole;
|
||||
};
|
||||
}
|
||||
#endif
|
|
@ -0,0 +1,81 @@
|
|||
// Copyright (c) 2023, 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_VALIDATION_UPDATE_FRAMEWORK_V1_HPP
|
||||
#define MAMBA_VALIDATION_UPDATE_FRAMEWORK_V1_HPP
|
||||
|
||||
#include <set>
|
||||
#include <string>
|
||||
|
||||
#include <nlohmann/json_fwd.hpp>
|
||||
|
||||
#include "mamba/validation/keys.hpp"
|
||||
#include "mamba/validation/update_framework.hpp"
|
||||
|
||||
namespace mamba::fs
|
||||
{
|
||||
class u8path;
|
||||
}
|
||||
|
||||
namespace mamba::validation::v1
|
||||
{
|
||||
/**
|
||||
* TUF v1 specific implementation.
|
||||
*/
|
||||
class SpecImpl final : public SpecBase
|
||||
{
|
||||
public:
|
||||
|
||||
SpecImpl(std::string sv = "1.0.17");
|
||||
|
||||
[[nodiscard]] auto json_key() const -> std::string override;
|
||||
[[nodiscard]] auto expiration_json_key() const -> std::string override;
|
||||
|
||||
[[nodiscard]] auto signatures(const nlohmann::json& j) const
|
||||
-> std::set<RoleSignature> override;
|
||||
};
|
||||
|
||||
/**
|
||||
* The Update Frameworkd 'root' role implementation.
|
||||
*
|
||||
* TUF v1.0.17 §2.1.1
|
||||
* https://theupdateframework.github.io/specification/latest/#root
|
||||
*/
|
||||
class RootImpl final : public RootRole
|
||||
{
|
||||
public:
|
||||
|
||||
RootImpl(const fs::u8path& p);
|
||||
RootImpl(const nlohmann::json& j);
|
||||
|
||||
[[nodiscard]] auto self_keys() const -> RoleFullKeys override;
|
||||
|
||||
auto build_index_checker(
|
||||
Context& context,
|
||||
const TimeRef& time_reference,
|
||||
const std::string& url,
|
||||
const fs::u8path& cache_path
|
||||
) const -> std::unique_ptr<RepoIndexChecker> override;
|
||||
|
||||
friend void to_json(nlohmann::json& j, const RootImpl& r);
|
||||
friend void from_json(const nlohmann::json& j, RootImpl& r);
|
||||
|
||||
private:
|
||||
|
||||
void load_from_json(const nlohmann::json& j);
|
||||
|
||||
auto create_update(const nlohmann::json& j) -> std::unique_ptr<RootRole> override;
|
||||
|
||||
[[nodiscard]] auto mandatory_defined_roles() const -> std::set<std::string> override;
|
||||
[[nodiscard]] auto optionally_defined_roles() const -> std::set<std::string> override;
|
||||
|
||||
void set_defined_roles(
|
||||
const std::map<std::string, Key>& keys,
|
||||
const std::map<std::string, RoleKeys>& roles
|
||||
);
|
||||
};
|
||||
}
|
||||
#endif
|
|
@ -18,10 +18,10 @@
|
|||
#include "mamba/core/menuinst.hpp"
|
||||
#include "mamba/core/output.hpp"
|
||||
#include "mamba/core/transaction_context.hpp"
|
||||
#include "mamba/core/validate.hpp"
|
||||
#include "mamba/util/build.hpp"
|
||||
#include "mamba/util/environment.hpp"
|
||||
#include "mamba/util/string.hpp"
|
||||
#include "mamba/validation/tools.hpp"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include "mamba/core/util_os.hpp"
|
||||
|
|
|
@ -10,10 +10,10 @@
|
|||
#include "mamba/core/output.hpp"
|
||||
#include "mamba/core/package_cache.hpp"
|
||||
#include "mamba/core/package_handling.hpp"
|
||||
#include "mamba/core/validate.hpp"
|
||||
#include "mamba/core/util.hpp"
|
||||
#include "mamba/specs/conda_url.hpp"
|
||||
#include "mamba/util/string.hpp"
|
||||
#include "mamba/util/url_manip.hpp"
|
||||
#include "mamba/validation/tools.hpp"
|
||||
|
||||
namespace mamba
|
||||
{
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
#include "mamba/core/invoke.hpp"
|
||||
#include "mamba/core/package_fetcher.hpp"
|
||||
#include "mamba/core/util.hpp"
|
||||
#include "mamba/core/validate.hpp"
|
||||
#include "mamba/util/string.hpp"
|
||||
#include "mamba/validation/tools.hpp"
|
||||
|
||||
namespace mamba
|
||||
{
|
||||
|
|
|
@ -5,8 +5,6 @@
|
|||
// The full license is in the file LICENSE, distributed with this software.
|
||||
|
||||
|
||||
#include <sstream>
|
||||
|
||||
#include <archive.h>
|
||||
#include <archive_entry.h>
|
||||
#include <reproc++/run.hpp>
|
||||
|
@ -17,8 +15,8 @@
|
|||
#include "mamba/core/package_paths.hpp"
|
||||
#include "mamba/core/thread_utils.hpp"
|
||||
#include "mamba/core/util_os.hpp"
|
||||
#include "mamba/core/validate.hpp"
|
||||
#include "mamba/util/string.hpp"
|
||||
#include "mamba/validation/tools.hpp"
|
||||
|
||||
#include "nlohmann/json.hpp"
|
||||
|
||||
|
|
|
@ -1,24 +1,22 @@
|
|||
// Copyright (c) 2023, 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 <atomic>
|
||||
#include <cassert>
|
||||
#include <mutex>
|
||||
#include <regex>
|
||||
|
||||
#include "mamba/util/build.hpp"
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include <curl/urlapi.h>
|
||||
}
|
||||
|
||||
#include "mamba/api/configuration.hpp"
|
||||
#include "mamba/core/channel_context.hpp"
|
||||
#include "mamba/core/context.hpp"
|
||||
#include "mamba/core/execution.hpp"
|
||||
#include "mamba/core/output.hpp"
|
||||
#include "mamba/core/validate.hpp"
|
||||
#include "mamba/util/build.hpp"
|
||||
|
||||
#include "spdlog/spdlog.h"
|
||||
|
||||
|
||||
namespace mamba
|
||||
{
|
||||
|
@ -181,5 +179,4 @@ namespace mamba
|
|||
{
|
||||
main_console = nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,71 @@
|
|||
// Copyright (c) 2023, 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/util/string.hpp"
|
||||
#include "mamba/validation/errors.hpp"
|
||||
|
||||
namespace mamba::validation
|
||||
{
|
||||
trust_error::trust_error(std::string_view message)
|
||||
: m_message(util::concat("Content trust error. ", message, ". Aborting."))
|
||||
{
|
||||
}
|
||||
|
||||
auto trust_error::what() const noexcept -> const char*
|
||||
{
|
||||
return this->m_message.c_str();
|
||||
}
|
||||
|
||||
threshold_error::threshold_error()
|
||||
: trust_error("Signatures threshold not met")
|
||||
{
|
||||
}
|
||||
|
||||
role_metadata_error::role_metadata_error()
|
||||
: trust_error("Invalid role metadata")
|
||||
{
|
||||
}
|
||||
|
||||
rollback_error::rollback_error()
|
||||
: trust_error("Possible rollback attack")
|
||||
{
|
||||
}
|
||||
|
||||
freeze_error::freeze_error()
|
||||
: trust_error("Possible freeze attack")
|
||||
{
|
||||
}
|
||||
|
||||
role_file_error::role_file_error()
|
||||
: trust_error("Invalid role file")
|
||||
{
|
||||
}
|
||||
|
||||
spec_version_error::spec_version_error()
|
||||
: trust_error("Unsupported specification version")
|
||||
{
|
||||
}
|
||||
|
||||
fetching_error::fetching_error()
|
||||
: trust_error("Failed to fetch role metadata")
|
||||
{
|
||||
}
|
||||
|
||||
package_error::package_error()
|
||||
: trust_error("Invalid package")
|
||||
{
|
||||
}
|
||||
|
||||
role_error::role_error()
|
||||
: trust_error("Invalid role")
|
||||
{
|
||||
}
|
||||
|
||||
index_error::index_error()
|
||||
: trust_error("Invalid package index metadata")
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,112 @@
|
|||
// Copyright (c) 2023, 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 <utility>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "mamba/validation/keys.hpp"
|
||||
|
||||
namespace mamba::validation
|
||||
{
|
||||
auto Key::from_ed25519(std::string keyval) -> Key
|
||||
{
|
||||
return { "ed25519", "ed25519", std::move(keyval) };
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json& j, const Key& key)
|
||||
{
|
||||
j = { { "keytype", key.keytype }, { "scheme", key.scheme }, { "keyval", key.keyval } };
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& j, Key& key)
|
||||
{
|
||||
j.at("keytype").get_to(key.keytype);
|
||||
j.at("scheme").get_to(key.scheme);
|
||||
j.at("keyval").get_to(key.keyval);
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json& j, const RoleSignature& role_sig)
|
||||
{
|
||||
j = { { "keyid", role_sig.keyid }, { "sig", role_sig.sig } };
|
||||
if (!role_sig.pgp_trailer.empty())
|
||||
{
|
||||
j["other_headers"] = role_sig.pgp_trailer;
|
||||
}
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& j, RoleSignature& role_sig)
|
||||
{
|
||||
j.at("keyid").get_to(role_sig.keyid);
|
||||
j.at("sig").get_to(role_sig.sig);
|
||||
if (j.find("other_headers") != j.end())
|
||||
{
|
||||
j.at("other_headers").get_to(role_sig.pgp_trailer);
|
||||
}
|
||||
}
|
||||
|
||||
auto operator<(const RoleSignature& rs1, const RoleSignature& rs2) -> bool
|
||||
{
|
||||
return rs1.keyid < rs2.keyid;
|
||||
};
|
||||
|
||||
void to_json(nlohmann::json& j, const RoleKeys& role_keys)
|
||||
{
|
||||
j = { { "keyids", role_keys.keyids }, { "threshold", role_keys.threshold } };
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& j, RoleKeys& role_keys)
|
||||
{
|
||||
j.at("keyids").get_to(role_keys.keyids);
|
||||
j.at("threshold").get_to(role_keys.threshold);
|
||||
}
|
||||
|
||||
auto RolePubKeys::to_role_keys() const -> RoleKeys
|
||||
{
|
||||
return { pubkeys, threshold };
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json& j, const RolePubKeys& role_keys)
|
||||
{
|
||||
j = { { "pubkeys", role_keys.pubkeys }, { "threshold", role_keys.threshold } };
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& j, RolePubKeys& role_keys)
|
||||
{
|
||||
j.at("pubkeys").get_to(role_keys.pubkeys);
|
||||
j.at("threshold").get_to(role_keys.threshold);
|
||||
}
|
||||
|
||||
RoleFullKeys::RoleFullKeys(const std::map<std::string, Key>& keys_, const std::size_t& threshold_)
|
||||
: keys(keys_)
|
||||
, threshold(threshold_){};
|
||||
|
||||
auto RoleFullKeys::to_keys() const -> std::map<std::string, Key>
|
||||
{
|
||||
return keys;
|
||||
}
|
||||
|
||||
auto RoleFullKeys::to_roles() const -> RoleKeys
|
||||
{
|
||||
std::vector<std::string> keyids;
|
||||
for (auto& k : keys)
|
||||
{
|
||||
keyids.push_back(k.first);
|
||||
}
|
||||
return { keyids, threshold };
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json& j, const RoleFullKeys& k)
|
||||
{
|
||||
j = { { "keys", k.keys }, { "threshold", k.threshold } };
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& j, RoleFullKeys& k)
|
||||
{
|
||||
j.at("keys").get_to(k.keys);
|
||||
j.at("threshold").get_to(k.threshold);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
// Copyright (c) 2023, 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 <string>
|
||||
|
||||
#include "mamba/core/download.hpp"
|
||||
#include "mamba/core/output.hpp"
|
||||
#include "mamba/core/timeref.hpp"
|
||||
#include "mamba/core/util.hpp"
|
||||
#include "mamba/util/string.hpp"
|
||||
#include "mamba/validation/errors.hpp"
|
||||
#include "mamba/validation/repo_checker.hpp"
|
||||
#include "mamba/validation/update_framework.hpp"
|
||||
#include "mamba/validation/update_framework_v0_6.hpp"
|
||||
#include "mamba/validation/update_framework_v1.hpp"
|
||||
|
||||
namespace mamba::validation
|
||||
{
|
||||
RepoChecker::RepoChecker(Context& context, std::string base_url, fs::u8path ref_path, fs::u8path cache_path)
|
||||
: m_base_url(std::move(base_url))
|
||||
, m_ref_path(std::move(ref_path))
|
||||
, m_cache_path(std::move(cache_path))
|
||||
, m_context(context)
|
||||
{
|
||||
}
|
||||
|
||||
RepoChecker::~RepoChecker() = default;
|
||||
|
||||
auto RepoChecker::cache_path() -> const fs::u8path&
|
||||
{
|
||||
return m_cache_path;
|
||||
}
|
||||
|
||||
void RepoChecker::generate_index_checker()
|
||||
{
|
||||
if (p_index_checker == nullptr)
|
||||
{
|
||||
// TUF spec 5.1 - Record fixed update start time
|
||||
// Expiration computations will be done against
|
||||
// this reference
|
||||
// https://theupdateframework.github.io/specification/latest/#fix-time
|
||||
const TimeRef time_reference;
|
||||
|
||||
auto root = get_root_role(time_reference);
|
||||
p_index_checker = root->build_index_checker(
|
||||
m_context,
|
||||
time_reference,
|
||||
m_base_url,
|
||||
cache_path()
|
||||
);
|
||||
|
||||
LOG_INFO << "Index checker successfully generated for '" << m_base_url << "'";
|
||||
}
|
||||
}
|
||||
|
||||
void RepoChecker::verify_index(const nlohmann::json& j) const
|
||||
{
|
||||
p_index_checker->verify_index(j);
|
||||
}
|
||||
|
||||
void RepoChecker::verify_index(const fs::u8path& p) const
|
||||
{
|
||||
p_index_checker->verify_index(p);
|
||||
}
|
||||
|
||||
void
|
||||
RepoChecker::verify_package(const nlohmann::json& signed_data, const nlohmann::json& signatures) const
|
||||
{
|
||||
p_index_checker->verify_package(signed_data, signatures);
|
||||
}
|
||||
|
||||
auto RepoChecker::root_version() -> std::size_t
|
||||
{
|
||||
return m_root_version;
|
||||
}
|
||||
|
||||
auto RepoChecker::ref_root() -> fs::u8path
|
||||
{
|
||||
return m_ref_path / "root.json";
|
||||
}
|
||||
|
||||
auto RepoChecker::cached_root() -> fs::u8path
|
||||
{
|
||||
if (cache_path().empty())
|
||||
{
|
||||
return "";
|
||||
}
|
||||
else
|
||||
{
|
||||
return cache_path() / "root.json";
|
||||
}
|
||||
}
|
||||
|
||||
void RepoChecker::persist_file(const fs::u8path& file_path)
|
||||
{
|
||||
if (fs::exists(cached_root()))
|
||||
{
|
||||
fs::remove(cached_root());
|
||||
}
|
||||
if (!cached_root().empty())
|
||||
{
|
||||
fs::copy(file_path, cached_root());
|
||||
}
|
||||
}
|
||||
|
||||
auto RepoChecker::initial_trusted_root() -> fs::u8path
|
||||
{
|
||||
if (fs::exists(cached_root()))
|
||||
{
|
||||
LOG_DEBUG << "Using cache for 'root' initial trusted file";
|
||||
return cached_root();
|
||||
}
|
||||
|
||||
if (!fs::exists(m_ref_path))
|
||||
{
|
||||
LOG_ERROR << "'root' initial trusted file not found at '" << m_ref_path.string()
|
||||
<< "' for repo '" << m_base_url << "'";
|
||||
throw role_file_error();
|
||||
}
|
||||
else
|
||||
{
|
||||
return ref_root();
|
||||
}
|
||||
}
|
||||
|
||||
auto RepoChecker::get_root_role(const TimeRef& time_reference) -> std::unique_ptr<RootRole>
|
||||
{
|
||||
// TUF spec 5.3 - Update the root role
|
||||
// https://theupdateframework.github.io/specification/latest/#update-root
|
||||
|
||||
std::unique_ptr<RootRole> updated_root;
|
||||
|
||||
LOG_DEBUG << "Loading 'root' metadata for repo '" << m_base_url << "'";
|
||||
auto trusted_root = initial_trusted_root();
|
||||
|
||||
if (v0_6::SpecImpl().is_compatible(trusted_root))
|
||||
{
|
||||
updated_root = std::make_unique<v0_6::RootImpl>(trusted_root);
|
||||
}
|
||||
else if (v1::SpecImpl().is_compatible(trusted_root))
|
||||
{
|
||||
updated_root = std::make_unique<v1::RootImpl>(trusted_root);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR << "Invalid 'root' initial trusted file '" << trusted_root.string()
|
||||
<< "' for repo '" << m_base_url << "'";
|
||||
throw role_file_error();
|
||||
}
|
||||
|
||||
if (trusted_root != cached_root())
|
||||
{
|
||||
persist_file(trusted_root);
|
||||
}
|
||||
|
||||
auto update_files = updated_root->possible_update_files();
|
||||
auto tmp_dir = TemporaryDirectory();
|
||||
auto tmp_dir_path = tmp_dir.path();
|
||||
|
||||
// do chained updates
|
||||
LOG_DEBUG << "Starting updates of 'root' metadata";
|
||||
do
|
||||
{
|
||||
fs::u8path tmp_file_path;
|
||||
|
||||
// Update from the most recent spec supported by this client
|
||||
for (auto& f : update_files)
|
||||
{
|
||||
auto url = util::concat(m_base_url, "/", f.string());
|
||||
tmp_file_path = tmp_dir_path / f;
|
||||
|
||||
if (check_resource_exists(url, m_context))
|
||||
{
|
||||
DownloadRequest request(f.string(), url, tmp_file_path.string());
|
||||
DownloadResult res = download(std::move(request), m_context);
|
||||
|
||||
if (res)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
tmp_file_path = "";
|
||||
}
|
||||
|
||||
if (tmp_file_path.empty())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
updated_root = updated_root->update(tmp_file_path);
|
||||
// TUF spec 5.3.8 - Persist root metadata
|
||||
// Updated 'root' metadata are persisted in a cache directory
|
||||
persist_file(tmp_file_path);
|
||||
|
||||
// Set the next possible files
|
||||
update_files = updated_root->possible_update_files();
|
||||
}
|
||||
// TUF spec 5.3.9 - Repeat steps 5.3.2 to 5.3.9
|
||||
while (true);
|
||||
|
||||
m_root_version = updated_root->version();
|
||||
LOG_DEBUG << "Latest 'root' metadata has version " << m_root_version;
|
||||
|
||||
// TUF spec 5.3.10 - Check for a freeze attack
|
||||
// Updated 'root' role should not be expired
|
||||
// https://theupdateframework.github.io/specification/latest/#update-root
|
||||
if (updated_root->expired(time_reference))
|
||||
{
|
||||
LOG_ERROR << "Possible freeze attack of 'root' metadata.\nExpired: "
|
||||
<< updated_root->expires();
|
||||
throw freeze_error();
|
||||
}
|
||||
|
||||
return updated_root;
|
||||
};
|
||||
}
|
|
@ -0,0 +1,397 @@
|
|||
// Copyright (c) 2023, 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 <regex>
|
||||
#include <utility>
|
||||
|
||||
#include <openssl/evp.h>
|
||||
|
||||
#include "mamba/core/output.hpp"
|
||||
#include "mamba/core/util.hpp"
|
||||
#include "mamba/fs/filesystem.hpp"
|
||||
#include "mamba/util/cryptography.hpp"
|
||||
#include "mamba/validation/errors.hpp"
|
||||
#include "mamba/validation/tools.hpp"
|
||||
|
||||
namespace mamba::validation
|
||||
{
|
||||
auto sha256sum(const fs::u8path& path) -> std::string_view
|
||||
{
|
||||
std::ifstream infile = mamba::open_ifstream(path);
|
||||
thread_local auto hasher = util::Sha256Hasher();
|
||||
thread_local auto hash = hasher.file_hex(infile);
|
||||
return { hash.data(), hash.size() };
|
||||
}
|
||||
|
||||
auto md5sum(const fs::u8path& path) -> std::string_view
|
||||
{
|
||||
std::ifstream infile = mamba::open_ifstream(path);
|
||||
thread_local auto hasher = util::Md5Hasher();
|
||||
thread_local auto hash = hasher.file_hex(infile);
|
||||
return { hash.data(), hash.size() };
|
||||
}
|
||||
|
||||
auto file_size(const fs::u8path& path, std::uintmax_t validation) -> bool
|
||||
{
|
||||
return fs::file_size(path) == validation;
|
||||
}
|
||||
|
||||
namespace
|
||||
{
|
||||
template <size_t S, class B>
|
||||
[[nodiscard]] auto hex_to_bytes_arr(const B& buffer, int& error_code) noexcept
|
||||
-> std::array<std::byte, S>
|
||||
{
|
||||
auto out = std::array<std::byte, S>{};
|
||||
auto err = util::EncodingError::Ok;
|
||||
util::hex_to_bytes_to(buffer, out.data(), err);
|
||||
error_code = err != util::EncodingError::Ok;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <class B>
|
||||
[[nodiscard]] auto hex_to_bytes_vec(const B& buffer, int& error_code) noexcept
|
||||
-> std::vector<std::byte>
|
||||
{
|
||||
auto out = std::vector<std::byte>(buffer.size() / 2);
|
||||
auto err = util::EncodingError::Ok;
|
||||
util::hex_to_bytes_to(buffer, out.data(), err);
|
||||
error_code = err != util::EncodingError::Ok;
|
||||
return out;
|
||||
}
|
||||
}
|
||||
|
||||
auto ed25519_sig_hex_to_bytes(const std::string& sig_hex, int& error_code) noexcept
|
||||
-> std::array<std::byte, MAMBA_ED25519_SIGSIZE_BYTES>
|
||||
|
||||
{
|
||||
return hex_to_bytes_arr<MAMBA_ED25519_SIGSIZE_BYTES>(sig_hex, error_code);
|
||||
}
|
||||
|
||||
auto ed25519_key_hex_to_bytes(const std::string& key_hex, int& error_code) noexcept
|
||||
-> std::array<std::byte, MAMBA_ED25519_KEYSIZE_BYTES>
|
||||
|
||||
{
|
||||
return hex_to_bytes_arr<MAMBA_ED25519_KEYSIZE_BYTES>(key_hex, error_code);
|
||||
}
|
||||
|
||||
auto generate_ed25519_keypair(std::byte* pk, std::byte* sk) -> int
|
||||
{
|
||||
std::size_t key_len = MAMBA_ED25519_KEYSIZE_BYTES;
|
||||
::EVP_PKEY* pkey = nullptr;
|
||||
struct EVPContext
|
||||
{
|
||||
::EVP_PKEY_CTX* pctx = ::EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, nullptr);
|
||||
|
||||
~EVPContext()
|
||||
{
|
||||
::EVP_PKEY_CTX_free(pctx);
|
||||
}
|
||||
|
||||
} evp_context;
|
||||
|
||||
|
||||
int gen_status = ::EVP_PKEY_keygen_init(evp_context.pctx);
|
||||
if (gen_status != 1)
|
||||
{
|
||||
LOG_DEBUG << "Failed to initialize ED25519 key pair generation";
|
||||
return gen_status;
|
||||
}
|
||||
|
||||
gen_status = ::EVP_PKEY_keygen(evp_context.pctx, &pkey);
|
||||
if (gen_status != 1)
|
||||
{
|
||||
LOG_DEBUG << "Failed to generate ED25519 key pair";
|
||||
return gen_status;
|
||||
}
|
||||
|
||||
int storage_status = ::EVP_PKEY_get_raw_public_key(
|
||||
pkey,
|
||||
reinterpret_cast<unsigned char*>(pk),
|
||||
&key_len
|
||||
);
|
||||
if (storage_status != 1)
|
||||
{
|
||||
LOG_DEBUG << "Failed to store public key of generated ED25519 key pair";
|
||||
return storage_status;
|
||||
}
|
||||
storage_status = ::EVP_PKEY_get_raw_private_key(
|
||||
pkey,
|
||||
reinterpret_cast<unsigned char*>(sk),
|
||||
&key_len
|
||||
);
|
||||
if (storage_status != 1)
|
||||
{
|
||||
LOG_DEBUG << "Failed to store private key of generated ED25519 key pair";
|
||||
return storage_status;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto generate_ed25519_keypair() -> std::pair<
|
||||
std::array<std::byte, MAMBA_ED25519_KEYSIZE_BYTES>,
|
||||
std::array<std::byte, MAMBA_ED25519_KEYSIZE_BYTES>>
|
||||
{
|
||||
std::array<std::byte, MAMBA_ED25519_KEYSIZE_BYTES> pk, sk;
|
||||
generate_ed25519_keypair(pk.data(), sk.data());
|
||||
return { pk, sk };
|
||||
}
|
||||
|
||||
auto generate_ed25519_keypair_hex() -> std::pair<std::string, std::string>
|
||||
{
|
||||
auto [first, second] = generate_ed25519_keypair();
|
||||
// TODO change function signature to use std::byte
|
||||
const auto first_data = reinterpret_cast<const std::byte*>(first.data());
|
||||
const auto second_data = reinterpret_cast<const std::byte*>(second.data());
|
||||
return {
|
||||
util::bytes_to_hex_str(first_data, first_data + first.size()),
|
||||
util::bytes_to_hex_str(second_data, second_data + second.size()),
|
||||
};
|
||||
}
|
||||
|
||||
auto sign(const std::string& data, const std::byte* sk, std::byte* signature) -> int
|
||||
{
|
||||
::EVP_PKEY* ed_key = ::EVP_PKEY_new_raw_private_key(
|
||||
EVP_PKEY_ED25519,
|
||||
nullptr,
|
||||
reinterpret_cast<const unsigned char*>(sk),
|
||||
MAMBA_ED25519_KEYSIZE_BYTES
|
||||
);
|
||||
::EVP_MD_CTX* md_ctx = ::EVP_MD_CTX_new();
|
||||
|
||||
if (ed_key == nullptr)
|
||||
{
|
||||
LOG_DEBUG << "Failed to read secret key raw buffer during signing step";
|
||||
return 0;
|
||||
}
|
||||
|
||||
int init_status, sign_status;
|
||||
init_status = ::EVP_DigestSignInit(md_ctx, nullptr, nullptr, nullptr, ed_key);
|
||||
if (init_status != 1)
|
||||
{
|
||||
LOG_DEBUG << "Failed to init signing step";
|
||||
return init_status;
|
||||
}
|
||||
|
||||
std::size_t sig_len = MAMBA_ED25519_SIGSIZE_BYTES;
|
||||
sign_status = ::EVP_DigestSign(
|
||||
md_ctx,
|
||||
reinterpret_cast<unsigned char*>(signature),
|
||||
&sig_len,
|
||||
reinterpret_cast<const unsigned char*>(data.data()),
|
||||
data.size()
|
||||
);
|
||||
if (sign_status != 1)
|
||||
{
|
||||
LOG_DEBUG << "Failed to sign the data";
|
||||
return sign_status;
|
||||
}
|
||||
|
||||
::EVP_MD_CTX_free(md_ctx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto sign(const std::string& data, const std::string& sk, std::string& signature) -> int
|
||||
{
|
||||
int error_code = 0;
|
||||
|
||||
auto bin_sk = ed25519_key_hex_to_bytes(sk, error_code);
|
||||
if (error_code != 0)
|
||||
{
|
||||
LOG_DEBUG << "Invalid secret key";
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::array<std::byte, MAMBA_ED25519_SIGSIZE_BYTES> sig;
|
||||
|
||||
error_code = sign(data, bin_sk.data(), sig.data());
|
||||
|
||||
const auto sig_data = reinterpret_cast<const std::byte*>(sig.data());
|
||||
signature = util::bytes_to_hex_str(sig_data, sig_data + sig.size());
|
||||
|
||||
return error_code;
|
||||
}
|
||||
|
||||
auto
|
||||
verify(const std::byte* data, std::size_t data_len, const std::byte* pk, const std::byte* signature)
|
||||
-> int
|
||||
{
|
||||
::EVP_PKEY* ed_key = ::EVP_PKEY_new_raw_public_key(
|
||||
EVP_PKEY_ED25519,
|
||||
nullptr,
|
||||
reinterpret_cast<const unsigned char*>(pk),
|
||||
MAMBA_ED25519_KEYSIZE_BYTES
|
||||
);
|
||||
::EVP_MD_CTX* md_ctx = ::EVP_MD_CTX_new();
|
||||
|
||||
if (ed_key == nullptr)
|
||||
{
|
||||
LOG_DEBUG << "Failed to read public key raw buffer during verification step";
|
||||
return 0;
|
||||
}
|
||||
|
||||
int init_status, verif_status;
|
||||
init_status = ::EVP_DigestVerifyInit(md_ctx, nullptr, nullptr, nullptr, ed_key);
|
||||
if (init_status != 1)
|
||||
{
|
||||
LOG_DEBUG << "Failed to init verification step";
|
||||
return init_status;
|
||||
}
|
||||
|
||||
std::size_t sig_len = MAMBA_ED25519_SIGSIZE_BYTES;
|
||||
verif_status = ::EVP_DigestVerify(
|
||||
md_ctx,
|
||||
reinterpret_cast<const unsigned char*>(signature),
|
||||
sig_len,
|
||||
reinterpret_cast<const unsigned char*>(data),
|
||||
data_len
|
||||
);
|
||||
if (verif_status != 1)
|
||||
{
|
||||
LOG_DEBUG << "Failed to verify the data signature";
|
||||
return verif_status;
|
||||
}
|
||||
|
||||
::EVP_MD_CTX_free(md_ctx);
|
||||
return 1;
|
||||
}
|
||||
|
||||
auto verify(const std::string& data, const std::byte* pk, const std::byte* signature) -> int
|
||||
{
|
||||
unsigned long long data_len = data.size();
|
||||
auto raw_data = reinterpret_cast<const std::byte*>(data.data());
|
||||
|
||||
return verify(raw_data, data_len, pk, signature);
|
||||
}
|
||||
|
||||
auto verify(const std::string& data, const std::string& pk, const std::string& signature) -> int
|
||||
{
|
||||
int error_code = 0;
|
||||
auto bin_signature = ed25519_sig_hex_to_bytes(signature, error_code);
|
||||
if (error_code != 0)
|
||||
{
|
||||
LOG_DEBUG << "Invalid signature '" << signature << "' for public key '" << pk << "'";
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto bin_pk = ed25519_key_hex_to_bytes(pk, error_code);
|
||||
if (error_code != 0)
|
||||
{
|
||||
LOG_DEBUG << "Invalid public key '" << pk << "'";
|
||||
return 0;
|
||||
}
|
||||
|
||||
return verify(data, bin_pk.data(), bin_signature.data());
|
||||
}
|
||||
|
||||
auto verify_gpg_hashed_msg(const std::byte* data, const std::byte* pk, const std::byte* signature)
|
||||
-> int
|
||||
{
|
||||
return verify(data, MAMBA_SHA256_SIZE_BYTES, pk, signature);
|
||||
}
|
||||
|
||||
|
||||
auto
|
||||
verify_gpg_hashed_msg(const std::string& data, const std::byte* pk, const std::byte* signature)
|
||||
-> int
|
||||
{
|
||||
int error = 0;
|
||||
auto data_bin = hex_to_bytes_arr<MAMBA_SHA256_SIZE_BYTES>(data, error);
|
||||
|
||||
return verify(data_bin.data(), MAMBA_SHA256_SIZE_BYTES, pk, signature) + error;
|
||||
}
|
||||
|
||||
auto
|
||||
verify_gpg_hashed_msg(const std::string& data, const std::string& pk, const std::string& signature)
|
||||
-> int
|
||||
{
|
||||
int error = 0;
|
||||
auto signature_bin = ed25519_sig_hex_to_bytes(signature, error);
|
||||
if (error)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
auto pk_bin = ed25519_key_hex_to_bytes(pk, error);
|
||||
if (error)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
return verify_gpg_hashed_msg(data, pk_bin.data(), signature_bin.data());
|
||||
}
|
||||
|
||||
auto verify_gpg(
|
||||
const std::string& data,
|
||||
const std::string& pgp_v4_trailer,
|
||||
const std::string& pk,
|
||||
const std::string& signature
|
||||
) -> int
|
||||
{
|
||||
unsigned long long data_len = data.size();
|
||||
auto data_bin = reinterpret_cast<const std::byte*>(data.data());
|
||||
|
||||
int error = 0;
|
||||
auto signature_bin = ed25519_sig_hex_to_bytes(signature, error);
|
||||
if (error)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
auto pk_bin = ed25519_key_hex_to_bytes(pk, error);
|
||||
if (error)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
|
||||
std::size_t trailer_hex_size = pgp_v4_trailer.size();
|
||||
if (trailer_hex_size % 2 != 0)
|
||||
{
|
||||
LOG_DEBUG << "PGP V4 trailer size is not even: " << pgp_v4_trailer;
|
||||
return 0;
|
||||
}
|
||||
|
||||
auto pgp_trailer_bin = hex_to_bytes_vec(pgp_v4_trailer, error);
|
||||
if (error)
|
||||
{
|
||||
return error;
|
||||
}
|
||||
auto final_trailer_bin = hex_to_bytes_arr<2>(std::string_view("04ff"), error);
|
||||
assert(!error);
|
||||
|
||||
auto trailer_bin_len_big_endian = static_cast<uint32_t>(pgp_trailer_bin.size());
|
||||
|
||||
#ifdef _WIN32
|
||||
trailer_bin_len_big_endian = _byteswap_ulong(trailer_bin_len_big_endian);
|
||||
#else
|
||||
trailer_bin_len_big_endian = __builtin_bswap32(trailer_bin_len_big_endian);
|
||||
#endif
|
||||
|
||||
std::array<std::byte, MAMBA_SHA256_SIZE_BYTES> hash;
|
||||
|
||||
auto digester = util::Sha256Digester();
|
||||
digester.digest_start();
|
||||
digester.digest_update(data_bin, data_len);
|
||||
digester.digest_update(pgp_trailer_bin.data(), pgp_trailer_bin.size());
|
||||
digester.digest_update(final_trailer_bin.data(), final_trailer_bin.size());
|
||||
digester.digest_update(reinterpret_cast<const std::byte*>(&trailer_bin_len_big_endian), 4);
|
||||
digester.digest_finalize_to(hash.data());
|
||||
|
||||
return verify_gpg_hashed_msg(hash.data(), pk_bin.data(), signature_bin.data()) + error;
|
||||
}
|
||||
|
||||
void check_timestamp_metadata_format(const std::string& ts)
|
||||
{
|
||||
std::regex timestamp_re("^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$");
|
||||
|
||||
if (!std::regex_match(ts, timestamp_re))
|
||||
{
|
||||
mamba::Console::stream() << "Invalid timestamp in content trust metadata";
|
||||
LOG_ERROR << "Invalid timestamp format '" << ts
|
||||
<< "', should be UTC ISO8601 ('<YYYY>-<MM>-<DD>T<HH>:<MM>:<SS>Z')";
|
||||
throw role_metadata_error();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,601 @@
|
|||
// Copyright (c) 2023, 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 <fstream>
|
||||
#include <regex>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include "mamba/core/output.hpp"
|
||||
#include "mamba/fs/filesystem.hpp"
|
||||
#include "mamba/util/string.hpp"
|
||||
#include "mamba/validation/errors.hpp"
|
||||
#include "mamba/validation/tools.hpp"
|
||||
#include "mamba/validation/update_framework.hpp"
|
||||
|
||||
namespace mamba::validation
|
||||
{
|
||||
SpecBase::SpecBase(std::string spec_version)
|
||||
: m_spec_version(std::move(spec_version))
|
||||
{
|
||||
}
|
||||
|
||||
auto SpecBase::version_str() const -> std::string
|
||||
{
|
||||
return m_spec_version;
|
||||
}
|
||||
|
||||
auto SpecBase::compatible_prefix() const -> std::string
|
||||
{
|
||||
auto split_spec_version = util::split(m_spec_version, ".", 2);
|
||||
auto spec_version_major = std::stoi(split_spec_version[0]);
|
||||
if (spec_version_major == 0)
|
||||
{
|
||||
return split_spec_version[0] + "." + split_spec_version[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
return split_spec_version[0];
|
||||
}
|
||||
}
|
||||
|
||||
auto SpecBase::upgrade_prefix() const -> std::vector<std::string>
|
||||
{
|
||||
auto split_spec_version = util::split(m_spec_version, ".", 2);
|
||||
auto spec_version_major = std::stoi(split_spec_version[0]);
|
||||
auto spec_version_minor = std::stoi(split_spec_version[1]);
|
||||
if (spec_version_major == 0)
|
||||
{
|
||||
// Return the most recent possible upgrade first
|
||||
return { "1", split_spec_version[0] + "." + std::to_string(spec_version_minor + 1) };
|
||||
}
|
||||
else
|
||||
{
|
||||
return { std::to_string(spec_version_major + 1) };
|
||||
}
|
||||
}
|
||||
|
||||
auto SpecBase::is_compatible(const fs::u8path& p) const -> bool
|
||||
{
|
||||
std::regex name_re;
|
||||
std::smatch matches;
|
||||
std::size_t min_match_size;
|
||||
|
||||
std::string f_name = p.filename().string();
|
||||
std::string f_spec_version_str, f_version_str, f_type, f_ext;
|
||||
|
||||
name_re = R"(^(?:[1-9]+\d*.)?(?:sv([1-9]\d*|0\.[1-9]\d*).)?(\w+)\.(\w+)$)";
|
||||
min_match_size = 3;
|
||||
|
||||
if (std::regex_search(f_name, matches, name_re) && (min_match_size <= matches.size()))
|
||||
{
|
||||
f_spec_version_str = matches[1].str();
|
||||
if (!f_spec_version_str.empty())
|
||||
{
|
||||
return is_compatible(matches[1].str() + ".");
|
||||
}
|
||||
else
|
||||
{
|
||||
std::ifstream i(p.std_path());
|
||||
nlohmann::json j;
|
||||
i >> j;
|
||||
return is_compatible(j);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto SpecBase::is_compatible(const std::string& version) const -> bool
|
||||
{
|
||||
return util::starts_with(version, compatible_prefix() + ".");
|
||||
}
|
||||
|
||||
auto SpecBase::is_compatible(const nlohmann::json& j) const -> bool
|
||||
{
|
||||
auto spec_version = get_json_value(j);
|
||||
if (!spec_version.empty())
|
||||
{
|
||||
return is_compatible(spec_version);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto SpecBase::is_upgrade(const std::string& version) const -> bool
|
||||
{
|
||||
auto upgrade_prefixes = upgrade_prefix();
|
||||
std::vector<std::string_view> possible_upgrades;
|
||||
for (auto& s : upgrade_prefixes)
|
||||
{
|
||||
s += '.';
|
||||
possible_upgrades.push_back(s);
|
||||
}
|
||||
|
||||
return util::starts_with_any(version, possible_upgrades);
|
||||
}
|
||||
|
||||
auto SpecBase::is_upgrade(const nlohmann::json& j) const -> bool
|
||||
{
|
||||
auto spec_version = get_json_value(j);
|
||||
if (!spec_version.empty())
|
||||
{
|
||||
return is_upgrade(spec_version);
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto SpecBase::get_json_value(const nlohmann::json& j) const -> std::string
|
||||
{
|
||||
try
|
||||
{
|
||||
return j.at("signed").at(json_key()).get<std::string>();
|
||||
}
|
||||
catch (const nlohmann::json::exception& e)
|
||||
{
|
||||
LOG_DEBUG << "Invalid 'root' metadata, impossible to check spec version compatibility: "
|
||||
<< e.what();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
auto SpecBase::canonicalize(const nlohmann::json& j) const -> std::string
|
||||
{
|
||||
return j.dump();
|
||||
}
|
||||
|
||||
auto SpecBase::upgradable() const -> bool
|
||||
{
|
||||
return false;
|
||||
};
|
||||
|
||||
auto operator==(const SpecBase& sv1, const SpecBase& sv2) -> bool
|
||||
{
|
||||
return sv1.version_str() == sv2.version_str();
|
||||
}
|
||||
|
||||
auto operator!=(const SpecBase& sv1, const SpecBase& sv2) -> bool
|
||||
{
|
||||
return sv1.version_str() != sv2.version_str();
|
||||
}
|
||||
|
||||
RoleBase::RoleBase(std::string type, std::shared_ptr<SpecBase> sv)
|
||||
: m_type(std::move(type))
|
||||
{
|
||||
p_spec = std::move(sv);
|
||||
};
|
||||
|
||||
RoleBase::~RoleBase() = default;
|
||||
|
||||
auto RoleBase::type() const -> std::string
|
||||
{
|
||||
return m_type;
|
||||
}
|
||||
|
||||
auto RoleBase::version() const -> std::size_t
|
||||
{
|
||||
return m_version;
|
||||
}
|
||||
|
||||
auto RoleBase::expires() const -> std::string
|
||||
{
|
||||
return m_expires;
|
||||
}
|
||||
|
||||
auto RoleBase::file_ext() const -> std::string
|
||||
{
|
||||
return m_ext;
|
||||
}
|
||||
|
||||
auto RoleBase::spec_version() const -> SpecBase&
|
||||
{
|
||||
return *p_spec;
|
||||
}
|
||||
|
||||
auto RoleBase::spec_impl() const -> std::shared_ptr<SpecBase>
|
||||
{
|
||||
return p_spec;
|
||||
}
|
||||
|
||||
auto RoleBase::expired(const TimeRef& time_reference) const -> bool
|
||||
{
|
||||
return time_reference.timestamp().compare(m_expires) < 0 ? false : true;
|
||||
}
|
||||
|
||||
auto RoleBase::canonicalize(const nlohmann::json& j) const -> std::string
|
||||
{
|
||||
return p_spec->canonicalize(j);
|
||||
}
|
||||
|
||||
auto RoleBase::signatures(const nlohmann::json& j) const -> std::set<RoleSignature>
|
||||
{
|
||||
return p_spec->signatures(j);
|
||||
}
|
||||
|
||||
auto RoleBase::roles() const -> std::set<std::string>
|
||||
{
|
||||
std::set<std::string> r;
|
||||
for (auto& it : m_defined_roles)
|
||||
{
|
||||
r.insert(it.first);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
auto RoleBase::mandatory_defined_roles() const -> std::set<std::string>
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
auto RoleBase::optionally_defined_roles() const -> std::set<std::string>
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
void RoleBase::check_expiration_format() const
|
||||
{
|
||||
check_timestamp_metadata_format(m_expires);
|
||||
}
|
||||
|
||||
void RoleBase::check_defined_roles(bool allow_any) const
|
||||
{
|
||||
auto mandatory_roles = mandatory_defined_roles();
|
||||
auto optional_roles = optionally_defined_roles();
|
||||
auto all_roles = mandatory_roles;
|
||||
all_roles.insert(optional_roles.cbegin(), optional_roles.cend());
|
||||
|
||||
if (!allow_any)
|
||||
{
|
||||
for (const auto& r : roles())
|
||||
{
|
||||
if (all_roles.find(r) == all_roles.end())
|
||||
{
|
||||
LOG_ERROR << "Invalid role defined in '" << type() << "' metadata: '" << r << "'";
|
||||
throw role_metadata_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto current_roles = roles();
|
||||
if (!std::includes(
|
||||
current_roles.begin(),
|
||||
current_roles.end(),
|
||||
mandatory_roles.begin(),
|
||||
mandatory_roles.end()
|
||||
))
|
||||
{
|
||||
std::vector<std::string> diff;
|
||||
std::set_difference(
|
||||
mandatory_roles.begin(),
|
||||
mandatory_roles.end(),
|
||||
current_roles.begin(),
|
||||
current_roles.end(),
|
||||
std::inserter(diff, diff.end())
|
||||
);
|
||||
LOG_ERROR << "Missing roles while loading '" << type() << "' metadata: '"
|
||||
<< ::mamba::util::join(", ", diff) << "'";
|
||||
throw role_metadata_error();
|
||||
}
|
||||
|
||||
for (const auto& r : all_keys())
|
||||
{
|
||||
auto r_keys = r.second;
|
||||
if (r_keys.keys.empty())
|
||||
{
|
||||
LOG_ERROR << "'" << type()
|
||||
<< "' metadata should declare at least one key ID for role: '" << r.first
|
||||
<< "'";
|
||||
throw role_metadata_error();
|
||||
}
|
||||
if (r_keys.threshold == 0)
|
||||
{
|
||||
LOG_ERROR << "'" << type()
|
||||
<< "' metadata should declare at least a 'threshold' of 1 for role: '"
|
||||
<< r.first << "'";
|
||||
throw role_metadata_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto RoleBase::all_keys() const -> std::map<std::string, RoleFullKeys>
|
||||
{
|
||||
return m_defined_roles;
|
||||
}
|
||||
|
||||
void RoleBase::set_spec_version(std::shared_ptr<SpecBase> new_sv)
|
||||
{
|
||||
auto& current_sv = spec_version();
|
||||
|
||||
if (!current_sv.is_compatible(new_sv->version_str()))
|
||||
{
|
||||
LOG_ERROR << "Incompatible 'spec_version' found in 'root' metadata, should start with '"
|
||||
<< current_sv.compatible_prefix() << "' but is: '" << new_sv->version_str()
|
||||
<< "'";
|
||||
|
||||
throw spec_version_error();
|
||||
}
|
||||
|
||||
p_spec = std::move(new_sv);
|
||||
}
|
||||
|
||||
void RoleBase::set_expiration(const std::string& expires)
|
||||
{
|
||||
m_expires = expires;
|
||||
}
|
||||
|
||||
auto RoleBase::read_json_file(const fs::u8path& p, bool update) const -> nlohmann::json
|
||||
{
|
||||
if (!fs::exists(p))
|
||||
{
|
||||
LOG_ERROR << "File not found for '" << type() << "' role: " << p.string();
|
||||
throw role_file_error();
|
||||
}
|
||||
|
||||
std::regex name_re;
|
||||
std::smatch matches;
|
||||
std::size_t min_match_size;
|
||||
|
||||
std::string f_name = p.filename().string();
|
||||
std::string f_spec_version_str, f_version_str, f_type, f_ext;
|
||||
|
||||
// Files should be named using one of the following structures:
|
||||
// - Trusted (reference) file:
|
||||
// - FILENAME.EXT
|
||||
// - svSPEC_VERSION_MAJOR.FILENAME.EXT
|
||||
// - Update file:
|
||||
// - VERSION_NUMBER.FILENAME.EXT
|
||||
// - VERSION_NUMBER.svSPEC_VERSION_MAJOR.FILENAME.EXT
|
||||
if (update)
|
||||
{
|
||||
name_re = R"(^(?:([1-9]+\d*).)(?:sv([1-9]\d*|0\.[1-9]\d*).)?(\w+)\.(\w+)$)";
|
||||
min_match_size = 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
name_re = R"(^(?:[1-9]+\d*.)?(?:sv([1-9]\d*|0\.[1-9]\d*).)?(\w+)\.(\w+)$)";
|
||||
min_match_size = 3;
|
||||
}
|
||||
|
||||
if (std::regex_search(f_name, matches, name_re) && (min_match_size <= matches.size()))
|
||||
{
|
||||
auto match_size = matches.size();
|
||||
|
||||
if (update)
|
||||
{
|
||||
f_version_str = matches[1].str();
|
||||
}
|
||||
|
||||
f_type = matches[match_size - 2].str();
|
||||
f_ext = matches[match_size - 1].str();
|
||||
|
||||
if ((min_match_size + 1) == match_size)
|
||||
{
|
||||
f_spec_version_str = matches[match_size - 3].str();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR << "Invalid file name for 'root' metadata update: " << f_name;
|
||||
throw role_file_error();
|
||||
}
|
||||
|
||||
if (f_ext != file_ext())
|
||||
{
|
||||
LOG_ERROR << "'root' metadata file should have 'json' extension, not: '" << f_ext << "'";
|
||||
throw role_file_error();
|
||||
}
|
||||
if (f_type != type())
|
||||
{
|
||||
LOG_ERROR << "'root' metadata file should have 'root' type, not: '" << f_type << "'";
|
||||
throw role_file_error();
|
||||
}
|
||||
if (!f_spec_version_str.empty())
|
||||
{
|
||||
auto new_spec_version_str = f_spec_version_str + ".";
|
||||
|
||||
if (update && spec_version().is_upgrade(new_spec_version_str)
|
||||
&& !spec_version().upgradable())
|
||||
{
|
||||
LOG_ERROR << "Please check for a client update, unsupported spec version: '"
|
||||
<< f_spec_version_str << "'";
|
||||
throw spec_version_error();
|
||||
}
|
||||
else if (!((!update && spec_version().is_compatible(new_spec_version_str))
|
||||
|| (update && spec_version().is_upgrade(new_spec_version_str))))
|
||||
{
|
||||
LOG_ERROR << "Invalid spec version specified in file name: '" << f_spec_version_str
|
||||
<< "'";
|
||||
throw role_file_error();
|
||||
}
|
||||
}
|
||||
|
||||
if (update)
|
||||
{
|
||||
// Check version number in filename is N+1
|
||||
unsigned long f_version;
|
||||
try
|
||||
{
|
||||
f_version = std::stoul(f_version_str);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LOG_ERROR << "Invalid version in file name for 'root' metadata update: "
|
||||
<< f_version_str;
|
||||
throw role_file_error();
|
||||
}
|
||||
if (f_version != (version() + 1))
|
||||
{
|
||||
LOG_ERROR << "'root' metadata file name should start with N+1 version ("
|
||||
<< version() + 1 << "), but starts with: " << f_version;
|
||||
throw role_file_error();
|
||||
}
|
||||
}
|
||||
|
||||
std::ifstream i(p.std_path());
|
||||
nlohmann::json j;
|
||||
i >> j;
|
||||
|
||||
return j;
|
||||
}
|
||||
|
||||
void RoleBase::check_role_signatures(const nlohmann::json& data, const RoleBase& role)
|
||||
{
|
||||
std::string signed_data = role.canonicalize(data["signed"]);
|
||||
auto signatures = role.signatures(data);
|
||||
auto k = self_keys();
|
||||
|
||||
try
|
||||
{
|
||||
check_signatures(signed_data, signatures, k);
|
||||
}
|
||||
catch (const threshold_error& e)
|
||||
{
|
||||
LOG_ERROR << "Validation failed on role '" << type() << "' : " << e.what();
|
||||
throw role_error();
|
||||
}
|
||||
}
|
||||
|
||||
void RoleBase::check_signatures(
|
||||
const std::string& signed_data,
|
||||
const std::set<RoleSignature>& signatures,
|
||||
const RoleFullKeys& keyring
|
||||
) const
|
||||
{
|
||||
std::size_t valid_sig = 0;
|
||||
|
||||
for (auto& s : signatures)
|
||||
{
|
||||
auto it = keyring.keys.find(s.keyid);
|
||||
if (it != keyring.keys.end())
|
||||
{
|
||||
auto& pk = it->second.keyval;
|
||||
int status;
|
||||
|
||||
if (s.pgp_trailer.empty())
|
||||
{
|
||||
status = verify(signed_data, pk, s.sig);
|
||||
}
|
||||
else
|
||||
{
|
||||
status = verify_gpg(signed_data, s.pgp_trailer, pk, s.sig);
|
||||
}
|
||||
|
||||
if (status == 1)
|
||||
{
|
||||
++valid_sig;
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_WARNING << "Invalid signature of metadata using keyid: " << s.keyid;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_WARNING << "Invalid keyid: " << s.keyid;
|
||||
}
|
||||
if (valid_sig >= keyring.threshold)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (valid_sig < keyring.threshold)
|
||||
{
|
||||
LOG_ERROR << "Threshold of valid signatures is not met (" << valid_sig << "/"
|
||||
<< keyring.threshold << ")";
|
||||
throw threshold_error();
|
||||
}
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json& j, const RoleBase& role)
|
||||
{
|
||||
j = { { "version", role.version() }, { "expires", role.expires() } };
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& j, RoleBase& role)
|
||||
{
|
||||
role.m_version = j.at("version");
|
||||
role.set_expiration(j.at(role.spec_version().expiration_json_key()));
|
||||
}
|
||||
|
||||
RootRole::RootRole(std::shared_ptr<SpecBase> spec)
|
||||
: RoleBase("root", spec)
|
||||
{
|
||||
}
|
||||
|
||||
auto RootRole::possible_update_files() -> std::vector<fs::u8path>
|
||||
{
|
||||
auto new_v = std::to_string(version() + 1);
|
||||
auto compat_spec = spec_impl()->compatible_prefix();
|
||||
auto upgrade_spec = spec_impl()->upgrade_prefix();
|
||||
|
||||
std::vector<fs::u8path> files;
|
||||
// upgrade first
|
||||
for (auto& s : upgrade_spec)
|
||||
{
|
||||
files.emplace_back(
|
||||
::mamba::util::join(".", std::vector<std::string>({ new_v, "sv" + s, "root.json" }))
|
||||
);
|
||||
}
|
||||
// compatible next
|
||||
files.emplace_back(::mamba::util::join(
|
||||
".",
|
||||
std::vector<std::string>({ new_v, "sv" + compat_spec, "root.json" })
|
||||
));
|
||||
// then finally undefined spec
|
||||
files.emplace_back(::mamba::util::join(".", std::vector<std::string>({ new_v, "root.json" })));
|
||||
|
||||
return files;
|
||||
}
|
||||
|
||||
auto RootRole::update(fs::u8path path) -> std::unique_ptr<RootRole>
|
||||
{
|
||||
auto j = read_json_file(path, true);
|
||||
return update(j);
|
||||
}
|
||||
|
||||
// `create_update` currently catch a possible spec version update by testing
|
||||
// and extracting spec version from JSON. It could be done upstream (in
|
||||
// `update(fs::u8path)`) if we decide to specify the spec version in the file name.
|
||||
// The filename would take the form VERSION_NUMBER.SPECVERSION.FILENAME.EXT
|
||||
// To disambiguate version and spec version: 1.sv0.6.root.json or 1.sv1.root.json
|
||||
auto RootRole::update(nlohmann::json j) -> std::unique_ptr<RootRole>
|
||||
{
|
||||
// TUF spec 5.3.4 - Check for an arbitrary software attack
|
||||
// Check signatures against current keyids and threshold in 'RootImpl' constructor
|
||||
auto root_update = create_update(j);
|
||||
|
||||
// Check signatures against new keyids and threshold
|
||||
// check_role_signatures(j, *root_update);
|
||||
|
||||
// TUF spec 5.3.5 - Check for a rollback attack
|
||||
// Version number has to be N+1
|
||||
if (root_update->version() != (version() + 1))
|
||||
{
|
||||
if (root_update->version() > (version() + 1))
|
||||
{
|
||||
LOG_ERROR << "Invalid 'root' metadata version, should be exactly N+1";
|
||||
throw role_metadata_error();
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR << "Possible rollback attack of 'root' metadata";
|
||||
throw rollback_error();
|
||||
}
|
||||
}
|
||||
return root_update;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,677 @@
|
|||
// Copyright (c) 2023, 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 <fstream>
|
||||
#include <utility>
|
||||
|
||||
#include "mamba/core/download.hpp"
|
||||
#include "mamba/core/output.hpp"
|
||||
#include "mamba/core/util.hpp"
|
||||
#include "mamba/specs/conda_url.hpp"
|
||||
#include "mamba/util/encoding.hpp"
|
||||
#include "mamba/validation/errors.hpp"
|
||||
#include "mamba/validation/tools.hpp"
|
||||
#include "mamba/validation/update_framework_v0_6.hpp"
|
||||
#include "mamba/validation/update_framework_v1.hpp"
|
||||
|
||||
namespace mamba::validation::v0_6
|
||||
{
|
||||
SpecImpl::SpecImpl(std::string sv)
|
||||
: SpecBase(std::move(sv))
|
||||
{
|
||||
}
|
||||
|
||||
auto SpecImpl::json_key() const -> std::string
|
||||
{
|
||||
return "metadata_spec_version";
|
||||
}
|
||||
|
||||
auto SpecImpl::expiration_json_key() const -> std::string
|
||||
{
|
||||
return "expiration";
|
||||
}
|
||||
|
||||
auto SpecImpl::signatures(const nlohmann::json& j) const -> std::set<RoleSignature>
|
||||
{
|
||||
auto sigs = j.at("signatures").get<std::map<std::string, std::map<std::string, std::string>>>();
|
||||
std::set<RoleSignature> unique_sigs;
|
||||
|
||||
for (auto& s : sigs)
|
||||
{
|
||||
std::string pgp_trailer = "";
|
||||
if (s.second.find("other_headers") != s.second.end())
|
||||
{
|
||||
pgp_trailer = s.second["other_headers"];
|
||||
}
|
||||
|
||||
unique_sigs.insert(RoleSignature({ s.first, s.second.at("signature"), pgp_trailer }));
|
||||
}
|
||||
|
||||
return unique_sigs;
|
||||
}
|
||||
|
||||
auto SpecImpl::upgradable() const -> bool
|
||||
{
|
||||
return true;
|
||||
};
|
||||
|
||||
auto SpecImpl::canonicalize(const nlohmann::json& j) const -> std::string
|
||||
{
|
||||
return j.dump(2);
|
||||
}
|
||||
|
||||
void V06RoleBaseExtension::check_timestamp_format() const
|
||||
{
|
||||
check_timestamp_metadata_format(m_timestamp);
|
||||
}
|
||||
|
||||
void V06RoleBaseExtension::set_timestamp(const std::string& ts)
|
||||
{
|
||||
m_timestamp = ts;
|
||||
}
|
||||
|
||||
auto V06RoleBaseExtension::timestamp() const -> std::string
|
||||
{
|
||||
return m_timestamp;
|
||||
}
|
||||
|
||||
RootImpl::RootImpl(const fs::u8path& path)
|
||||
: RootRole(std::make_shared<SpecImpl>())
|
||||
{
|
||||
auto j = read_json_file(path);
|
||||
load_from_json(j);
|
||||
}
|
||||
|
||||
RootImpl::RootImpl(const nlohmann::json& j)
|
||||
: RootRole(std::make_shared<SpecImpl>())
|
||||
{
|
||||
load_from_json(j);
|
||||
}
|
||||
|
||||
RootImpl::RootImpl(const std::string& json_str)
|
||||
: RootRole(std::make_shared<SpecImpl>())
|
||||
{
|
||||
load_from_json(nlohmann::json::parse(json_str));
|
||||
}
|
||||
|
||||
auto RootImpl::create_update(const nlohmann::json& j) -> std::unique_ptr<RootRole>
|
||||
{
|
||||
if (SpecImpl().is_compatible(j))
|
||||
{
|
||||
return std::make_unique<RootImpl>(j);
|
||||
}
|
||||
else if (v1::SpecImpl().is_compatible(j))
|
||||
{
|
||||
LOG_DEBUG << "Updating 'root' role spec version";
|
||||
return std::make_unique<v1::RootImpl>(j);
|
||||
}
|
||||
{
|
||||
LOG_ERROR << "Invalid spec version for 'root' update";
|
||||
throw spec_version_error();
|
||||
}
|
||||
}
|
||||
|
||||
void RootImpl::load_from_json(const nlohmann::json& j)
|
||||
{
|
||||
from_json(j, *this);
|
||||
|
||||
// TUF spec 5.3.4 - Check for an arbitrary software attack
|
||||
// Check signatures against current keyids and threshold
|
||||
check_role_signatures(j, *this);
|
||||
}
|
||||
|
||||
auto RootImpl::upgraded_signable() const -> nlohmann::json
|
||||
{
|
||||
nlohmann::json v1_equivalent_root;
|
||||
|
||||
v1_equivalent_root["roles"]["root"] = m_defined_roles.at("root").to_roles();
|
||||
v1_equivalent_root["roles"]["targets"] = m_defined_roles.at("key_mgr").to_roles();
|
||||
v1_equivalent_root["roles"]["snapshot"] = RoleKeys({ std::vector<std::string>(), 1 });
|
||||
v1_equivalent_root["roles"]["timestamp"] = RoleKeys({ std::vector<std::string>(), 1 });
|
||||
|
||||
std::map<std::string, Key> v1_keys = m_defined_roles.at("root").to_keys();
|
||||
auto key_mgr_keys = m_defined_roles.at("key_mgr").to_keys();
|
||||
v1_keys.insert(key_mgr_keys.cbegin(), key_mgr_keys.cend());
|
||||
|
||||
v1_equivalent_root["keys"] = v1_keys;
|
||||
v1_equivalent_root["_type"] = "root";
|
||||
v1_equivalent_root["version"] = version();
|
||||
v1_equivalent_root["spec_version"] = "1.0.17";
|
||||
v1_equivalent_root["expires"] = expires();
|
||||
|
||||
return v1_equivalent_root;
|
||||
}
|
||||
|
||||
auto
|
||||
RootImpl::upgraded_signature(const nlohmann::json& j, const std::string& pk, const std::byte* sk) const
|
||||
-> RoleSignature
|
||||
{
|
||||
std::array<std::byte, MAMBA_ED25519_SIGSIZE_BYTES> sig_bin;
|
||||
sign(j.dump(), sk, sig_bin.data());
|
||||
|
||||
const auto sig_bin_data = sig_bin.data();
|
||||
auto sig_hex = util::bytes_to_hex_str(sig_bin_data, sig_bin_data + sig_bin.size());
|
||||
|
||||
return { pk, sig_hex };
|
||||
}
|
||||
|
||||
auto RootImpl::self_keys() const -> RoleFullKeys
|
||||
{
|
||||
return m_defined_roles.at("root");
|
||||
}
|
||||
|
||||
auto RootImpl::mandatory_defined_roles() const -> std::set<std::string>
|
||||
{
|
||||
return { "root", "key_mgr" };
|
||||
}
|
||||
|
||||
auto RootImpl::optionally_defined_roles() const -> std::set<std::string>
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
void RootImpl::set_defined_roles(std::map<std::string, RolePubKeys> keys)
|
||||
{
|
||||
m_defined_roles.clear();
|
||||
for (auto& it : keys)
|
||||
{
|
||||
std::map<std::string, Key> role_keys;
|
||||
for (auto& key : it.second.pubkeys)
|
||||
{
|
||||
role_keys.insert({ key, Key::from_ed25519(key) });
|
||||
}
|
||||
m_defined_roles.insert({ it.first, { role_keys, it.second.threshold } });
|
||||
}
|
||||
}
|
||||
|
||||
auto RootImpl::build_index_checker(
|
||||
Context& context,
|
||||
const TimeRef& time_reference,
|
||||
const std::string& base_url,
|
||||
const fs::u8path& cache_path
|
||||
) const -> std::unique_ptr<RepoIndexChecker>
|
||||
{
|
||||
fs::u8path metadata_path = cache_path / "key_mgr.json";
|
||||
|
||||
auto tmp_dir = TemporaryDirectory();
|
||||
auto tmp_metadata_path = tmp_dir.path() / "key_mgr.json";
|
||||
|
||||
const auto url = specs::CondaURL::parse(base_url) / "key_mgr.json";
|
||||
|
||||
if (check_resource_exists(url.pretty_str(), context))
|
||||
{
|
||||
DownloadRequest request(
|
||||
"key_mgr.json",
|
||||
url.str(util::URL::Credentials::Show),
|
||||
tmp_metadata_path.string()
|
||||
);
|
||||
DownloadResult res = download(std::move(request), context);
|
||||
if (res)
|
||||
{
|
||||
KeyMgrRole key_mgr = create_key_mgr(tmp_metadata_path);
|
||||
|
||||
// TUF spec 5.6.5 - Check for a freeze attack
|
||||
// 'key_mgr' (equivalent of 'targets') role should not be expired
|
||||
// https://theupdateframework.github.io/specification/latest/#update-targets
|
||||
if (key_mgr.expired(time_reference))
|
||||
{
|
||||
LOG_ERROR << "Possible freeze attack of 'key_mgr' metadata.\nExpired: "
|
||||
<< key_mgr.expires();
|
||||
throw freeze_error();
|
||||
}
|
||||
|
||||
// TUF spec 5.6.6 - Persist targets metadata
|
||||
if (!cache_path.empty())
|
||||
{
|
||||
if (fs::exists(metadata_path))
|
||||
{
|
||||
fs::remove(metadata_path);
|
||||
}
|
||||
fs::copy(tmp_metadata_path, metadata_path);
|
||||
}
|
||||
|
||||
return key_mgr.build_index_checker(context, time_reference, base_url, cache_path);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to local cached-copy if existing
|
||||
if (fs::exists(metadata_path))
|
||||
{
|
||||
KeyMgrRole key_mgr = create_key_mgr(metadata_path);
|
||||
return key_mgr.build_index_checker(context, time_reference, base_url, cache_path);
|
||||
}
|
||||
|
||||
LOG_ERROR << "Error while fetching 'key_mgr' metadata";
|
||||
throw fetching_error();
|
||||
}
|
||||
|
||||
auto RootImpl::create_key_mgr(const fs::u8path& p) const -> KeyMgrRole
|
||||
{
|
||||
return { p, all_keys()["key_mgr"], spec_impl() };
|
||||
}
|
||||
|
||||
auto RootImpl::create_key_mgr(const nlohmann::json& j) const -> KeyMgrRole
|
||||
{
|
||||
return { j, all_keys()["key_mgr"], spec_impl() };
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json& j, const RootImpl& r)
|
||||
{
|
||||
to_json(j, static_cast<const RoleBase&>(r));
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& j, RootImpl& role)
|
||||
{
|
||||
auto j_signed = j.at("signed");
|
||||
try
|
||||
{
|
||||
from_json(j_signed, static_cast<RoleBase&>(role));
|
||||
|
||||
role.set_timestamp(j_signed.at("timestamp").get<std::string>());
|
||||
|
||||
auto type = j_signed.at("type").get<std::string>();
|
||||
if (type != role.type())
|
||||
{
|
||||
LOG_ERROR << "Wrong 'type' found in 'root' metadata, should be 'root': '" << type
|
||||
<< "'";
|
||||
throw role_metadata_error();
|
||||
}
|
||||
|
||||
role.set_spec_version(
|
||||
std::make_shared<SpecImpl>(j_signed.at("metadata_spec_version").get<std::string>())
|
||||
);
|
||||
|
||||
role.set_defined_roles(
|
||||
j_signed.at("delegations").get<std::map<std::string, RolePubKeys>>()
|
||||
);
|
||||
}
|
||||
catch (const nlohmann::json::exception& e)
|
||||
{
|
||||
LOG_ERROR << "Invalid 'root' metadata: " << e.what();
|
||||
throw role_metadata_error();
|
||||
}
|
||||
|
||||
role.check_expiration_format();
|
||||
role.check_timestamp_format();
|
||||
role.check_defined_roles();
|
||||
}
|
||||
|
||||
KeyMgrRole::KeyMgrRole(const fs::u8path& p, RoleFullKeys keys, const std::shared_ptr<SpecBase> spec)
|
||||
: RoleBase("key_mgr", spec)
|
||||
, m_keys(std::move(keys))
|
||||
{
|
||||
auto j = read_json_file(p);
|
||||
load_from_json(j);
|
||||
}
|
||||
|
||||
KeyMgrRole::KeyMgrRole(const nlohmann::json& j, RoleFullKeys keys, const std::shared_ptr<SpecBase> spec)
|
||||
: RoleBase("key_mgr", spec)
|
||||
, m_keys(std::move(keys))
|
||||
{
|
||||
load_from_json(j);
|
||||
}
|
||||
|
||||
KeyMgrRole::KeyMgrRole(
|
||||
const std::string& json_str,
|
||||
RoleFullKeys keys,
|
||||
const std::shared_ptr<SpecBase> spec
|
||||
)
|
||||
: RoleBase("key_mgr", spec)
|
||||
, m_keys(std::move(keys))
|
||||
{
|
||||
load_from_json(nlohmann::json::parse(json_str));
|
||||
}
|
||||
|
||||
void KeyMgrRole::load_from_json(const nlohmann::json& j)
|
||||
{
|
||||
from_json(j, *this);
|
||||
// Check signatures against keyids and threshold
|
||||
check_role_signatures(j, *this);
|
||||
}
|
||||
|
||||
auto KeyMgrRole::self_keys() const -> RoleFullKeys
|
||||
{
|
||||
return m_keys;
|
||||
}
|
||||
|
||||
auto KeyMgrRole::create_pkg_mgr(const fs::u8path& p) const -> PkgMgrRole
|
||||
{
|
||||
return { p, all_keys()["pkg_mgr"], spec_impl() };
|
||||
}
|
||||
|
||||
auto KeyMgrRole::create_pkg_mgr(const nlohmann::json& j) const -> PkgMgrRole
|
||||
{
|
||||
return { j, all_keys()["pkg_mgr"], spec_impl() };
|
||||
}
|
||||
|
||||
auto KeyMgrRole::build_index_checker(
|
||||
Context& context,
|
||||
const TimeRef& time_reference,
|
||||
const std::string& base_url,
|
||||
const fs::u8path& cache_path
|
||||
) const -> std::unique_ptr<RepoIndexChecker>
|
||||
{
|
||||
fs::u8path metadata_path = cache_path / "pkg_mgr.json";
|
||||
|
||||
auto tmp_dir = TemporaryDirectory();
|
||||
auto tmp_metadata_path = tmp_dir.path() / "pkg_mgr.json";
|
||||
|
||||
const auto url = mamba::util::URL::parse(base_url + "/pkg_mgr.json");
|
||||
|
||||
if (check_resource_exists(url.pretty_str(), context))
|
||||
{
|
||||
DownloadRequest request("pkg_mgr.json", url.pretty_str(), tmp_metadata_path.string());
|
||||
DownloadResult res = download(std::move(request), context);
|
||||
|
||||
if (res)
|
||||
{
|
||||
PkgMgrRole pkg_mgr = create_pkg_mgr(tmp_metadata_path);
|
||||
|
||||
// TUF spec 5.6.5 - Check for a freeze attack
|
||||
// 'pkg_mgr' (equivalent of delegated 'targets') role should not be expired
|
||||
// https://theupdateframework.github.io/specification/latest/#update-targets
|
||||
if (pkg_mgr.expired(time_reference))
|
||||
{
|
||||
LOG_ERROR << "Possible freeze attack of 'pkg_mgr' metadata.\nExpired: "
|
||||
<< pkg_mgr.expires();
|
||||
throw freeze_error();
|
||||
}
|
||||
|
||||
// TUF spec 5.6.6 - Persist targets metadata
|
||||
if (!cache_path.empty())
|
||||
{
|
||||
if (fs::exists(metadata_path))
|
||||
{
|
||||
fs::remove(metadata_path);
|
||||
}
|
||||
fs::copy(tmp_metadata_path, metadata_path);
|
||||
}
|
||||
|
||||
return std::make_unique<PkgMgrRole>(pkg_mgr);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to local cached-copy if existing
|
||||
if (fs::exists(metadata_path))
|
||||
{
|
||||
return std::make_unique<PkgMgrRole>(create_pkg_mgr(metadata_path));
|
||||
}
|
||||
|
||||
LOG_ERROR << "Error while fetching 'pkg_mgr' metadata";
|
||||
throw fetching_error();
|
||||
}
|
||||
|
||||
auto KeyMgrRole::mandatory_defined_roles() const -> std::set<std::string>
|
||||
{
|
||||
return { "pkg_mgr" };
|
||||
}
|
||||
|
||||
auto KeyMgrRole::optionally_defined_roles() const -> std::set<std::string>
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
void KeyMgrRole::set_defined_roles(std::map<std::string, RolePubKeys> keys)
|
||||
{
|
||||
m_defined_roles.clear();
|
||||
for (auto& it : keys)
|
||||
{
|
||||
std::map<std::string, Key> role_keys;
|
||||
for (auto& key : it.second.pubkeys)
|
||||
{
|
||||
role_keys.insert({ key, Key::from_ed25519(key) });
|
||||
}
|
||||
m_defined_roles.insert({ it.first, { role_keys, it.second.threshold } });
|
||||
}
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json& j, const KeyMgrRole& r)
|
||||
{
|
||||
to_json(j, static_cast<const RoleBase&>(r));
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& j, KeyMgrRole& role)
|
||||
{
|
||||
auto j_signed = j.at("signed");
|
||||
try
|
||||
{
|
||||
from_json(j_signed, static_cast<RoleBase&>(role));
|
||||
|
||||
role.set_timestamp(j_signed.at("timestamp").get<std::string>());
|
||||
|
||||
auto type = j_signed.at("type").get<std::string>();
|
||||
if (type != role.type())
|
||||
{
|
||||
LOG_ERROR << "Wrong 'type' found in 'key_mgr' metadata, should be 'key_mgr': '"
|
||||
<< type << "'";
|
||||
throw role_metadata_error();
|
||||
}
|
||||
|
||||
auto new_spec_version = j_signed.at(role.spec_version().json_key()).get<std::string>();
|
||||
if (role.spec_version() != SpecImpl(new_spec_version))
|
||||
{
|
||||
LOG_ERROR << "Invalid spec version '" << new_spec_version
|
||||
<< "' in 'key_mgr' metadata, it should match exactly 'root' spec version: '"
|
||||
<< role.spec_version().version_str() << "'";
|
||||
throw spec_version_error();
|
||||
}
|
||||
|
||||
role.set_defined_roles(
|
||||
j_signed.at("delegations").get<std::map<std::string, RolePubKeys>>()
|
||||
);
|
||||
}
|
||||
catch (const nlohmann::json::exception& e)
|
||||
{
|
||||
LOG_ERROR << "Invalid 'key_mgr' metadata: " << e.what();
|
||||
throw role_metadata_error();
|
||||
}
|
||||
|
||||
role.check_expiration_format();
|
||||
role.check_timestamp_format();
|
||||
role.check_defined_roles();
|
||||
}
|
||||
|
||||
PkgMgrRole::PkgMgrRole(RoleFullKeys keys, const std::shared_ptr<SpecBase> spec)
|
||||
: RoleBase("pkg_mgr", spec)
|
||||
, m_keys(std::move(keys))
|
||||
{
|
||||
}
|
||||
|
||||
PkgMgrRole::PkgMgrRole(const fs::u8path& p, RoleFullKeys keys, const std::shared_ptr<SpecBase> spec)
|
||||
: RoleBase("pkg_mgr", spec)
|
||||
, m_keys(std::move(keys))
|
||||
{
|
||||
auto j = read_json_file(p);
|
||||
load_from_json(j);
|
||||
}
|
||||
|
||||
PkgMgrRole::PkgMgrRole(const nlohmann::json& j, RoleFullKeys keys, const std::shared_ptr<SpecBase> spec)
|
||||
: RoleBase("pkg_mgr", spec)
|
||||
, m_keys(std::move(keys))
|
||||
{
|
||||
load_from_json(j);
|
||||
}
|
||||
|
||||
PkgMgrRole::PkgMgrRole(
|
||||
const std::string& json_str,
|
||||
RoleFullKeys keys,
|
||||
const std::shared_ptr<SpecBase> spec
|
||||
)
|
||||
: RoleBase("pkg_mgr", spec)
|
||||
, m_keys(std::move(keys))
|
||||
{
|
||||
load_from_json(nlohmann::json::parse(json_str));
|
||||
}
|
||||
|
||||
void PkgMgrRole::load_from_json(const nlohmann::json& j)
|
||||
{
|
||||
from_json(j, *this);
|
||||
// Check signatures against keyids and threshold
|
||||
check_role_signatures(j, *this);
|
||||
}
|
||||
|
||||
void PkgMgrRole::set_defined_roles(std::map<std::string, RolePubKeys> keys)
|
||||
{
|
||||
m_defined_roles.clear();
|
||||
for (auto& it : keys)
|
||||
{
|
||||
std::map<std::string, Key> role_keys;
|
||||
for (auto& key : it.second.pubkeys)
|
||||
{
|
||||
role_keys.insert({ key, Key::from_ed25519(key) });
|
||||
}
|
||||
m_defined_roles.insert({ it.first, { role_keys, it.second.threshold } });
|
||||
}
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json& j, const PkgMgrRole& r)
|
||||
{
|
||||
to_json(j, static_cast<const RoleBase&>(r));
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& j, PkgMgrRole& role)
|
||||
{
|
||||
auto j_signed = j.at("signed");
|
||||
try
|
||||
{
|
||||
from_json(j_signed, static_cast<RoleBase&>(role));
|
||||
|
||||
role.set_timestamp(j_signed.at("timestamp").get<std::string>());
|
||||
|
||||
auto type = j_signed.at("type").get<std::string>();
|
||||
if (type != role.type())
|
||||
{
|
||||
LOG_ERROR << "Wrong 'type' found in 'pkg_mgr' metadata, should be 'pkg_mgr': '"
|
||||
<< type << "'";
|
||||
throw role_metadata_error();
|
||||
}
|
||||
|
||||
auto new_spec_version = j_signed.at(role.spec_version().json_key()).get<std::string>();
|
||||
if (role.spec_version() != SpecImpl(new_spec_version))
|
||||
{
|
||||
LOG_ERROR << "Invalid spec version '" << new_spec_version
|
||||
<< "' in 'pkg_mgr' metadata, it should match exactly 'root' spec version: '"
|
||||
<< role.spec_version().version_str() << "'";
|
||||
throw spec_version_error();
|
||||
}
|
||||
|
||||
role.set_defined_roles(
|
||||
j_signed.at("delegations").get<std::map<std::string, RolePubKeys>>()
|
||||
);
|
||||
}
|
||||
catch (const nlohmann::json::exception& e)
|
||||
{
|
||||
LOG_ERROR << "Invalid 'pkg_mgr' metadata: " << e.what();
|
||||
throw role_metadata_error();
|
||||
}
|
||||
|
||||
role.check_expiration_format();
|
||||
role.check_timestamp_format();
|
||||
role.check_defined_roles();
|
||||
}
|
||||
|
||||
auto PkgMgrRole::self_keys() const -> RoleFullKeys
|
||||
{
|
||||
return m_keys;
|
||||
}
|
||||
|
||||
auto PkgMgrRole::pkg_signatures(const nlohmann::json& j) const -> std::set<RoleSignature>
|
||||
{
|
||||
auto sigs = j.get<std::map<std::string, std::map<std::string, std::string>>>();
|
||||
std::set<RoleSignature> unique_sigs;
|
||||
|
||||
for (auto& s : sigs)
|
||||
{
|
||||
std::string pgp_trailer = "";
|
||||
if (s.second.find("other_headers") != s.second.end())
|
||||
{
|
||||
pgp_trailer = s.second["other_headers"];
|
||||
}
|
||||
|
||||
unique_sigs.insert(RoleSignature({ s.first, s.second.at("signature"), pgp_trailer }));
|
||||
}
|
||||
|
||||
return unique_sigs;
|
||||
}
|
||||
|
||||
void
|
||||
PkgMgrRole::check_pkg_signatures(const nlohmann::json& metadata, const nlohmann::json& signatures) const
|
||||
{
|
||||
std::string signed_data = canonicalize(metadata);
|
||||
auto sigs = pkg_signatures(signatures);
|
||||
auto k = self_keys();
|
||||
|
||||
check_signatures(signed_data, sigs, k);
|
||||
}
|
||||
|
||||
void PkgMgrRole::verify_index(const nlohmann::json& j) const
|
||||
{
|
||||
try
|
||||
{
|
||||
auto packages = j.at("packages").get<nlohmann::json::object_t>();
|
||||
auto sigs = j.at("signatures").get<nlohmann::json::object_t>();
|
||||
|
||||
for (auto& it : packages)
|
||||
{
|
||||
auto pkg_name = it.first;
|
||||
auto pkg_meta = it.second;
|
||||
auto pkg_sigs = sigs.at(pkg_name).get<nlohmann::json::object_t>();
|
||||
|
||||
try
|
||||
{
|
||||
check_pkg_signatures(pkg_meta, pkg_sigs);
|
||||
}
|
||||
catch (const threshold_error& e)
|
||||
{
|
||||
LOG_ERROR << "Validation failed on package: '" << pkg_name << "' : " << e.what();
|
||||
throw package_error();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (const nlohmann::json::exception& e)
|
||||
{
|
||||
LOG_ERROR << "Invalid package index metadata: " << e.what();
|
||||
throw index_error();
|
||||
}
|
||||
}
|
||||
|
||||
void PkgMgrRole::verify_index(const fs::u8path& p) const
|
||||
{
|
||||
if (!fs::exists(p))
|
||||
{
|
||||
LOG_ERROR << "'repodata' file not found at: " << p.string();
|
||||
throw index_error();
|
||||
}
|
||||
|
||||
std::ifstream i(p.std_path());
|
||||
nlohmann::json j;
|
||||
i >> j;
|
||||
|
||||
try
|
||||
{
|
||||
verify_index(j);
|
||||
}
|
||||
catch (const package_error& e)
|
||||
{
|
||||
LOG_ERROR << "Validation failed on package index: '" << p.string() << "' : " << e.what();
|
||||
throw index_error();
|
||||
}
|
||||
}
|
||||
void
|
||||
PkgMgrRole::verify_package(const nlohmann::json& signed_data, const nlohmann::json& signatures) const
|
||||
{
|
||||
try
|
||||
{
|
||||
check_pkg_signatures(signed_data, signatures);
|
||||
}
|
||||
catch (const threshold_error& e)
|
||||
{
|
||||
LOG_ERROR << "Validation failed on package: '" << signed_data.at("name")
|
||||
<< "' : " << e.what();
|
||||
throw package_error();
|
||||
}
|
||||
}
|
||||
} // namespace v06
|
|
@ -0,0 +1,165 @@
|
|||
// Copyright (c) 2023, 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 <nlohmann/json.hpp>
|
||||
|
||||
#include "mamba/core/output.hpp"
|
||||
#include "mamba/fs/filesystem.hpp"
|
||||
#include "mamba/validation/errors.hpp"
|
||||
#include "mamba/validation/update_framework_v1.hpp"
|
||||
|
||||
namespace mamba::validation::v1
|
||||
{
|
||||
SpecImpl::SpecImpl(std::string sv)
|
||||
: SpecBase(std::move(sv))
|
||||
{
|
||||
}
|
||||
|
||||
auto SpecImpl::json_key() const -> std::string
|
||||
{
|
||||
return "spec_version";
|
||||
}
|
||||
|
||||
auto SpecImpl::expiration_json_key() const -> std::string
|
||||
{
|
||||
return "expires";
|
||||
}
|
||||
|
||||
auto SpecImpl::signatures(const nlohmann::json& j) const -> std::set<RoleSignature>
|
||||
{
|
||||
auto sigs = j.at("signatures").get<std::vector<RoleSignature>>();
|
||||
std::set<RoleSignature> unique_sigs(sigs.cbegin(), sigs.cend());
|
||||
|
||||
return unique_sigs;
|
||||
}
|
||||
|
||||
RootImpl::RootImpl(const nlohmann::json& j)
|
||||
: RootRole(std::make_shared<SpecImpl>())
|
||||
|
||||
{
|
||||
load_from_json(j);
|
||||
}
|
||||
|
||||
RootImpl::RootImpl(const fs::u8path& path)
|
||||
: RootRole(std::make_shared<SpecImpl>())
|
||||
{
|
||||
auto j = read_json_file(path);
|
||||
load_from_json(j);
|
||||
}
|
||||
|
||||
auto RootImpl::create_update(const nlohmann::json& j) -> std::unique_ptr<RootRole>
|
||||
{
|
||||
if (v1::SpecImpl().is_compatible(j))
|
||||
{
|
||||
return std::make_unique<RootImpl>(j);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR << "Invalid spec version for 'root' update";
|
||||
throw spec_version_error();
|
||||
}
|
||||
}
|
||||
|
||||
void RootImpl::load_from_json(const nlohmann::json& j)
|
||||
{
|
||||
from_json(j, *this);
|
||||
|
||||
// TUF spec 5.3.4 - Check for an arbitrary software attack
|
||||
// Check signatures against current keyids and threshold
|
||||
check_role_signatures(j, *this);
|
||||
}
|
||||
|
||||
auto RootImpl::self_keys() const -> RoleFullKeys
|
||||
{
|
||||
return m_defined_roles.at("root");
|
||||
}
|
||||
|
||||
auto RootImpl::mandatory_defined_roles() const -> std::set<std::string>
|
||||
{
|
||||
return { "root", "snapshot", "targets", "timestamp" };
|
||||
}
|
||||
|
||||
auto RootImpl::optionally_defined_roles() const -> std::set<std::string>
|
||||
{
|
||||
return { "mirrors" };
|
||||
}
|
||||
|
||||
void RootImpl::set_defined_roles(
|
||||
const std::map<std::string, Key>& keys,
|
||||
const std::map<std::string, RoleKeys>& roles
|
||||
)
|
||||
{
|
||||
m_defined_roles.clear();
|
||||
|
||||
for (auto& it : roles)
|
||||
{
|
||||
std::map<std::string, Key> role_keys;
|
||||
for (auto& keyid : it.second.keyids)
|
||||
{
|
||||
try
|
||||
{
|
||||
role_keys.insert({ keyid, keys.at(keyid) });
|
||||
}
|
||||
catch (const std::out_of_range&)
|
||||
{
|
||||
LOG_ERROR << "Missing key in 'keys' is used in '" << it.first
|
||||
<< "' delegation: '" << keyid << "'";
|
||||
throw role_metadata_error();
|
||||
}
|
||||
}
|
||||
m_defined_roles.insert({ it.first, { role_keys, it.second.threshold } });
|
||||
}
|
||||
}
|
||||
|
||||
auto RootImpl::build_index_checker(
|
||||
Context&,
|
||||
const TimeRef& /*time_reference*/,
|
||||
const std::string& /*url*/,
|
||||
const fs::u8path& /*cache_path*/
|
||||
) const -> std::unique_ptr<RepoIndexChecker>
|
||||
{
|
||||
std::unique_ptr<RepoIndexChecker> ptr;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
void to_json(nlohmann::json& j, const RootImpl& r)
|
||||
{
|
||||
to_json(j, static_cast<const RoleBase&>(r));
|
||||
}
|
||||
|
||||
void from_json(const nlohmann::json& j, RootImpl& role)
|
||||
{
|
||||
auto j_signed = j.at("signed");
|
||||
try
|
||||
{
|
||||
from_json(j_signed, static_cast<RoleBase&>(role));
|
||||
|
||||
auto type = j_signed.at("_type").get<std::string>();
|
||||
if (type != role.type())
|
||||
{
|
||||
LOG_ERROR << "Wrong '_type' found in 'root' metadata, should be 'root': '" << type
|
||||
<< "'";
|
||||
throw role_metadata_error();
|
||||
}
|
||||
|
||||
role.set_spec_version(
|
||||
std::make_shared<SpecImpl>(j_signed.at("spec_version").get<std::string>())
|
||||
);
|
||||
|
||||
auto keys = j_signed.at("keys").get<std::map<std::string, Key>>();
|
||||
auto roles = j_signed.at("roles").get<std::map<std::string, RoleKeys>>();
|
||||
role.set_defined_roles(keys, roles);
|
||||
}
|
||||
catch (const nlohmann::json::exception& e)
|
||||
{
|
||||
LOG_ERROR << "Invalid 'root' metadata: " << e.what();
|
||||
throw role_metadata_error();
|
||||
}
|
||||
|
||||
role.check_expiration_format();
|
||||
role.check_defined_roles();
|
||||
}
|
||||
}
|
|
@ -49,6 +49,10 @@ set(
|
|||
src/specs/test_repo_data.cpp
|
||||
src/specs/test_version.cpp
|
||||
src/specs/test_version_spec.cpp
|
||||
# Artifacts validation
|
||||
src/validation/test_tools.cpp
|
||||
src/validation/test_update_framework_v0_6.cpp
|
||||
src/validation/test_update_framework_v1.cpp
|
||||
# The un-triaged rest
|
||||
../longpath.manifest
|
||||
src/core/test_activation.cpp
|
||||
|
@ -66,9 +70,6 @@ set(
|
|||
src/core/test_progress_bar.cpp
|
||||
src/core/test_shell_init.cpp
|
||||
src/core/test_thread_utils.cpp
|
||||
src/core/test_validate_common.cpp
|
||||
src/core/test_validate_v06.cpp
|
||||
src/core/test_validate_v1.cpp
|
||||
src/core/test_virtual_packages.cpp
|
||||
src/core/test_util.cpp
|
||||
src/core/test_env_lockfile.cpp
|
||||
|
|
|
@ -8,23 +8,22 @@
|
|||
#include <nlohmann/json.hpp>
|
||||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include "mamba/core/validate.hpp"
|
||||
#include "mamba/core/util.hpp"
|
||||
#include "mamba/util/encoding.hpp"
|
||||
#include "mamba/validation/tools.hpp"
|
||||
|
||||
using namespace mamba;
|
||||
using namespace mamba::validation;
|
||||
namespace nl = nlohmann;
|
||||
|
||||
// TODO validate API should move to std::byte
|
||||
template <std::size_t size>
|
||||
auto
|
||||
hex_str(const std::array<std::byte, size>& bytes)
|
||||
{
|
||||
auto data = reinterpret_cast<const std::byte*>(bytes.data());
|
||||
return util::bytes_to_hex_str(data, data + bytes.size());
|
||||
return util::bytes_to_hex_str(bytes.data(), bytes.data() + bytes.size());
|
||||
}
|
||||
|
||||
TEST_SUITE("Validate")
|
||||
TEST_SUITE("validation::tools")
|
||||
{
|
||||
TEST_CASE("sha256sum")
|
||||
{
|
||||
|
@ -116,7 +115,7 @@ protected:
|
|||
std::array<std::byte, MAMBA_ED25519_SIGSIZE_BYTES> signature;
|
||||
};
|
||||
|
||||
TEST_SUITE("VerifyMsg")
|
||||
TEST_SUITE("validation::VerifyMsg")
|
||||
{
|
||||
TEST_CASE_FIXTURE(VerifyMsg, "from_bytes")
|
||||
{
|
||||
|
@ -189,7 +188,7 @@ protected:
|
|||
std::string data;
|
||||
};
|
||||
|
||||
TEST_SUITE("VerifyGPGMsg")
|
||||
TEST_SUITE("validation::VerifyGPGMsg")
|
||||
{
|
||||
TEST_CASE_FIXTURE(VerifyGPGMsg, "verify_gpg_hashed_msg_from_bin")
|
||||
{
|
|
@ -11,9 +11,14 @@
|
|||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include "mamba/core/fsutil.hpp"
|
||||
#include "mamba/core/validate.hpp"
|
||||
#include "mamba/core/util.hpp"
|
||||
#include "mamba/util/encoding.hpp"
|
||||
#include "mamba/util/path_manip.hpp"
|
||||
#include "mamba/validation/errors.hpp"
|
||||
#include "mamba/validation/repo_checker.hpp"
|
||||
#include "mamba/validation/tools.hpp"
|
||||
#include "mamba/validation/update_framework_v0_6.hpp"
|
||||
#include "mamba/validation/update_framework_v1.hpp"
|
||||
|
||||
#include "mambatests.hpp"
|
||||
|
||||
|
@ -21,14 +26,14 @@ using namespace mamba;
|
|||
using namespace mamba::validation;
|
||||
namespace nl = nlohmann;
|
||||
|
||||
class RootImplT_v06
|
||||
class RootImplT_v0_6
|
||||
{
|
||||
public:
|
||||
|
||||
using role_secrets_type = std::map<std::string, std::array<std::byte, MAMBA_ED25519_KEYSIZE_BYTES>>;
|
||||
using secrets_type = std::map<std::string, role_secrets_type>;
|
||||
|
||||
RootImplT_v06()
|
||||
RootImplT_v0_6()
|
||||
{
|
||||
channel_dir = std::make_unique<mamba::TemporaryDirectory>();
|
||||
|
||||
|
@ -132,7 +137,7 @@ public:
|
|||
return signatures;
|
||||
}
|
||||
|
||||
auto upgrade_to_v1(const v06::RootImpl& root, const nl::json& patch = nl::json()) -> nl::json
|
||||
auto upgrade_to_v1(const v0_6::RootImpl& root, const nl::json& patch = nl::json()) -> nl::json
|
||||
{
|
||||
auto root_meta = root.upgraded_signable();
|
||||
if (!patch.empty())
|
||||
|
@ -183,59 +188,59 @@ protected:
|
|||
}
|
||||
};
|
||||
|
||||
TEST_SUITE("RootImplT_v06")
|
||||
TEST_SUITE("validation::v0_6::RootImpl")
|
||||
{
|
||||
TEST_CASE_FIXTURE(RootImplT_v06, "ctor_from_path")
|
||||
TEST_CASE_FIXTURE(RootImplT_v0_6, "ctor_from_path")
|
||||
{
|
||||
v06::RootImpl root(trusted_root_file_raw_key());
|
||||
v0_6::RootImpl root(trusted_root_file_raw_key());
|
||||
|
||||
CHECK_EQ(root.type(), "root");
|
||||
CHECK_EQ(root.file_ext(), "json");
|
||||
CHECK_EQ(root.spec_version(), v06::SpecImpl("0.6.0"));
|
||||
CHECK_EQ(root.spec_version(), v0_6::SpecImpl("0.6.0"));
|
||||
CHECK_EQ(root.version(), 1);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RootImplT_v06, "ctor_from_path_pgp_signed")
|
||||
TEST_CASE_FIXTURE(RootImplT_v0_6, "ctor_from_path_pgp_signed")
|
||||
{
|
||||
v06::RootImpl root(trusted_root_file_pgp());
|
||||
v0_6::RootImpl root(trusted_root_file_pgp());
|
||||
|
||||
CHECK_EQ(root.type(), "root");
|
||||
CHECK_EQ(root.file_ext(), "json");
|
||||
CHECK_EQ(root.spec_version(), v06::SpecImpl("0.6.0"));
|
||||
CHECK_EQ(root.spec_version(), v0_6::SpecImpl("0.6.0"));
|
||||
CHECK_EQ(root.version(), 1);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RootImplT_v06, "ctor_from_json")
|
||||
TEST_CASE_FIXTURE(RootImplT_v0_6, "ctor_from_json")
|
||||
{
|
||||
v06::RootImpl root(root1_json);
|
||||
v0_6::RootImpl root(root1_json);
|
||||
|
||||
CHECK_EQ(root.type(), "root");
|
||||
CHECK_EQ(root.file_ext(), "json");
|
||||
CHECK_EQ(root.spec_version(), v06::SpecImpl("0.6.0"));
|
||||
CHECK_EQ(root.spec_version(), v0_6::SpecImpl("0.6.0"));
|
||||
CHECK_EQ(root.version(), 1);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RootImplT_v06, "ctor_from_json_str")
|
||||
TEST_CASE_FIXTURE(RootImplT_v0_6, "ctor_from_json_str")
|
||||
{
|
||||
v06::RootImpl root(root1_json.dump());
|
||||
v0_6::RootImpl root(root1_json.dump());
|
||||
|
||||
CHECK_EQ(root.type(), "root");
|
||||
CHECK_EQ(root.file_ext(), "json");
|
||||
CHECK_EQ(root.spec_version(), v06::SpecImpl("0.6.0"));
|
||||
CHECK_EQ(root.spec_version(), v0_6::SpecImpl("0.6.0"));
|
||||
CHECK_EQ(root.version(), 1);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RootImplT_v06, "ctor_from_json_pgp_signed")
|
||||
TEST_CASE_FIXTURE(RootImplT_v0_6, "ctor_from_json_pgp_signed")
|
||||
{
|
||||
v06::RootImpl root(root1_pgp_json);
|
||||
v0_6::RootImpl root(root1_pgp_json);
|
||||
|
||||
CHECK_EQ(root.type(), "root");
|
||||
CHECK_EQ(root.file_ext(), "json");
|
||||
CHECK_EQ(root.spec_version(), v06::SpecImpl("0.6.0"));
|
||||
CHECK_EQ(root.spec_version(), v0_6::SpecImpl("0.6.0"));
|
||||
CHECK_EQ(root.version(), 1);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RootImplT_v06, "ctor_wrong_filename_spec_version")
|
||||
TEST_CASE_FIXTURE(RootImplT_v0_6, "ctor_wrong_filename_spec_version")
|
||||
{
|
||||
fs::u8path p = channel_dir->path() / "2.sv1.root.json";
|
||||
|
||||
|
@ -244,31 +249,31 @@ TEST_SUITE("RootImplT_v06")
|
|||
out_file.close();
|
||||
|
||||
// "2.sv1.root.json" is not compatible spec version (spec version N)
|
||||
CHECK_THROWS_AS(v06::RootImpl root(p), role_file_error);
|
||||
CHECK_THROWS_AS(v0_6::RootImpl root(p), role_file_error);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RootImplT_v06, "update_from_path")
|
||||
TEST_CASE_FIXTURE(RootImplT_v0_6, "update_from_path")
|
||||
{
|
||||
using namespace mamba;
|
||||
|
||||
auto f = trusted_root_file_raw_key();
|
||||
v06::RootImpl root(f);
|
||||
v0_6::RootImpl root(f);
|
||||
|
||||
nl::json patch = R"([
|
||||
{ "op": "replace", "path": "/signed/version", "value": 2 }
|
||||
])"_json;
|
||||
auto updated_root = root.update(create_root_update("2.root.json", patch));
|
||||
|
||||
auto testing_root = static_cast<v06::RootImpl*>(updated_root.get());
|
||||
auto testing_root = static_cast<v0_6::RootImpl*>(updated_root.get());
|
||||
CHECK_EQ(testing_root->type(), "root");
|
||||
CHECK_EQ(testing_root->file_ext(), "json");
|
||||
CHECK_EQ(testing_root->spec_version(), v06::SpecImpl("0.6.0"));
|
||||
CHECK_EQ(testing_root->spec_version(), v0_6::SpecImpl("0.6.0"));
|
||||
CHECK_EQ(testing_root->version(), 2);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RootImplT_v06, "wrong_version")
|
||||
TEST_CASE_FIXTURE(RootImplT_v0_6, "wrong_version")
|
||||
{
|
||||
v06::RootImpl root(root1_json);
|
||||
v0_6::RootImpl root(root1_json);
|
||||
|
||||
nl::json patch = R"([
|
||||
{ "op": "replace", "path": "/signed/version", "value": 3 }
|
||||
|
@ -277,9 +282,9 @@ TEST_SUITE("RootImplT_v06")
|
|||
CHECK_THROWS_AS(root.update(create_root_update("2.root.json", patch)), role_metadata_error);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RootImplT_v06, "spec_version")
|
||||
TEST_CASE_FIXTURE(RootImplT_v0_6, "spec_version")
|
||||
{
|
||||
v06::RootImpl root(root1_json);
|
||||
v0_6::RootImpl root(root1_json);
|
||||
|
||||
nl::json patch = R"([
|
||||
{ "op": "replace", "path": "/signed/version", "value": 2 },
|
||||
|
@ -287,15 +292,15 @@ TEST_SUITE("RootImplT_v06")
|
|||
])"_json;
|
||||
auto updated_root = root.update(create_root_update("2.root.json", patch));
|
||||
|
||||
auto testing_root = static_cast<v06::RootImpl*>(updated_root.get());
|
||||
CHECK_EQ(testing_root->spec_version(), v06::SpecImpl("0.6.1"));
|
||||
auto testing_root = static_cast<v0_6::RootImpl*>(updated_root.get());
|
||||
CHECK_EQ(testing_root->spec_version(), v0_6::SpecImpl("0.6.1"));
|
||||
CHECK_EQ(testing_root->version(), 2);
|
||||
CHECK_EQ(testing_root->expires(), root.expires());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RootImplT_v06, "upgraded_spec_version")
|
||||
TEST_CASE_FIXTURE(RootImplT_v0_6, "upgraded_spec_version")
|
||||
{
|
||||
v06::RootImpl root(root1_json);
|
||||
v0_6::RootImpl root(root1_json);
|
||||
|
||||
nl::json patch = R"([
|
||||
{ "op": "replace", "path": "/signed/version", "value": 2 },
|
||||
|
@ -318,14 +323,14 @@ TEST_SUITE("RootImplT_v06")
|
|||
|
||||
auto testing_root = dynamic_cast<v1::RootImpl*>(updated_root.get());
|
||||
REQUIRE_NE(testing_root, nullptr);
|
||||
CHECK_EQ(testing_root->spec_version(), v06::SpecImpl("1.0.17"));
|
||||
CHECK_EQ(testing_root->spec_version(), v0_6::SpecImpl("1.0.17"));
|
||||
CHECK_EQ(testing_root->version(), 2);
|
||||
CHECK_LT(testing_root->expires(), root.expires());
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RootImplT_v06, "equivalent_upgraded_spec_version")
|
||||
TEST_CASE_FIXTURE(RootImplT_v0_6, "equivalent_upgraded_spec_version")
|
||||
{
|
||||
v06::RootImpl root(root1_json);
|
||||
v0_6::RootImpl root(root1_json);
|
||||
|
||||
nl::json signable_patch = R"([
|
||||
{ "op": "add", "path": "/keys/dummy_value", "value": { "keytype": "ed25519", "scheme": "ed25519", "keyval": "dummy_value" } },
|
||||
|
@ -338,9 +343,9 @@ TEST_SUITE("RootImplT_v06")
|
|||
CHECK_EQ(updated_root.version(), 1);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RootImplT_v06, "wrong_spec_version")
|
||||
TEST_CASE_FIXTURE(RootImplT_v0_6, "wrong_spec_version")
|
||||
{
|
||||
v06::RootImpl root(root1_json);
|
||||
v0_6::RootImpl root(root1_json);
|
||||
|
||||
nl::json patch = R"([
|
||||
{ "op": "replace", "path": "/signed/version", "value": 2 },
|
||||
|
@ -357,23 +362,23 @@ TEST_SUITE("RootImplT_v06")
|
|||
CHECK_THROWS_AS(root.update(create_root_update("2.root.json", patch)), spec_version_error);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RootImplT_v06, "wrong_filename_role")
|
||||
TEST_CASE_FIXTURE(RootImplT_v0_6, "wrong_filename_role")
|
||||
{
|
||||
v06::RootImpl root(root1_json);
|
||||
v0_6::RootImpl root(root1_json);
|
||||
|
||||
CHECK_THROWS_AS(root.update(create_root_update("2.rooot.json")), role_file_error);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RootImplT_v06, "wrong_filename_version")
|
||||
TEST_CASE_FIXTURE(RootImplT_v0_6, "wrong_filename_version")
|
||||
{
|
||||
v06::RootImpl root(root1_json);
|
||||
v0_6::RootImpl root(root1_json);
|
||||
|
||||
CHECK_THROWS_AS(root.update(create_root_update("3.root.json")), role_file_error);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RootImplT_v06, "wrong_filename_spec_version")
|
||||
TEST_CASE_FIXTURE(RootImplT_v0_6, "wrong_filename_spec_version")
|
||||
{
|
||||
v06::RootImpl root(root1_json);
|
||||
v0_6::RootImpl root(root1_json);
|
||||
|
||||
// "2.sv1.root.json" is upgradable spec version (spec version N+1)
|
||||
nl::json signable_patch = R"([
|
||||
|
@ -386,7 +391,7 @@ TEST_SUITE("RootImplT_v06")
|
|||
auto updated_root = root.update(upgrade_to_v1(root, signable_patch));
|
||||
auto testing_root = dynamic_cast<v1::RootImpl*>(updated_root.get());
|
||||
REQUIRE_NE(testing_root, nullptr);
|
||||
CHECK_EQ(testing_root->spec_version(), v06::SpecImpl("1.0.0"));
|
||||
CHECK_EQ(testing_root->spec_version(), v0_6::SpecImpl("1.0.0"));
|
||||
|
||||
// "2.sv2.root.json" is not upgradable spec version (spec version N+1)
|
||||
nl::json patch = R"([
|
||||
|
@ -395,18 +400,18 @@ TEST_SUITE("RootImplT_v06")
|
|||
CHECK_THROWS_AS(root.update(create_root_update("2.sv2.root.json", patch)), role_file_error);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RootImplT_v06, "illformed_filename_version")
|
||||
TEST_CASE_FIXTURE(RootImplT_v0_6, "illformed_filename_version")
|
||||
{
|
||||
v06::RootImpl root(root1_json);
|
||||
v0_6::RootImpl root(root1_json);
|
||||
|
||||
CHECK_THROWS_AS(root.update(create_root_update("wrong.root.json")), role_file_error);
|
||||
CHECK_THROWS_AS(root.update(create_root_update("2..root.json")), role_file_error);
|
||||
CHECK_THROWS_AS(root.update(create_root_update("2.sv04.root.json")), role_file_error);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RootImplT_v06, "rollback_attack")
|
||||
TEST_CASE_FIXTURE(RootImplT_v0_6, "rollback_attack")
|
||||
{
|
||||
v06::RootImpl root(root1_json);
|
||||
v0_6::RootImpl root(root1_json);
|
||||
|
||||
nl::json patch = R"([
|
||||
{ "op": "replace", "path": "/signed/version", "value": 1 }
|
||||
|
@ -415,9 +420,9 @@ TEST_SUITE("RootImplT_v06")
|
|||
CHECK_THROWS_AS(root.update(create_root_update("2.root.json", patch)), rollback_error);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RootImplT_v06, "wrong_type")
|
||||
TEST_CASE_FIXTURE(RootImplT_v0_6, "wrong_type")
|
||||
{
|
||||
v06::RootImpl root(root1_json);
|
||||
v0_6::RootImpl root(root1_json);
|
||||
|
||||
nl::json patch = R"([
|
||||
{ "op": "replace", "path": "/signed/type", "value": "timestamp" },
|
||||
|
@ -427,9 +432,9 @@ TEST_SUITE("RootImplT_v06")
|
|||
CHECK_THROWS_AS(root.update(create_root_update("2.root.json", patch)), role_metadata_error);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RootImplT_v06, "missing_type")
|
||||
TEST_CASE_FIXTURE(RootImplT_v0_6, "missing_type")
|
||||
{
|
||||
v06::RootImpl root(root1_json);
|
||||
v0_6::RootImpl root(root1_json);
|
||||
|
||||
nl::json patch = R"([
|
||||
{ "op": "remove", "path": "/signed/type" },
|
||||
|
@ -439,9 +444,9 @@ TEST_SUITE("RootImplT_v06")
|
|||
CHECK_THROWS_AS(root.update(create_root_update("2.root.json", patch)), role_metadata_error);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RootImplT_v06, "missing_delegations")
|
||||
TEST_CASE_FIXTURE(RootImplT_v0_6, "missing_delegations")
|
||||
{
|
||||
v06::RootImpl root(root1_json);
|
||||
v0_6::RootImpl root(root1_json);
|
||||
|
||||
nl::json patch = R"([
|
||||
{ "op": "remove", "path": "/signed/delegations" },
|
||||
|
@ -451,9 +456,9 @@ TEST_SUITE("RootImplT_v06")
|
|||
CHECK_THROWS_AS(root.update(create_root_update("2.root.json", patch)), role_metadata_error);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RootImplT_v06, "missing_delegation")
|
||||
TEST_CASE_FIXTURE(RootImplT_v0_6, "missing_delegation")
|
||||
{
|
||||
v06::RootImpl root(root1_json);
|
||||
v0_6::RootImpl root(root1_json);
|
||||
|
||||
nl::json patch = R"([
|
||||
{ "op": "remove", "path": "/signed/delegations/root" },
|
||||
|
@ -463,9 +468,9 @@ TEST_SUITE("RootImplT_v06")
|
|||
CHECK_THROWS_AS(root.update(create_root_update("2.root.json", patch)), role_metadata_error);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RootImplT_v06, "empty_delegation_pubkeys")
|
||||
TEST_CASE_FIXTURE(RootImplT_v0_6, "empty_delegation_pubkeys")
|
||||
{
|
||||
v06::RootImpl root(root1_json);
|
||||
v0_6::RootImpl root(root1_json);
|
||||
|
||||
nl::json patch = R"([
|
||||
{ "op": "replace", "path": "/signed/delegations/root/pubkeys", "value": [] },
|
||||
|
@ -475,9 +480,9 @@ TEST_SUITE("RootImplT_v06")
|
|||
CHECK_THROWS_AS(root.update(create_root_update("2.root.json", patch)), role_metadata_error);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RootImplT_v06, "null_role_threshold")
|
||||
TEST_CASE_FIXTURE(RootImplT_v0_6, "null_role_threshold")
|
||||
{
|
||||
v06::RootImpl root(root1_json);
|
||||
v0_6::RootImpl root(root1_json);
|
||||
|
||||
nl::json patch = R"([
|
||||
{ "op": "replace", "path": "/signed/delegations/root/threshold", "value": 0 },
|
||||
|
@ -487,9 +492,9 @@ TEST_SUITE("RootImplT_v06")
|
|||
CHECK_THROWS_AS(root.update(create_root_update("2.root.json", patch)), role_metadata_error);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RootImplT_v06, "extra_roles")
|
||||
TEST_CASE_FIXTURE(RootImplT_v0_6, "extra_roles")
|
||||
{
|
||||
v06::RootImpl root(root1_json);
|
||||
v0_6::RootImpl root(root1_json);
|
||||
|
||||
nl::json patch = R"([
|
||||
{ "op": "add", "path": "/signed/delegations/some_wrong_role",
|
||||
|
@ -514,9 +519,9 @@ TEST_SUITE("RootImplT_v06")
|
|||
CHECK(mirrors_role_found);
|
||||
}
|
||||
*/
|
||||
TEST_CASE_FIXTURE(RootImplT_v06, "threshold_not_met")
|
||||
TEST_CASE_FIXTURE(RootImplT_v0_6, "threshold_not_met")
|
||||
{
|
||||
v06::RootImpl root(root1_json);
|
||||
v0_6::RootImpl root(root1_json);
|
||||
|
||||
nl::json patch = R"([
|
||||
{ "op": "replace", "path": "/signed/version", "value": 2 },
|
||||
|
@ -526,9 +531,9 @@ TEST_SUITE("RootImplT_v06")
|
|||
CHECK_THROWS_AS(root.update(create_root_update("2.root.json", patch)), role_error);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RootImplT_v06, "expires")
|
||||
TEST_CASE_FIXTURE(RootImplT_v0_6, "expires")
|
||||
{
|
||||
v06::RootImpl root(root1_json);
|
||||
v0_6::RootImpl root(root1_json);
|
||||
|
||||
// expiration is set to now+3600s in 'sign_root'
|
||||
TimeRef time_ref;
|
||||
|
@ -546,13 +551,13 @@ TEST_SUITE("RootImplT_v06")
|
|||
);
|
||||
auto updated_root = root.update(create_root_update("2.root.json", patch));
|
||||
|
||||
auto testing_root = static_cast<v06::RootImpl*>(updated_root.get());
|
||||
auto testing_root = static_cast<v0_6::RootImpl*>(updated_root.get());
|
||||
CHECK_FALSE(testing_root->expired(time_ref));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RootImplT_v06, "timestamp")
|
||||
TEST_CASE_FIXTURE(RootImplT_v0_6, "timestamp")
|
||||
{
|
||||
v06::RootImpl root(root1_json);
|
||||
v0_6::RootImpl root(root1_json);
|
||||
|
||||
nl::json patch;
|
||||
|
||||
|
@ -575,9 +580,9 @@ TEST_SUITE("RootImplT_v06")
|
|||
CHECK_THROWS_AS(root.update(create_root_update("2.root.json", patch)), role_metadata_error);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(RootImplT_v06, "possible_update_files")
|
||||
TEST_CASE_FIXTURE(RootImplT_v0_6, "possible_update_files")
|
||||
{
|
||||
v06::RootImpl root(root1_json);
|
||||
v0_6::RootImpl root(root1_json);
|
||||
|
||||
auto update_f = root.possible_update_files();
|
||||
CHECK(update_f[0].string().c_str() == doctest::Contains("2.sv1.root.json"));
|
||||
|
@ -605,14 +610,14 @@ public:
|
|||
|
||||
protected:
|
||||
|
||||
v06::SpecImpl spec;
|
||||
v0_6::SpecImpl spec;
|
||||
};
|
||||
|
||||
TEST_SUITE("SpecImplT_v06")
|
||||
TEST_SUITE("validation::v0_6::SpecImpl")
|
||||
{
|
||||
TEST_CASE_FIXTURE(SpecImplT_v06, "ctor")
|
||||
{
|
||||
v06::SpecImpl new_spec("0.6.1");
|
||||
v0_6::SpecImpl new_spec("0.6.1");
|
||||
CHECK_EQ(new_spec.version_str(), "0.6.1");
|
||||
}
|
||||
|
||||
|
@ -700,12 +705,12 @@ TEST_SUITE("SpecImplT_v06")
|
|||
}
|
||||
|
||||
|
||||
class KeyMgrT_v06 : public RootImplT_v06
|
||||
class KeyMgrT_v06 : public RootImplT_v0_6
|
||||
{
|
||||
public:
|
||||
|
||||
KeyMgrT_v06()
|
||||
: RootImplT_v06()
|
||||
: RootImplT_v0_6()
|
||||
{
|
||||
sign_key_mgr();
|
||||
}
|
||||
|
@ -780,33 +785,33 @@ protected:
|
|||
}
|
||||
};
|
||||
|
||||
TEST_SUITE("KeyMgrT_v06")
|
||||
TEST_SUITE("validation::v0_6::KeyMgr")
|
||||
{
|
||||
TEST_CASE_FIXTURE(KeyMgrT_v06, "ctor_from_json")
|
||||
{
|
||||
v06::RootImpl root(root1_json);
|
||||
v0_6::RootImpl root(root1_json);
|
||||
auto key_mgr = root.create_key_mgr(key_mgr_json);
|
||||
|
||||
CHECK_EQ(key_mgr.spec_version(), v06::SpecImpl("0.6.0"));
|
||||
CHECK_EQ(key_mgr.spec_version(), v0_6::SpecImpl("0.6.0"));
|
||||
CHECK_EQ(key_mgr.version(), 1);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(KeyMgrT_v06, "ctor_from_json_str")
|
||||
{
|
||||
v06::RootImpl root(root1_json);
|
||||
auto key_mgr = v06::KeyMgrRole(
|
||||
v0_6::RootImpl root(root1_json);
|
||||
auto key_mgr = v0_6::KeyMgrRole(
|
||||
key_mgr_json.dump(),
|
||||
root.all_keys()["key_mgr"],
|
||||
std::make_shared<v06::SpecImpl>(v06::SpecImpl())
|
||||
std::make_shared<v0_6::SpecImpl>(v0_6::SpecImpl())
|
||||
);
|
||||
|
||||
CHECK_EQ(key_mgr.spec_version(), v06::SpecImpl("0.6.0"));
|
||||
CHECK_EQ(key_mgr.spec_version(), v0_6::SpecImpl("0.6.0"));
|
||||
CHECK_EQ(key_mgr.version(), 1);
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(KeyMgrT_v06, "version")
|
||||
{
|
||||
v06::RootImpl root(root1_json);
|
||||
v0_6::RootImpl root(root1_json);
|
||||
|
||||
{
|
||||
nl::json key_mgr_patch = R"([
|
||||
|
@ -814,7 +819,7 @@ TEST_SUITE("KeyMgrT_v06")
|
|||
])"_json;
|
||||
auto key_mgr = root.create_key_mgr(patched_key_mgr_json(key_mgr_patch));
|
||||
|
||||
CHECK_EQ(key_mgr.spec_version(), v06::SpecImpl("0.6.0"));
|
||||
CHECK_EQ(key_mgr.spec_version(), v0_6::SpecImpl("0.6.0"));
|
||||
CHECK_EQ(key_mgr.version(), 2);
|
||||
}
|
||||
|
||||
|
@ -824,14 +829,14 @@ TEST_SUITE("KeyMgrT_v06")
|
|||
])"_json;
|
||||
auto key_mgr = root.create_key_mgr(patched_key_mgr_json(key_mgr_patch));
|
||||
|
||||
CHECK_EQ(key_mgr.spec_version(), v06::SpecImpl("0.6.0"));
|
||||
CHECK_EQ(key_mgr.spec_version(), v0_6::SpecImpl("0.6.0"));
|
||||
CHECK_EQ(key_mgr.version(), 20);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(KeyMgrT_v06, "spec_version")
|
||||
{ // spec version as to match exactly 'root' spec version
|
||||
v06::RootImpl root(root1_json);
|
||||
v0_6::RootImpl root(root1_json);
|
||||
|
||||
{
|
||||
nl::json key_mgr_patch = R"([
|
||||
|
@ -839,7 +844,7 @@ TEST_SUITE("KeyMgrT_v06")
|
|||
])"_json;
|
||||
auto key_mgr = root.create_key_mgr(patched_key_mgr_json(key_mgr_patch));
|
||||
|
||||
CHECK_EQ(key_mgr.spec_version(), v06::SpecImpl("0.6.0"));
|
||||
CHECK_EQ(key_mgr.spec_version(), v0_6::SpecImpl("0.6.0"));
|
||||
CHECK_EQ(key_mgr.version(), 1);
|
||||
}
|
||||
|
||||
|
@ -865,15 +870,15 @@ TEST_SUITE("KeyMgrT_v06")
|
|||
|
||||
TEST_CASE_FIXTURE(KeyMgrT_v06, "ctor_from_path")
|
||||
{
|
||||
v06::RootImpl root(root1_json);
|
||||
v0_6::RootImpl root(root1_json);
|
||||
|
||||
auto key_mgr = root.create_key_mgr(write_key_mgr_file(key_mgr_json));
|
||||
CHECK_EQ(key_mgr.spec_version(), v06::SpecImpl("0.6.0"));
|
||||
CHECK_EQ(key_mgr.spec_version(), v0_6::SpecImpl("0.6.0"));
|
||||
CHECK_EQ(key_mgr.version(), 1);
|
||||
|
||||
// TODO: enforce consistency between spec version in filename and metadata
|
||||
key_mgr = root.create_key_mgr(write_key_mgr_file(key_mgr_json, "20.sv0.6.key_mgr.json"));
|
||||
CHECK_EQ(key_mgr.spec_version(), v06::SpecImpl("0.6.0"));
|
||||
CHECK_EQ(key_mgr.spec_version(), v0_6::SpecImpl("0.6.0"));
|
||||
CHECK_EQ(key_mgr.version(), 1);
|
||||
|
||||
|
||||
|
@ -897,7 +902,7 @@ TEST_SUITE("KeyMgrT_v06")
|
|||
|
||||
TEST_CASE_FIXTURE(KeyMgrT_v06, "expires")
|
||||
{
|
||||
v06::RootImpl root(root1_json);
|
||||
v0_6::RootImpl root(root1_json);
|
||||
auto key_mgr = root.create_key_mgr(key_mgr_json);
|
||||
|
||||
// expiration is set to now+3600s in 'sign_key_mgr'
|
||||
|
@ -923,7 +928,7 @@ TEST_SUITE("KeyMgrT_v06")
|
|||
|
||||
TEST_CASE_FIXTURE(KeyMgrT_v06, "timestamp")
|
||||
{
|
||||
v06::RootImpl root(root1_json);
|
||||
v0_6::RootImpl root(root1_json);
|
||||
|
||||
nl::json patch;
|
||||
|
||||
|
@ -957,7 +962,7 @@ public:
|
|||
{
|
||||
sign_pkg_mgr();
|
||||
generate_index_checkerdata();
|
||||
root = std::make_unique<v06::RootImpl>(root1_json);
|
||||
root = std::make_unique<v0_6::RootImpl>(root1_json);
|
||||
};
|
||||
|
||||
auto sign_repodata(const nl::json& patch = nl::json()) -> nl::json
|
||||
|
@ -1035,7 +1040,7 @@ protected:
|
|||
|
||||
nl::json pkg_mgr_json, repodata_json, signed_repodata_json;
|
||||
|
||||
std::unique_ptr<v06::RootImpl> root;
|
||||
std::unique_ptr<v0_6::RootImpl> root;
|
||||
|
||||
auto sign_pkg_mgr_meta(const nl::json& meta) -> nl::json
|
||||
{
|
||||
|
@ -1103,7 +1108,7 @@ protected:
|
|||
}
|
||||
};
|
||||
|
||||
TEST_SUITE("PkgMgrT_v06")
|
||||
TEST_SUITE("validation::v0_6::PkgMgr")
|
||||
{
|
||||
TEST_CASE_FIXTURE(PkgMgrT_v06, "verify_index")
|
||||
{
|
||||
|
@ -1182,7 +1187,7 @@ protected:
|
|||
}
|
||||
};
|
||||
|
||||
TEST_SUITE("RepoCheckerT")
|
||||
TEST_SUITE("validation::v0_6::RepoChecker")
|
||||
{
|
||||
TEST_CASE_FIXTURE(RepoCheckerT, "ctor")
|
||||
{
|
|
@ -11,8 +11,11 @@
|
|||
#include <spdlog/spdlog.h>
|
||||
|
||||
#include "mamba/core/fsutil.hpp"
|
||||
#include "mamba/core/validate.hpp"
|
||||
#include "mamba/core/util.hpp"
|
||||
#include "mamba/util/encoding.hpp"
|
||||
#include "mamba/validation/errors.hpp"
|
||||
#include "mamba/validation/tools.hpp"
|
||||
#include "mamba/validation/update_framework_v1.hpp"
|
||||
|
||||
#include "mambatests.hpp"
|
||||
|
||||
|
@ -149,7 +152,7 @@ protected:
|
|||
}
|
||||
};
|
||||
|
||||
TEST_SUITE("RootImplT_v1")
|
||||
TEST_SUITE("validation::v1::RootImpl")
|
||||
{
|
||||
TEST_CASE_FIXTURE(RootImplT_v1, "ctor_from_path")
|
||||
{
|
||||
|
@ -495,7 +498,7 @@ protected:
|
|||
v1::SpecImpl spec;
|
||||
};
|
||||
|
||||
TEST_SUITE("SpecImplT_v1")
|
||||
TEST_SUITE("validation::v1::SpecImpl")
|
||||
{
|
||||
TEST_CASE_FIXTURE(SpecImplT_v1, "ctore")
|
||||
{
|
||||
|
@ -581,7 +584,7 @@ TEST_SUITE("SpecImplT_v1")
|
|||
}
|
||||
}
|
||||
|
||||
TEST_SUITE("RoleSignature")
|
||||
TEST_SUITE("validation::v1::RoleSignature")
|
||||
{
|
||||
// Test serialization/deserialization
|
||||
TEST_CASE("to_json")
|
|
@ -33,10 +33,11 @@
|
|||
#include "mamba/core/subdirdata.hpp"
|
||||
#include "mamba/core/transaction.hpp"
|
||||
#include "mamba/core/util_os.hpp"
|
||||
#include "mamba/core/validate.hpp"
|
||||
#include "mamba/core/virtual_packages.hpp"
|
||||
#include "mamba/specs/version.hpp"
|
||||
#include "mamba/util/string.hpp"
|
||||
#include "mamba/validation/tools.hpp"
|
||||
#include "mamba/validation/update_framework_v0_6.hpp"
|
||||
|
||||
#include "bindings.hpp"
|
||||
#include "flat_set_caster.hpp"
|
||||
|
@ -1150,53 +1151,53 @@ bind_submodule_impl(pybind11::module_ m)
|
|||
.def_property_readonly("expired", &validation::RoleBase::expired)
|
||||
.def("all_keys", &validation::RoleBase::all_keys);
|
||||
|
||||
py::class_<validation::v06::V06RoleBaseExtension, std::shared_ptr<validation::v06::V06RoleBaseExtension>>(
|
||||
py::class_<validation::v0_6::V06RoleBaseExtension, std::shared_ptr<validation::v0_6::V06RoleBaseExtension>>(
|
||||
m,
|
||||
"RoleBaseExtension"
|
||||
)
|
||||
.def_property_readonly("timestamp", &validation::v06::V06RoleBaseExtension::timestamp);
|
||||
.def_property_readonly("timestamp", &validation::v0_6::V06RoleBaseExtension::timestamp);
|
||||
|
||||
py::class_<validation::v06::SpecImpl, validation::SpecBase, std::shared_ptr<validation::v06::SpecImpl>>(
|
||||
py::class_<validation::v0_6::SpecImpl, validation::SpecBase, std::shared_ptr<validation::v0_6::SpecImpl>>(
|
||||
m,
|
||||
"SpecImpl"
|
||||
)
|
||||
.def(py::init<>());
|
||||
|
||||
py::class_<
|
||||
validation::v06::KeyMgrRole,
|
||||
validation::v0_6::KeyMgrRole,
|
||||
validation::RoleBase,
|
||||
validation::v06::V06RoleBaseExtension,
|
||||
std::shared_ptr<validation::v06::KeyMgrRole>>(m, "KeyMgr")
|
||||
validation::v0_6::V06RoleBaseExtension,
|
||||
std::shared_ptr<validation::v0_6::KeyMgrRole>>(m, "KeyMgr")
|
||||
.def(py::init<
|
||||
const std::string&,
|
||||
const validation::RoleFullKeys&,
|
||||
const std::shared_ptr<validation::SpecBase>>());
|
||||
|
||||
py::class_<
|
||||
validation::v06::PkgMgrRole,
|
||||
validation::v0_6::PkgMgrRole,
|
||||
validation::RoleBase,
|
||||
validation::v06::V06RoleBaseExtension,
|
||||
std::shared_ptr<validation::v06::PkgMgrRole>>(m, "PkgMgr")
|
||||
validation::v0_6::V06RoleBaseExtension,
|
||||
std::shared_ptr<validation::v0_6::PkgMgrRole>>(m, "PkgMgr")
|
||||
.def(py::init<
|
||||
const std::string&,
|
||||
const validation::RoleFullKeys&,
|
||||
const std::shared_ptr<validation::SpecBase>>());
|
||||
|
||||
py::class_<
|
||||
validation::v06::RootImpl,
|
||||
validation::v0_6::RootImpl,
|
||||
validation::RoleBase,
|
||||
validation::v06::V06RoleBaseExtension,
|
||||
std::shared_ptr<validation::v06::RootImpl>>(m, "RootImpl")
|
||||
validation::v0_6::V06RoleBaseExtension,
|
||||
std::shared_ptr<validation::v0_6::RootImpl>>(m, "RootImpl")
|
||||
.def(py::init<const std::string&>(), py::arg("json_str"))
|
||||
.def(
|
||||
"update",
|
||||
[](validation::v06::RootImpl& role, const std::string& json_str)
|
||||
[](validation::v0_6::RootImpl& role, const std::string& json_str)
|
||||
{ return role.update(nlohmann::json::parse(json_str)); },
|
||||
py::arg("json_str")
|
||||
)
|
||||
.def(
|
||||
"create_key_mgr",
|
||||
[](validation::v06::RootImpl& role, const std::string& json_str)
|
||||
[](validation::v0_6::RootImpl& role, const std::string& json_str)
|
||||
{ return role.create_key_mgr(nlohmann::json::parse(json_str)); },
|
||||
py::arg("json_str")
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue