mamba/libmamba/include/mamba/core/validate.hpp

782 lines
23 KiB
C++

// 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 "mamba/core/mamba_fs.hpp"
#include "mamba/core/util.hpp"
#include <nlohmann/json.hpp>
#include <string>
#include <vector>
#include <set>
#include <stdexcept>
namespace validate
{
using nlohmann::json;
std::string sha256sum(const std::string& path);
std::string md5sum(const std::string& path);
bool sha256(const std::string& path, const std::string& validation);
bool md5(const std::string& path, const std::string& validation);
bool file_size(const fs::path& path, std::uintmax_t validation);
const std::size_t MAMBA_SHA256_SIZE_HEX = 64;
const std::size_t MAMBA_SHA256_SIZE_BYTES = 32;
const std::size_t MAMBA_ED25519_KEYSIZE_HEX = 64;
const std::size_t MAMBA_ED25519_KEYSIZE_BYTES = 32;
const std::size_t MAMBA_ED25519_SIGSIZE_HEX = 128;
const std::size_t MAMBA_ED25519_SIGSIZE_BYTES = 64;
int generate_ed25519_keypair(unsigned char* pk, unsigned char* sk);
std::pair<std::array<unsigned char, MAMBA_ED25519_KEYSIZE_BYTES>,
std::array<unsigned char, MAMBA_ED25519_KEYSIZE_BYTES>>
generate_ed25519_keypair();
std::pair<std::string, std::string> generate_ed25519_keypair_hex();
int sign(const std::string& data, const unsigned char* sk, unsigned char* signature);
int sign(const std::string& data, const std::string& sk, std::string& signature);
std::array<unsigned char, MAMBA_ED25519_SIGSIZE_BYTES> ed25519_sig_hex_to_bytes(
const std::string& sig_hex) noexcept;
std::array<unsigned char, MAMBA_ED25519_SIGSIZE_BYTES> ed25519_sig_hex_to_bytes(
const std::string& sig_hex, int& error_code) noexcept;
std::array<unsigned char, MAMBA_ED25519_KEYSIZE_BYTES> ed25519_key_hex_to_bytes(
const std::string& key_hex) noexcept;
std::array<unsigned char, MAMBA_ED25519_KEYSIZE_BYTES> ed25519_key_hex_to_bytes(
const std::string& key_hex, int& error_code) noexcept;
int verify(const unsigned char* data,
std::size_t data_len,
const unsigned char* pk,
const unsigned char* signature);
int verify(const std::string& data, const unsigned char* pk, const unsigned char* 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 unsigned char* data,
const unsigned char* pk,
const unsigned char* signature);
int verify_gpg_hashed_msg(const std::string& data,
const unsigned char* pk,
const unsigned char* 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(const std::string& message) noexcept;
virtual ~trust_error() = default;
virtual const char* what() const noexcept 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() noexcept;
virtual ~threshold_error() = default;
};
/**
* Error raised when wrong metadata are spotted
* in a role file.
*/
class role_metadata_error : public trust_error
{
public:
role_metadata_error() noexcept;
virtual ~role_metadata_error() = default;
};
/**
* Error raised when a wrong file name is
* detected for role metadata.
*/
class role_file_error : public trust_error
{
public:
role_file_error() noexcept;
virtual ~role_file_error() = default;
};
/**
* Error raised when a possible rollback
* attack is detected.
*/
class rollback_error : public trust_error
{
public:
rollback_error() noexcept;
virtual ~rollback_error() = default;
};
/**
* Error raised when a possible freeze
* attack is detected.
*/
class freeze_error : public trust_error
{
public:
freeze_error() noexcept;
virtual ~freeze_error() = 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() noexcept;
virtual ~spec_version_error() = default;
};
/**
* Error raised when a role metadata file
* fetching process fails.
*/
class fetching_error : public trust_error
{
public:
fetching_error() noexcept;
virtual ~fetching_error() = default;
};
/**
* Error raised when signatures threshold
* is not met for a package.
*/
class package_error : public trust_error
{
public:
package_error() noexcept;
virtual ~package_error() = default;
};
/**
* Error raised when signatures threshold
* is not met for a trust role.
*/
class role_error : public trust_error
{
public:
role_error() noexcept;
virtual ~role_error() = default;
};
/**
* Error raised when an invalid package
* index is met.
*/
class index_error : public trust_error
{
public:
index_error() noexcept;
virtual ~index_error() = 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(json& j, const Key& k);
void from_json(const 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(json& j, const RoleSignature& rs);
void from_json(const 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(json& j, const RoleKeys& rk);
void from_json(const 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(json& j, const RolePubKeys& rk);
void from_json(const 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(json& j, const RoleFullKeys& r);
void from_json(const json& j, RoleFullKeys& r);
/**
* Singleton class to define a time reference.
* TUF 5.1 'Record fixed update start time'
* https://theupdateframework.github.io/specification/latest/#fix-time
*/
class TimeRef
{
public:
static TimeRef& instance();
void set(const std::time_t& time);
void set_now();
std::string timestamp();
private:
TimeRef();
~TimeRef() = default;
std::time_t m_time_ref;
};
/**
* Base class for spec implementations.
*/
class SpecBase
{
public:
virtual ~SpecBase() = default;
std::string version_str() const;
virtual std::string canonicalize(const json& j) const;
std::string compatible_prefix() const;
std::vector<std::string> upgrade_prefix() const;
bool is_compatible(const fs::path& p) const;
bool is_compatible(const json& j) const;
bool is_compatible(const std::string& version) const;
bool is_upgrade(const 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 json& j) const = 0;
protected:
SpecBase(const std::string& spec_version);
SpecBase() = delete;
std::string get_json_value(const 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;
std::set<std::string> roles() const;
std::set<RoleSignature> signatures(const json& j) const;
virtual RoleFullKeys self_keys() const = 0;
std::map<std::string, RoleFullKeys> all_keys() const;
friend void to_json(json& j, const RoleBase* r);
friend void from_json(const json& j, RoleBase* r);
protected:
json read_json_file(const fs::path& 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 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 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::path path);
std::unique_ptr<RootRole> update(json j);
std::vector<fs::path> possible_update_files();
virtual std::unique_ptr<RepoIndexChecker> build_index_checker(
const std::string& url, const fs::path& cache_path) const = 0;
protected:
RootRole(std::shared_ptr<SpecBase> spec);
private:
virtual std::unique_ptr<RootRole> create_update(const json& j) = 0;
};
/**
* Interface that performs validity checks
* on a repository packages index.
*/
class RepoIndexChecker
{
public:
virtual ~RepoIndexChecker() = default;
virtual void verify_index(const json& j) const = 0;
virtual void verify_index(const fs::path& p) const = 0;
virtual void verify_package(const json& signed_data, const 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(const std::string& base_url,
const fs::path& ref_path,
const fs::path& cache_path = "");
// Forwarding to a ``RepoIndexChecker`` implementation
void verify_index(const json& j) const;
void verify_index(const fs::path& p) const;
void verify_package(const json& signed_data, const json& signatures) const;
void generate_index_checker();
const fs::path& cache_path();
std::size_t root_version();
private:
std::string m_base_url;
std::size_t m_root_version = 0;
fs::path m_ref_path;
fs::path m_cache_path;
fs::path initial_trusted_root();
fs::path ref_root();
fs::path cached_root();
void persist_file(const fs::path& file_path);
std::unique_ptr<RepoIndexChecker> p_index_checker;
std::unique_ptr<RootRole> get_root_role();
};
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 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::path& p);
RootImpl(const json& j);
RoleFullKeys self_keys() const override;
std::unique_ptr<RepoIndexChecker> build_index_checker(
const std::string& url, const fs::path& cache_path) const override;
friend void to_json(json& j, const RootImpl& r);
friend void from_json(const json& j, RootImpl& r);
private:
RootImpl() = delete;
void load_from_json(const json& j);
std::unique_ptr<RootRole> create_update(const 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 json& j) const override;
std::string canonicalize(const 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::path& p);
RootImpl(const 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(
const std::string& url, const fs::path& cache_path) const override;
RoleFullKeys self_keys() const override;
json upgraded_signable() const;
RoleSignature upgraded_signature(const json& j,
const std::string& pk,
const unsigned char* sk) const;
KeyMgrRole create_key_mgr(const fs::path& p) const;
KeyMgrRole create_key_mgr(const json& j) const;
friend void to_json(json& j, const RootImpl& r);
friend void from_json(const json& j, RootImpl& r);
private:
RootImpl() = delete;
void load_from_json(const json& j);
std::unique_ptr<RootRole> create_update(const 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::path& p,
const RoleFullKeys& keys,
const std::shared_ptr<SpecBase> spec);
KeyMgrRole(const 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;
/**
* Return a ``RepoIndexChecker`` implementation (derived class)
* from repository base URL.
*/
std::unique_ptr<RepoIndexChecker> build_index_checker(const std::string& url,
const fs::path& cache_path) const;
friend void to_json(json& j, const KeyMgrRole& r);
friend void from_json(const json& j, KeyMgrRole& r);
private:
KeyMgrRole() = delete;
void load_from_json(const 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::path& p,
const RoleFullKeys& keys,
const std::shared_ptr<SpecBase> spec);
PkgMgrRole(const 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::path& p) const override;
void verify_index(const json& j) const override;
void verify_package(const json& signed_data, const json& signatures) const override;
friend void to_json(json& j, const PkgMgrRole& r);
friend void from_json(const json& j, PkgMgrRole& r);
private:
PkgMgrRole() = delete;
void load_from_json(const json& j);
RoleFullKeys self_keys() const override;
std::set<RoleSignature> pkg_signatures(const json& j) const;
void check_pkg_signatures(const json& signed_data, const 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