mirror of
https://github.com/lux-tel/libsession-util.git
synced 2026-10-30 20:25:11 +00:00
723 lines
35 KiB
C++
723 lines
35 KiB
C++
#pragma once
|
|
|
|
#include <chrono>
|
|
#include <unordered_set>
|
|
|
|
#include "../../config.hpp"
|
|
#include "../base.hpp"
|
|
#include "../namespaces.hpp"
|
|
#include "members.hpp"
|
|
|
|
namespace session::config::groups {
|
|
|
|
class Members;
|
|
class Info;
|
|
|
|
using namespace std::literals;
|
|
|
|
/// This "config" isn't exactly a regular config type that inherits from ConfigBase; in particular:
|
|
/// - it doesn't encrypt the message (but merely contains encrypted elements within it)
|
|
/// - it doesn't merge
|
|
/// - it does have a concept analogous to the message seqno
|
|
/// - conflict resolution involves regenerating and distributing new keys; nothing gets merged.
|
|
/// - it cares strongly about when new configs were pushed (configs expire after having been
|
|
/// replaced for a certain amount of time, not by being updated).
|
|
/// - its internal state isn't fully serialized when pushing updates
|
|
/// - messages don't contain the outer layer of config messages (where config metadata, references
|
|
/// to other objects, etc.) that ConfigBase-derived type hold.
|
|
/// - it isn't compressed (since most of the data fields are encrypted or random, compression
|
|
/// reduction would be minimal).
|
|
///
|
|
/// Fields used for generating key messages (in ascii order):
|
|
/// # -- 24-byte nonce used for all the encrypted values in this message; required.
|
|
///
|
|
/// For non-supplemental messages:
|
|
///
|
|
/// G -- monotonically incrementing counter identifying key generation changes
|
|
/// K -- encrypted copy of the key for admins (omitted for `+` incremental key messages)
|
|
/// k -- packed bytes of encrypted keys for non-admin members; this is a single byte string in which
|
|
/// each 48 bytes is a separate encrypted value.
|
|
///
|
|
/// For supplemental messages:
|
|
/// + -- encrypted supplemental key info list; this is a list of encrypted values, encrypted for
|
|
/// each member to whom keys are being disclosed. The *decrypted* value of these entries are
|
|
/// the same value (encrypted separately for each member) which is a bt-encoded list of dicts
|
|
/// where each dict contains keys:
|
|
/// - g -- the key generation
|
|
/// - k -- the key itself (32 bytes).
|
|
/// - t -- the storage timestamp of the key (so that recipients know when keys expire)
|
|
/// G -- the maximum generation of the keys included in this message; this is used to track when
|
|
/// this message can be allowed to expire.
|
|
///
|
|
/// And finally, for both types:
|
|
///
|
|
/// ~ -- signature of the message signed by the group's master keypair, signing the message value up
|
|
/// to but not including the ~ keypair. The signature must be the last key in the dict (thus
|
|
/// `~` since it is the largest 7-bit ascii character value). Note that this signature
|
|
/// mechanism works exactly the same as the signature on regular config messages.
|
|
///
|
|
/// Fields used for dumping the config (in ascii order):
|
|
/// A -- active config messages list. A list of lists, where each list has:
|
|
/// - as first argument the generation number
|
|
/// - the rest are the hashes valid for that generation number
|
|
/// L -- a list of dict representing all the keys. Each dict has:
|
|
/// - k -- same as "For supplemental messages"
|
|
/// - g -- same as "For supplemental messages"
|
|
/// - t -- same as "For supplemental messages"
|
|
/// P -- pending key as a dict (if present).
|
|
/// - c -- the pending config message to push
|
|
/// - g -- same as "For supplemental messages"
|
|
/// - k -- same as "For supplemental messages"
|
|
///
|
|
/// Some extra details:
|
|
///
|
|
/// - each copy of the encryption key uses xchacha20_poly1305 using the `#` nonce
|
|
/// - the `k` members list gets padded with junk entries up to the next multiple of 75 (for
|
|
/// non-supplemental messages).
|
|
/// - the decryption key for the admin version of the key is H(admin_seed,
|
|
/// key="SessionGroupKeyAdmin")
|
|
/// - the encryption key for a member is H(a'B || A' || B, key="SessionGroupKeyMember") where a'/A'
|
|
/// is the group Ed25519 master key converted to X25519, and b/B is the member's X25519 keypair
|
|
/// (i.e. B is the non-05-prefixed session_id).
|
|
/// - the decryption key is calculated by the member using `bA' || A' || B`
|
|
/// - A new key and nonce is created from a 56-byte H(M0 || M1 || ... || Mn || g || S,
|
|
/// key="SessionGroupKeyGen"), where S = H(group_seed, key="SessionGroupKeySeed").
|
|
|
|
class Keys : public ConfigSig {
|
|
|
|
Ed25519Secret user_ed25519_sk;
|
|
|
|
struct key_info {
|
|
std::array<unsigned char, 32> key;
|
|
std::chrono::system_clock::time_point timestamp; // millisecond precision
|
|
int64_t generation;
|
|
|
|
auto cmpval() const { return std::tie(generation, timestamp, key); }
|
|
bool operator<(const key_info& b) const { return cmpval() < b.cmpval(); }
|
|
bool operator>(const key_info& b) const { return cmpval() > b.cmpval(); }
|
|
bool operator<=(const key_info& b) const { return cmpval() <= b.cmpval(); }
|
|
bool operator>=(const key_info& b) const { return cmpval() >= b.cmpval(); }
|
|
bool operator==(const key_info& b) const { return cmpval() == b.cmpval(); }
|
|
bool operator!=(const key_info& b) const { return cmpval() != b.cmpval(); }
|
|
};
|
|
|
|
/// Vector of keys that is kept sorted by generation/timestamp/key. This gets pruned as keys
|
|
/// have been superceded by another key for a sufficient amount of time (see KEY_EXPIRY).
|
|
sodium_vector<key_info> keys_;
|
|
|
|
/// Hashes of messages we have successfully parsed; used for deciding what needs to be renewed.
|
|
std::map<int64_t, std::unordered_set<std::string>> active_msgs_;
|
|
|
|
sodium_cleared<std::array<unsigned char, 32>> pending_key_;
|
|
sodium_vector<unsigned char> pending_key_config_;
|
|
int64_t pending_gen_ = -1;
|
|
|
|
bool needs_dump_ = false;
|
|
|
|
ConfigMessage::verify_callable verifier_;
|
|
ConfigMessage::sign_callable signer_;
|
|
|
|
void set_verifier(ConfigMessage::verify_callable v) override { verifier_ = std::move(v); }
|
|
void set_signer(ConfigMessage::sign_callable s) override { signer_ = std::move(s); }
|
|
|
|
std::vector<unsigned char> sign(std::span<const unsigned char> data) const;
|
|
|
|
// Checks for and drops expired keys.
|
|
void remove_expired();
|
|
|
|
// Loads existing state from a previous dump of keys data
|
|
void load_dump(std::span<const unsigned char> dump);
|
|
|
|
// Inserts a key into the correct place in `keys_`.
|
|
void insert_key(std::string_view message_hash, key_info&& key);
|
|
|
|
// Returned the blinding factor for a given session X25519 pubkey. This depends on the group's
|
|
// seed and thus is only obtainable by an admin account.
|
|
std::array<unsigned char, 32> subaccount_blind_factor(
|
|
const std::array<unsigned char, 32>& session_xpk) const;
|
|
|
|
public:
|
|
/// The multiple of members keys we include in the message; we add junk entries to the key list
|
|
/// to reach a multiple of this. 45 is chosen because it's a decently large human-round number
|
|
/// that should still fit within 2.5kiB size limitation for push notifications.
|
|
static constexpr int MESSAGE_KEY_MULTIPLE = 45;
|
|
|
|
// 45 because:
|
|
// 2 // for the 'de' delimiters of the outer dict
|
|
// + 3 + 2 + 12 // for the `1:g` and `iNNNNNNNNNNe` generation keypair
|
|
// + 3 + 3 + 24 // for the `1:n`, `24:`, and 24 byte nonce
|
|
// + 3 + 3 + 48 // for the `1:K`, `48:`, and 48 byte ciphertexted key
|
|
// + 3 + 6 // for the `1:k` and `NNNNN:` key and prefix of the keys pair
|
|
// + N * 48 // for the packed encryption keys
|
|
// + 3 + 3 + 64; // for the `1:~` and `64:` and 64 byte signature
|
|
// = 177 + 48N
|
|
//
|
|
// and N=45 puts us a little bit under 2.5kiB (which is the limit we have on the push
|
|
// notification server because after base64 encoding it gets close to the 4kiB limit for push
|
|
// notification content).
|
|
|
|
/// A key expires when it has been surpassed by another key for at least this amount of time.
|
|
/// We default this to double the 30 days that we strictly need to avoid race conditions with
|
|
/// 30-day old config messages that might need the key for a client that is only very rarely
|
|
/// online.
|
|
static constexpr auto KEY_EXPIRY = 2 * 30 * 24h;
|
|
|
|
/// The maximum uncompressed message size we allow in message decryption/encryption.
|
|
static constexpr size_t MAX_PLAINTEXT_MESSAGE_SIZE = 1'000'000;
|
|
|
|
// No default constructor
|
|
Keys() = delete;
|
|
|
|
/// API: groups/Keys::Keys
|
|
///
|
|
/// Constructs a group members config object from existing data (stored from `dump()`) and a
|
|
/// list of encryption keys for encrypting new and decrypting existing messages.
|
|
///
|
|
/// To construct a blank info object (i.e. with no pre-existing dumped data to load) pass
|
|
/// `std::nullopt` as the last argument.
|
|
///
|
|
/// When no dump is provided the initial Keys object will be created with no keys loaded at all,
|
|
/// these will be loaded later into this and the info/members objects when loading keys via
|
|
/// received config messages. If this is a brand new group then rekey() MUST be called,
|
|
/// otherwise the group will be in an invalid state.
|
|
///
|
|
/// Inputs:
|
|
/// - `user_ed25519_secretkey` is the ed25519 secret key backing the current user's session ID,
|
|
/// and is used to decrypt incoming keys. It is required.
|
|
/// - `group_ed25519_pubkey` is the public key of the group, used to verify message signatures
|
|
/// on key updates. Required. Should not include the `03` prefix.
|
|
/// - `group_ed25519_secretkey` is the secret key of the group, used to encrypt, decrypt, and
|
|
/// sign config messages. This is only possessed by the group admin(s), and must be provided
|
|
/// in order to make and push config changes.
|
|
/// - `dumped` -- either `std::nullopt` to construct a new, empty object; or binary state data
|
|
/// that was previously dumped from an instance of this class by calling `dump()`.
|
|
/// - `info` and `members` -- will be loaded with the group keys, if present in the dump.
|
|
Keys(std::span<const unsigned char> user_ed25519_secretkey,
|
|
std::span<const unsigned char> group_ed25519_pubkey,
|
|
std::optional<std::span<const unsigned char>> group_ed25519_secretkey,
|
|
std::optional<std::span<const unsigned char>> dumped,
|
|
Info& info,
|
|
Members& members);
|
|
|
|
/// API: groups/Keys::storage_namespace
|
|
///
|
|
/// Returns the Keys namespace. Is constant, will always return Namespace::GroupKeys
|
|
///
|
|
/// Inputs: None
|
|
///
|
|
/// Outputs:
|
|
/// - `Namespace` - Will return Namespace::GroupKeys
|
|
Namespace storage_namespace() const { return Namespace::GroupKeys; }
|
|
|
|
/// API: groups/Keys::encryption_domain
|
|
///
|
|
/// Returns the encryption domain used when encrypting messages of this type.
|
|
///
|
|
/// Inputs: None
|
|
///
|
|
/// Outputs:
|
|
/// - `const char*` - Will return "groups::Keys"
|
|
const char* encryption_domain() const { return "groups::Keys"; }
|
|
|
|
/// API: groups/Keys::group_keys
|
|
///
|
|
/// Returns all the unexpired decryption keys that we know about. Keys are returned ordered
|
|
/// from most-recent to least-recent (and so the first one is meant to be used as the encryption
|
|
/// key), including a pending key if this object is in the process of pushing a new keys
|
|
/// message.
|
|
///
|
|
/// This isn't typically directly needed: this object manages the key lists in the `info` and
|
|
/// `members` objects itself.
|
|
///
|
|
/// Inputs: none.
|
|
///
|
|
/// Outputs:
|
|
/// - `std::vector<std::span<const unsigned char>>` - vector of encryption keys.
|
|
std::vector<std::span<const unsigned char>> group_keys() const;
|
|
|
|
/// API: groups/Keys::size
|
|
///
|
|
/// Returns the number of distinct decryption keys that we know about. Mainly for
|
|
/// debugging/information purposes.
|
|
///
|
|
/// Inputs: none
|
|
///
|
|
/// Outputs:
|
|
/// - `size_t` of the number of keys we know about
|
|
size_t size() const;
|
|
|
|
/// API: groups/Keys::encryption_key
|
|
///
|
|
/// Accesses the current encryption key: that is, the most current group decryption key. Throws
|
|
/// if there are no encryption keys at all. (This is essentially the same as `group_keys()[0]`,
|
|
/// except for the throwing and avoiding needing to constructor a vector).
|
|
///
|
|
/// You normally don't need to call this; the key is used automatically by methods such as
|
|
/// encrypt_message() that need it.
|
|
///
|
|
/// Inputs: none.
|
|
///
|
|
/// Outputs:
|
|
/// - `std::span<const unsigned char>` of the most current group encryption key.
|
|
std::span<const unsigned char> group_enc_key() const;
|
|
|
|
/// API: groups/Keys::is_admin
|
|
///
|
|
/// True if we have admin permissions (i.e. we know the group's master secret key).
|
|
///
|
|
/// Inputs: none.
|
|
///
|
|
/// Outputs:
|
|
/// - `true` if this object knows the group's master key
|
|
bool admin() const { return _sign_sk && _sign_pk; }
|
|
|
|
/// API: groups/Keys::load_admin_key
|
|
///
|
|
/// Loads the group secret key into the Keys object (as well as passing it along to the Info and
|
|
/// Members objects).
|
|
///
|
|
/// The primary use of this is when accepting a promotion-to-admin: the Keys object would be
|
|
/// constructed as a regular member (without the admin key) then this method "upgrades" the
|
|
/// object with the group signing key.
|
|
///
|
|
/// This will do nothing if the secret key is already known; it will throw if
|
|
/// the given secret key does not yield the group's public key. The given key can be either the
|
|
/// 32 byte seed, or the libsodium 64 byte "secret key" (which is just the seed and cached
|
|
/// public key stuck together).
|
|
///
|
|
/// Inputs:
|
|
/// - `secret` -- the group's 64-byte secret key or 32-byte seed
|
|
/// - `info` and `members` -- will be loaded with the group keys if the key is loaded
|
|
/// successfully.
|
|
///
|
|
/// Outputs: nothing. After a successful call, `admin()` will return true. Throws if the given
|
|
/// secret key does not match the group's pubkey.
|
|
void load_admin_key(std::span<const unsigned char> secret, Info& info, Members& members);
|
|
|
|
/// API: groups/Keys::rekey
|
|
///
|
|
/// Generate a new encryption key for the group and returns an encrypted key message to be
|
|
/// pushed to the swarm containing the key, encrypted for the members of the given
|
|
/// config::groups::Members object. This can only be done by an admin account (i.e. we must
|
|
/// have the group's private key).
|
|
///
|
|
/// This method is intended to be called in these situations:
|
|
/// - potentially after loading new keys config messages (see `needs_rekey()`)
|
|
/// - when removing a member to switch to a new encryption key for the group that excludes that
|
|
/// member.
|
|
/// - when adding a member *and* switching to a new encryption key (without making the old key
|
|
/// available to the member) so that the new member cannot decipher pre-existing configs and
|
|
/// messages.
|
|
///
|
|
/// This method is closely coupled to the group's Info and Members configs: it updates their
|
|
/// encryption keys and sets them as dirty, requiring a re-push to re-encrypt each of them.
|
|
/// Typically a rekey is performed as follows:
|
|
///
|
|
/// - `rekey()` is called, returning the new keys config.
|
|
/// - `info.push()` is called to get the new info config (re-encrypted with the new key)
|
|
/// - `members.push()` is called to get the new members config (using the new key)
|
|
/// - all three new configs are pushed (ideally all at once, in a single batch request).
|
|
///
|
|
/// Inputs:
|
|
/// - `Info` - the group's Info; it will be dirtied after the rekey and will require a push.
|
|
/// - `Members` - the current Members config for the group. When removing one or more members
|
|
/// this should be the list of members with the specific members already removed. The members
|
|
/// config will be dirtied after the rekey and will require a push.
|
|
///
|
|
/// Outputs:
|
|
/// - `std::span<const unsigned char>` containing the data that needs to be pushed to the config
|
|
/// keys namespace
|
|
/// for the group. (This can be re-obtained from `pending_config()` if needed until it has
|
|
/// been confirmed or superceded). This data must be consumed or copied from the returned
|
|
/// string_view immediately: it will not be valid past other calls on the Keys config object.
|
|
std::span<const unsigned char> rekey(Info& info, Members& members);
|
|
|
|
/// API: groups/Keys::key_supplement
|
|
///
|
|
/// Generates a supplemental key message for one or more session IDs. This is used to
|
|
/// distribute existing active keys to a new member so that that member can access existing
|
|
/// keys, configs, and messages. Only admins can call this.
|
|
///
|
|
/// The recommended order of operations for adding such a member is:
|
|
/// - add the member to Members
|
|
/// - generate the key supplement
|
|
/// - push new members & key supplement (ideally in a batch)
|
|
/// - send invite details, auth signature, etc. to the new user
|
|
///
|
|
/// To add a member *without* giving them access you would use rekey() instead of this method.
|
|
///
|
|
/// Inputs:
|
|
/// - `sid` or `sids` -- session ID(s) of the members to generate a supplemental key for (there
|
|
/// are two versions of this function, one taking a single ID and one taking a vector).
|
|
/// Session IDs are specified in hex.
|
|
///
|
|
/// Outputs:
|
|
/// - `std::vector<unsigned char>` containing the message that should be pushed to the swarm
|
|
/// containing encrypted
|
|
/// keys for the given user(s).
|
|
std::vector<unsigned char> key_supplement(const std::vector<std::string>& sids) const;
|
|
std::vector<unsigned char> key_supplement(std::string sid) const {
|
|
return key_supplement(std::vector{{std::move(sid)}});
|
|
}
|
|
|
|
/// API: groups/current_generation
|
|
///
|
|
/// Returns the current generation number for the latest keys message.
|
|
///
|
|
/// Inputs: none.
|
|
///
|
|
/// Outputs:
|
|
/// - `int` -- latest keys generation number.
|
|
int current_generation() const { return keys_.empty() ? 0 : keys_.back().generation; }
|
|
|
|
/// API: groups/Keys::swarm_make_subaccount
|
|
///
|
|
/// Constructs a swarm subaccount signing value that a member can use to access messages in the
|
|
/// swarm. Requires group admins keys.
|
|
///
|
|
/// Inputs:
|
|
/// - `session_id` -- the session ID of the member (in hex)
|
|
/// - `write` -- if true (which is the default if omitted) then the member shall be allowed to
|
|
/// submit messages into the group account of the swarm and extend (but not shorten) the
|
|
/// expiry of messages in the group account. If false then the user can only retrieve
|
|
/// messages.
|
|
/// - `del` -- if true (default is false) then the user shall be allowed to delete messages
|
|
/// from the swarm. This permission can be used to appoint a sort of "moderator" who can
|
|
/// delete messages without having the full admin group keys.
|
|
///
|
|
/// Outputs:
|
|
/// - `std::vector<unsigned char>` -- contains a subaccount swarm signing value; this can be
|
|
/// passed (by the user)
|
|
/// into `swarm_subaccount_sign` to sign a value suitable for swarm authentication.
|
|
/// (Internally this packs the flags, blinding factor, and group admin signature together and
|
|
/// will be 4 + 32 + 64 = 100 bytes long).
|
|
///
|
|
/// This value must be provided to the user so that they can authentication. The user should
|
|
/// call `swarm_verify_subaccount` to verify that the signing value was indeed signed by a
|
|
/// group admin before using/storing it.
|
|
///
|
|
/// The signing value produced will be the same (for a given `session_id`/`write`/`del`
|
|
/// values) when constructed by any admin of the group.
|
|
std::vector<unsigned char> swarm_make_subaccount(
|
|
std::string_view session_id, bool write = true, bool del = false) const;
|
|
|
|
/// API: groups/Keys::swarm_verify_subaccount
|
|
///
|
|
/// Verifies that a received subaccount signing value (allegedly produced by
|
|
/// swarm_make_subaccount) is a valid subaccount signing value for the given group pubkey,
|
|
/// including a proper signature by an admin of the group. The signing value must have read
|
|
/// permission, but parameters can be given to also require write or delete permissions. A
|
|
/// subaccount signing value should always be checked for validity using this before creating a
|
|
/// group that would depend on it.
|
|
///
|
|
/// There are two versions of this function: a static one callable without having a Keys
|
|
/// instance that takes the group id and user's session Ed25519 secret key as arguments; and a
|
|
/// member function that omits these first two arguments (using the ones from the Keys
|
|
/// instance).
|
|
///
|
|
/// Inputs:
|
|
/// - `groupid` -- the group id/pubkey, in hex, beginning with "03".
|
|
/// - `session_ed25519_secretkey` -- the user's Session ID secret key.
|
|
/// - `signing_value` -- the subaccount signing value to validate
|
|
/// - `write` -- if true, require that the signing_value has write permission (i.e. that the
|
|
/// user will be allowed to post messages).
|
|
/// - `del` -- if true, required that the signing_value has delete permissions (i.e. that the
|
|
/// user will be allowed to remove storage messages from the group's swarm). Note that this
|
|
/// permission is about forcible swarm message deletion, and has no effect on an ability to
|
|
/// submit a deletion meta-message to the group (which only requires writing a message).
|
|
///
|
|
/// Outputs:
|
|
/// - `true` if `signing_value` is a valid subaccount signing value for `groupid` with read (and
|
|
/// possible write and/or del permissions, if requested). `false` if the signing value does
|
|
/// not validate or does not meet the requirements.
|
|
static bool swarm_verify_subaccount(
|
|
std::string group_id,
|
|
std::span<const unsigned char> session_ed25519_secretkey,
|
|
std::span<const unsigned char> signing_value,
|
|
bool write = false,
|
|
bool del = false);
|
|
bool swarm_verify_subaccount(
|
|
std::span<const unsigned char> signing_value,
|
|
bool write = false,
|
|
bool del = false) const;
|
|
|
|
/// API: groups/Keys::swarm_auth
|
|
///
|
|
/// This struct containing the storage server authentication values for subaccount
|
|
/// authentication. The three strings in this struct may be either raw bytes, or base64
|
|
/// encoded, depending on the `binary` parameter passed to `swarm_subaccount_sign`.
|
|
///
|
|
/// `.subaccount` is the value to be passed as the "subaccount" authentication parameter. (It
|
|
/// consists of permission flags followed by a blinded public key.)
|
|
///
|
|
/// `.subaccount_sig` is the value to be passed as the "subaccount_sig" authentication
|
|
/// parameter. (It consists of an admin-produced signature of the subaccount, providing
|
|
/// permission for that token to be used for authentication).
|
|
///
|
|
/// `.signature` is the value to be passed as the "signature" authentication parameter. (It is
|
|
/// an Ed25519 signature that validates using the blinded public key inside `subaccount`).
|
|
///
|
|
/// Inputs: none.
|
|
struct swarm_auth {
|
|
std::string subaccount;
|
|
std::string subaccount_sig;
|
|
std::string signature;
|
|
};
|
|
|
|
/// API: groups/Keys::swarm_subaccount_sign
|
|
///
|
|
/// This helper function generates the required signature for swarm subaccount authentication,
|
|
/// given the user's keys and swarm auth keys (as provided by an admin, produced via
|
|
/// `swarm_make_subaccount`).
|
|
///
|
|
/// Storage server subaccount authentication requires passing the three values in the returned
|
|
/// struct in the storage server request. (See Keys::swarm_auth for details).
|
|
///
|
|
/// Inputs:
|
|
/// - `msg` -- the data that needs to be signed (which depends on the storage server request
|
|
/// being made; for example, "retrieve9991234567890123" for a retrieve request to namespace
|
|
/// 999 made at unix time 1234567890.123; see storage server RPC documentation for details).
|
|
/// - `signing_value` -- the 100-byte subaccount signing value, as produced by an admin's
|
|
/// `swarm_make_subaccount` and provided to this member.
|
|
/// - `binary` -- if set to true then the returned values will be binary. If omitted (or
|
|
/// explicitly false), the returned struct values will be base64-encoded suitable for direct
|
|
/// passing as JSON values to the storage server without further encoding/modification.
|
|
///
|
|
/// Outputs:
|
|
/// - struct containing three binary values enabling swarm authentication (see description
|
|
/// above).
|
|
swarm_auth swarm_subaccount_sign(
|
|
std::span<const unsigned char> msg,
|
|
std::span<const unsigned char> signing_value,
|
|
bool binary = false) const;
|
|
|
|
/// API: groups/Keys::swarm_subaccount_token
|
|
///
|
|
/// Constructs the subaccount token for a session id. The main use of this is to submit a swarm
|
|
/// token revocation; for issuing subaccount tokens you want to use `swarm_make_subaccount`
|
|
/// instead. This will produce the same subaccount token that `swarm_make_subaccount`
|
|
/// implicitly creates that can be passed to a swarm to add a revocation for that subaccount.
|
|
///
|
|
/// This is recommended to be used when removing a non-admin member to prevent their access.
|
|
/// (Note, however, that there are circumstances where this can fail to prevent access, and so
|
|
/// should be combined with proper member removal and key rotation so that even if the member
|
|
/// gains access to messages, they cannot read them).
|
|
///
|
|
/// Inputs:
|
|
/// - `session_id` -- the session ID of the member (in hex)
|
|
/// - `write`, `del` -- optional; see `swarm_make_subaccount`. The same arguments should be
|
|
/// provided (or omitted) as were used in `swarm_make_subaccount`.
|
|
///
|
|
/// Outputs:
|
|
/// - 36 byte token that can be used for swarm token revocation.
|
|
std::vector<unsigned char> swarm_subaccount_token(
|
|
std::string_view session_id, bool write = true, bool del = false) const;
|
|
|
|
/// API: groups/Keys::pending_config
|
|
///
|
|
/// If a rekey has been performed but not yet confirmed then this will contain the config
|
|
/// message to be pushed to the swarm. If there is no push current pending then this returns
|
|
/// nullopt. The value should be used immediately (i.e. the std::span<const unsigned char> may
|
|
/// not remain valid if other calls to the config object are made).
|
|
///
|
|
/// Inputs: None
|
|
///
|
|
/// Outputs:
|
|
/// - `std::optional<std::span<const unsigned char>>` -- returns a populated config message that
|
|
/// should be pushed,
|
|
/// if not yet confirmed, otherwise when no pending update is present this returns nullopt.
|
|
std::optional<std::span<const unsigned char>> pending_config() const;
|
|
|
|
/// API: groups/Keys::pending_key
|
|
///
|
|
/// After calling rekey() this contains the new group encryption key *before* it is confirmed
|
|
/// pushed into the swarm. This is primarily intended for internal use as this key is generally
|
|
/// already propagated to the member/info lists when rekeying occurs.
|
|
///
|
|
/// The pending key is dropped when an incoming keys message is successfully loaded with either
|
|
/// the pending key itself, or a keys message with a higher generation.
|
|
///
|
|
/// Inputs: None
|
|
///
|
|
/// Outputs:
|
|
/// - `std::optional<std::span<const unsigned char>>` the encryption key generated by the last
|
|
/// `rekey()` call.
|
|
/// This is set to a new key when `rekey()` is called, and is cleared when any config message
|
|
/// is successfully loaded by `load_key`.
|
|
std::optional<std::span<const unsigned char>> pending_key() const;
|
|
|
|
/// API: groups/Keys::load_key
|
|
///
|
|
/// Loads a key pulled down from the swarm into this Keys object.
|
|
///
|
|
/// A Session client must process messages from the keys namespace *before* other group config
|
|
/// messages as new key messages may contain encryption keys needed to decrypt the other group
|
|
/// config message types.
|
|
///
|
|
/// It is safe to load the same config multiple times, and to load expired configs; such cases
|
|
/// would typically not change the keys, but are allowed anyway.
|
|
///
|
|
/// This method should always be wrapped in a `try/catch`: if the given configuration data is
|
|
/// malformed or is not properly signed an exception will be raised (but the Keys object remains
|
|
/// usable).
|
|
///
|
|
/// Inputs:
|
|
/// - `hash` - the message hash from the swarm
|
|
/// - `data` - the full stored config message value
|
|
/// - `timestamp_ms` - the timestamp (from the swarm) when this message was stored (used to
|
|
/// track when other keys expire).
|
|
/// - `info` - the given group::Info object's en/decryption key list will be updated to match
|
|
/// this object's key list.
|
|
/// - `members` - the given group::Members object's en/decryption key list will be updated to
|
|
/// match this object's key list.
|
|
///
|
|
/// Outputs:
|
|
/// - throws `std::runtime_error` (typically a subclass thereof) on failure to parse.
|
|
/// - returns true if we found a key for us in the message, false if we did not. Note that this
|
|
/// is mainly informative and does not signal an error: false could mean, for instance, be a
|
|
/// supplemental message that wasn't for us. Note also that true doesn't mean keys changed:
|
|
/// it could mean we decrypted one for us, but already had it.
|
|
bool load_key_message(
|
|
std::string_view hash,
|
|
std::span<const unsigned char> data,
|
|
int64_t timestamp_ms,
|
|
Info& info,
|
|
Members& members);
|
|
|
|
/// API: groups/Keys::active_hashes
|
|
///
|
|
/// Returns a set of message hashes of messages that contain currently active decryption keys.
|
|
/// These are the messages that should be periodically renewed by clients with write access to
|
|
/// keep them alive for other accounts (or devices) who might need them in the future.
|
|
///
|
|
/// Inputs: none
|
|
///
|
|
/// Outputs:
|
|
/// - vector of message hashes
|
|
std::unordered_set<std::string> active_hashes() const;
|
|
|
|
/// API: groups/Keys::needs_rekey
|
|
///
|
|
/// Returns true if the key list requires a new key to be generated and pushed to the server (by
|
|
/// calling `rekey()`). This will only be true for admin accounts (as only admin accounts can
|
|
/// call rekey()). Note that this value will also remain true until the pushed data is fetched
|
|
/// and loaded via `load_key_message`.
|
|
///
|
|
/// Note that this not only tracks when an automatic `rekey()` is needed because of a key
|
|
/// collision (such as two admins removing different members at the same time); there are other
|
|
/// situations in which rekey() should also be called (such as when kicking a member) that are
|
|
/// not reflected by this flag.
|
|
///
|
|
/// The recommended use of this method is to call it immediately after fetching messages from
|
|
/// the group config namespace of the swarm, whether or not new configs were retrieved, but
|
|
/// after processing incoming new config messages that were pulled down.
|
|
///
|
|
/// Unlike regular config messages, there is no need to confirm the push: confirmation (and
|
|
/// adoption of the new keys) happens when the new keys arrived back down from the swarm in the
|
|
/// next fetch.
|
|
///
|
|
/// Inputs: None
|
|
///
|
|
/// Outputs:
|
|
/// - `true` if a rekey is needed, `false` otherwise.
|
|
bool needs_rekey() const;
|
|
|
|
/// API: groups/Keys::needs_dump
|
|
///
|
|
/// Returns true if this Keys config has changes, either made directly or from incoming configs,
|
|
/// that need to be dumped to the database (made since the last call to `dump()`), false if no
|
|
/// changes have been made.
|
|
///
|
|
/// Inputs: None
|
|
///
|
|
/// Outputs:
|
|
/// - `true` if state needs to be dumped, `false` if state hasn't changed since the last
|
|
/// call to `dump()`.
|
|
bool needs_dump() const;
|
|
|
|
/// API: groups/Keys::dump
|
|
///
|
|
/// Returns a dump of the current state of this keys config that allows the Keys object to be
|
|
/// reinstantiated from scratch. Updates the internal needs_dump flag to false.
|
|
///
|
|
/// Although this can be called at any time, it is recommended to only do so when
|
|
/// `needs_dump()` returns true.
|
|
///
|
|
/// Inputs: None
|
|
///
|
|
/// Outputs:
|
|
/// - opaque binary data containing the group keys and other Keys config data that can be passed
|
|
/// to the `Keys` constructor to reinitialize a Keys object with the current state.
|
|
std::vector<unsigned char> dump();
|
|
|
|
/// API: groups/Keys::make_dump
|
|
///
|
|
/// Returns a dump of the current state; unlike `dump()` this does *not* update the internal
|
|
/// needs_dump flag; it is mostly used internally (by `dump()`), but can also be called
|
|
/// externally for debugging purposes.
|
|
///
|
|
/// Inputs: None
|
|
///
|
|
/// Outputs:
|
|
/// - `std::vector<unsigned char>` -- Returns binary data of the state dump
|
|
std::vector<unsigned char> make_dump() const;
|
|
|
|
/// API: groups/Keys::encrypt_message
|
|
///
|
|
/// Compresses, signs, and encrypts group message content with the user's underlying session
|
|
/// Ed25519 pubkey and the most-current group encryption key.
|
|
///
|
|
/// See: crypto/encrypt_for_group
|
|
///
|
|
/// This method will throw on failure:
|
|
/// - if there no encryption keys are available at all (which should not occur in normal use).
|
|
/// - if given a plaintext buffer larger than 1MB (even if the compressed version would be much
|
|
/// smaller). It is recommended that clients impose their own limits much smaller than this
|
|
/// on data passed into encrypt_message; this limitation is in *this* function to match the
|
|
/// `decrypt_message` limit which is merely intended to guard against decompression memory
|
|
/// exhaustion attacks.
|
|
///
|
|
/// Inputs:
|
|
/// - `plaintext` -- the binary message to encrypt.
|
|
/// - `compress` -- can be specified as `false` to forcibly disable compression. Normally
|
|
/// omitted, to use compression if and only if it reduces the size.
|
|
/// - `padding` -- the padding multiple: padding will be added as needed to attain a multiple of
|
|
/// this value for the final result. 0 or 1 disables padding entirely. Normally omitted to
|
|
/// use the default of next-multiple-of-256.
|
|
///
|
|
/// Outputs:
|
|
/// - `ciphertext` -- the encrypted, etc. value to send to the swarm
|
|
std::vector<unsigned char> encrypt_message(
|
|
std::span<const unsigned char> plaintext,
|
|
bool compress = true,
|
|
size_t padding = 256) const;
|
|
|
|
/// API: groups/Keys::decrypt_message
|
|
///
|
|
/// Decrypts group message content that encrypted with `encrypt_message`.
|
|
///
|
|
/// See: crypto/decrypt_group_message
|
|
/// verifies the sender signature, decompresses the message (if necessary) and then returns the
|
|
/// author pubkey and the plaintext data.
|
|
///
|
|
/// See: crypto/decrypt_group_message
|
|
///
|
|
/// Inputs:
|
|
/// - `ciphertext` -- an encrypted, encoded, signed, (possibly) compressed message as produced
|
|
/// by `encrypt_message()`.
|
|
///
|
|
/// Outputs:
|
|
/// - `std::pair<std::string, std::vector<unsigned char>>` -- the session ID (in hex) and the
|
|
/// plaintext binary
|
|
/// data that was encrypted.
|
|
///
|
|
/// On failure this throws a std::exception-derived exception with a `.what()` string containing
|
|
/// some diagnostic info on what part failed. Typically a production session client would catch
|
|
/// (and possibly log) but otherwise ignore such exceptions and just not process the message if
|
|
/// it throws.
|
|
std::pair<std::string, std::vector<unsigned char>> decrypt_message(
|
|
std::span<const unsigned char> ciphertext) const;
|
|
};
|
|
|
|
} // namespace session::config::groups
|