#pragma once #include "mls/crypto.h" #include "mls/key_schedule.h" #include "mls/messages.h" #include "mls/treekem.h" #include #include #include namespace mlspp { // Index into the session roster struct RosterIndex : public UInt32 { using UInt32::UInt32; }; struct CommitOpts { std::vector extra_proposals; bool inline_tree; bool force_path; LeafNodeOptions leaf_node_opts; }; struct MessageOpts { bool encrypt = false; bytes authenticated_data; size_t padding_size = 0; }; class State { public: /// /// Constructors /// // Initialize an empty group State(bytes group_id, CipherSuite suite, HPKEPrivateKey enc_priv, SignaturePrivateKey sig_priv, const LeafNode& leaf_node, ExtensionList extensions); // Initialize a group from a Welcome State(const HPKEPrivateKey& init_priv, HPKEPrivateKey leaf_priv, SignaturePrivateKey sig_priv, const KeyPackage& key_package, const Welcome& welcome, const std::optional& tree, std::map psks); // Join a group from outside // XXX(RLB) To be fully general, we would need a few more options here, e.g., // whether to include PSKs or evict our prior appearance. static std::tuple external_join( const bytes& leaf_secret, SignaturePrivateKey sig_priv, const KeyPackage& key_package, const GroupInfo& group_info, const std::optional& tree, const MessageOpts& msg_opts, std::optional remove_prior, const std::map& psks); // Propose that a new member be added a group static MLSMessage new_member_add(const bytes& group_id, epoch_t epoch, const KeyPackage& new_member, const SignaturePrivateKey& sig_priv); /// /// Message factories /// Proposal add_proposal(const KeyPackage& key_package) const; Proposal update_proposal(HPKEPrivateKey leaf_priv, const LeafNodeOptions& opts); Proposal remove_proposal(RosterIndex index) const; Proposal remove_proposal(LeafIndex removed) const; Proposal group_context_extensions_proposal(ExtensionList exts) const; Proposal pre_shared_key_proposal(const bytes& external_psk_id) const; Proposal pre_shared_key_proposal(const bytes& group_id, epoch_t epoch) const; static Proposal reinit_proposal(bytes group_id, ProtocolVersion version, CipherSuite cipher_suite, ExtensionList extensions); MLSMessage add(const KeyPackage& key_package, const MessageOpts& msg_opts); MLSMessage update(HPKEPrivateKey leaf_priv, const LeafNodeOptions& opts, const MessageOpts& msg_opts); MLSMessage remove(RosterIndex index, const MessageOpts& msg_opts); MLSMessage remove(LeafIndex removed, const MessageOpts& msg_opts); MLSMessage group_context_extensions(ExtensionList exts, const MessageOpts& msg_opts); MLSMessage pre_shared_key(const bytes& external_psk_id, const MessageOpts& msg_opts); MLSMessage pre_shared_key(const bytes& group_id, epoch_t epoch, const MessageOpts& msg_opts); MLSMessage reinit(bytes group_id, ProtocolVersion version, CipherSuite cipher_suite, ExtensionList extensions, const MessageOpts& msg_opts); std::tuple commit( const bytes& leaf_secret, const std::optional& opts, const MessageOpts& msg_opts); /// /// Generic handshake message handlers /// std::optional handle(const MLSMessage& msg); std::optional handle(const MLSMessage& msg, std::optional cached_state); std::optional handle(const ValidatedContent& content_auth); std::optional handle(const ValidatedContent& content_auth, std::optional cached_state); /// /// PSK management /// void add_resumption_psk(const bytes& group_id, epoch_t epoch, bytes secret); void remove_resumption_psk(const bytes& group_id, epoch_t epoch); void add_external_psk(const bytes& id, const bytes& secret); void remove_external_psk(const bytes& id); /// /// Accessors /// const bytes& group_id() const { return _group_id; } epoch_t epoch() const { return _epoch; } LeafIndex index() const { return _index; } CipherSuite cipher_suite() const { return _suite; } const ExtensionList& extensions() const { return _extensions; } const TreeKEMPublicKey& tree() const { return _tree; } const bytes& resumption_psk() const { return _key_schedule.resumption_psk; } bytes do_export(const std::string& label, const bytes& context, size_t size) const; GroupInfo group_info(bool inline_tree) const; // Ordered list of credentials from non-blank leaves std::vector roster() const; bytes epoch_authenticator() const; /// /// Unwrap messages so that applications can inspect them /// ValidatedContent unwrap(const MLSMessage& msg); /// /// Application encryption and decryption /// MLSMessage protect(const bytes& authenticated_data, const bytes& pt, size_t padding_size); std::tuple unprotect(const MLSMessage& ct); // Assemble a group context for this state GroupContext group_context() const; // Subgroup branching std::tuple create_branch( bytes group_id, HPKEPrivateKey enc_priv, SignaturePrivateKey sig_priv, const LeafNode& leaf_node, ExtensionList extensions, const std::vector& key_packages, const bytes& leaf_secret, const CommitOpts& commit_opts) const; State handle_branch(const HPKEPrivateKey& init_priv, HPKEPrivateKey enc_priv, SignaturePrivateKey sig_priv, const KeyPackage& key_package, const Welcome& welcome, const std::optional& tree) const; // Reinitialization struct Tombstone { std::tuple create_welcome( HPKEPrivateKey enc_priv, SignaturePrivateKey sig_priv, const LeafNode& leaf_node, const std::vector& key_packages, const bytes& leaf_secret, const CommitOpts& commit_opts) const; State handle_welcome(const HPKEPrivateKey& init_priv, HPKEPrivateKey enc_priv, SignaturePrivateKey sig_priv, const KeyPackage& key_package, const Welcome& welcome, const std::optional& tree) const; TLS_SERIALIZABLE(prior_group_id, prior_epoch, resumption_psk, reinit); const bytes epoch_authenticator; const ReInit reinit; private: Tombstone(const State& state_in, ReInit reinit_in); bytes prior_group_id; epoch_t prior_epoch; bytes resumption_psk; friend class State; }; std::tuple reinit_commit( const bytes& leaf_secret, const std::optional& opts, const MessageOpts& msg_opts); Tombstone handle_reinit_commit(const MLSMessage& commit); protected: // Shared confirmed state // XXX(rlb@ipv.sx): Can these be made const? CipherSuite _suite; bytes _group_id; epoch_t _epoch; TreeKEMPublicKey _tree; TreeKEMPrivateKey _tree_priv; TranscriptHash _transcript_hash; ExtensionList _extensions; // Shared secret state KeyScheduleEpoch _key_schedule; GroupKeySource _keys; // Per-participant state LeafIndex _index; SignaturePrivateKey _identity_priv; // Storage for PSKs std::map _external_psks; using EpochRef = std::tuple; std::map _resumption_psks; // Cache of Proposals and update secrets struct CachedProposal { ProposalRef ref; Proposal proposal; std::optional sender; }; std::list _pending_proposals; struct CachedUpdate { HPKEPrivateKey update_priv; Update proposal; }; std::optional _cached_update; // Assemble a preliminary, unjoined group state State(SignaturePrivateKey sig_priv, const GroupInfo& group_info, const std::optional& tree); // Assemble a group from a Welcome, allowing for resumption PSKs State(const HPKEPrivateKey& init_priv, HPKEPrivateKey leaf_priv, SignaturePrivateKey sig_priv, const KeyPackage& key_package, const Welcome& welcome, const std::optional& tree, std::map external_psks, std::map resumption_psks); // Import a tree from an externally-provided tree or an extension TreeKEMPublicKey import_tree(const bytes& tree_hash, const std::optional& external, const ExtensionList& extensions); bool validate_tree() const; // Form a commit, covering all the cases with slightly different validation // rules: // * Normal // * External // * Branch // * Reinit struct NormalCommitParams {}; struct ExternalCommitParams { KeyPackage joiner_key_package; bytes force_init_secret; }; struct RestartCommitParams { ResumptionPSKUsage allowed_usage; }; struct ReInitCommitParams {}; using CommitParams = var::variant; std::tuple commit( const bytes& leaf_secret, const std::optional& opts, const MessageOpts& msg_opts, CommitParams params); std::optional handle( const MLSMessage& msg, std::optional cached_state, const std::optional& expected_params); std::optional handle( const ValidatedContent& val_content, std::optional cached_state, const std::optional& expected_params); // Create an MLSMessage encapsulating some content template AuthenticatedContent sign(const Sender& sender, Inner&& content, const bytes& authenticated_data, bool encrypt) const; MLSMessage protect(AuthenticatedContent&& content_auth, size_t padding_size); template MLSMessage protect_full(Inner&& content, const MessageOpts& msg_opts); // Apply the changes requested by various messages LeafIndex apply(const Add& add); void apply(LeafIndex target, const Update& update); void apply(LeafIndex target, const Update& update, const HPKEPrivateKey& leaf_priv); LeafIndex apply(const Remove& remove); void apply(const GroupContextExtensions& gce); std::vector apply(const std::vector& proposals, Proposal::Type required_type); std::tuple, std::vector> apply( const std::vector& proposals); // Verify that a specific key package or all members support a given set of // extensions bool extensions_supported(const ExtensionList& exts) const; // Extract proposals and PSKs from cache void cache_proposal(AuthenticatedContent content_auth); std::optional resolve( const ProposalOrRef& id, std::optional sender_index) const; std::vector must_resolve( const std::vector& ids, std::optional sender_index) const; std::vector resolve( const std::vector& psks) const; // Check properties of proposals bool valid(const LeafNode& leaf_node, LeafNodeSource required_source, std::optional index) const; bool valid(const KeyPackage& key_package) const; bool valid(const Add& add) const; bool valid(LeafIndex sender, const Update& update) const; bool valid(const Remove& remove) const; bool valid(const PreSharedKey& psk) const; static bool valid(const ReInit& reinit); bool valid(const ExternalInit& external_init) const; bool valid(const GroupContextExtensions& gce) const; bool valid(std::optional sender, const Proposal& proposal) const; bool valid(const std::vector& proposals, LeafIndex commit_sender, const CommitParams& params) const; bool valid_normal(const std::vector& proposals, LeafIndex commit_sender) const; bool valid_external(const std::vector& proposals) const; static bool valid_reinit(const std::vector& proposals); static bool valid_restart(const std::vector& proposals, ResumptionPSKUsage allowed_usage); static bool valid_external_proposal_type(const Proposal::Type proposal_type); CommitParams infer_commit_type( const std::optional& sender, const std::vector& proposals, const std::optional& expected_params) const; static bool path_required(const std::vector& proposals); // Compare the **shared** attributes of the states friend bool operator==(const State& lhs, const State& rhs); friend bool operator!=(const State& lhs, const State& rhs); // Derive and set the secrets for an epoch, given some new entropy void update_epoch_secrets(const bytes& commit_secret, const std::vector& psks, const std::optional& force_init_secret); // Signature verification over a handshake message bool verify_internal(const AuthenticatedContent& content_auth) const; bool verify_external(const AuthenticatedContent& content_auth) const; bool verify_new_member_proposal( const AuthenticatedContent& content_auth) const; bool verify_new_member_commit(const AuthenticatedContent& content_auth) const; bool verify(const AuthenticatedContent& content_auth) const; // Convert a Roster entry into LeafIndex LeafIndex leaf_for_roster_entry(RosterIndex index) const; // Create a draft successor state State successor() const; }; } // namespace mlspp