Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Groups support #42

Merged
merged 55 commits into from
Sep 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
5d10667
Group metadata; signed & read-only configs support
jagerman Aug 3, 2023
80f4d14
Add multi-key encryption tests
jagerman Aug 3, 2023
83c9dec
Remove duplicate implementation
jagerman Aug 4, 2023
12d601b
Add group info fields test
jagerman Aug 4, 2023
6f19646
Add group members config
jagerman Aug 4, 2023
8f142c0
Fix bad doc name
jagerman Aug 4, 2023
bf3df15
More API doc fixes
jagerman Aug 8, 2023
7e3001d
Group encryption keys
jagerman Aug 18, 2023
fdc664e
Address ftrget review comments
jagerman Aug 21, 2023
6e3cbf8
Fix broken assert
jagerman Aug 21, 2023
0fcc07c
Remove `signature_optional` parameter
jagerman Aug 21, 2023
c6fd471
Move all key management into `Keys`; make Keys dumpable
jagerman Aug 21, 2023
dddc5b3
Doc CI fix
jagerman Aug 22, 2023
f929e79
Doc fixes
jagerman Aug 22, 2023
cb40a14
Add dedicated namespace for messages; rearrange config namespace values
jagerman Aug 22, 2023
60cbeca
Add group message encryption + compression
jagerman Aug 22, 2023
286243c
Revert me -- disable broken group keys test
jagerman Aug 22, 2023
95aeea6
groups::Info C API
jagerman Aug 22, 2023
a2dc2e9
Add Group Members C wrappers
jagerman Aug 23, 2023
c454e35
Add safety limit to decompressed group decryption size
jagerman Aug 23, 2023
ae2f1ba
C wrapper API for group keys
jagerman Aug 23, 2023
a44567e
oxen-encoding update to fix llvm compile error
jagerman Aug 24, 2023
9953645
Add new group storage to UserGroups config
jagerman Aug 24, 2023
d3b902f
Fix unclosed list in key config generation
jagerman Aug 24, 2023
4551257
Fix broken x25519 extraction
jagerman Aug 25, 2023
8d9ce6e
Don't truncate secretkey in C API
jagerman Aug 25, 2023
744b25e
Fix broken logic in group_keys
jagerman Aug 25, 2023
3c5f74b
Add supplemental key messages
jagerman Aug 25, 2023
e83f479
fix admin key loading
jagerman Aug 25, 2023
cb89c0f
off by 2 error (Jason)
dr7ana Aug 24, 2023
b4cf7e2
Config keys unit tests
dr7ana Aug 25, 2023
9f447b6
review
dr7ana Aug 25, 2023
d121864
Fix crash when keys_ is empty
jagerman Aug 25, 2023
c431f12
Fix crash in C API keys init when no dump given
jagerman Aug 25, 2023
8837103
Supplemental config fixes and tests
jagerman Aug 26, 2023
517a61a
Updates for user groups -> groups
jagerman Aug 28, 2023
390faa8
Remove temporary testKeys binary
jagerman Aug 29, 2023
8cb26be
Swarm subaccount authentication
jagerman Aug 30, 2023
18d3df2
Don't build swarm-auth-test by default
jagerman Aug 30, 2023
224dda9
Format fix
jagerman Aug 30, 2023
9b0cdcd
C API updates, and related tweaks
jagerman Aug 31, 2023
e30122b
Add missing user_groups C API for new groups
jagerman Aug 31, 2023
2ad96d5
Keys.size(); updates to Keys C API
jagerman Aug 31, 2023
8a9d8ac
Fix doc typos/mistakes
jagerman Aug 31, 2023
24ed158
Add groups to convo info volatile
jagerman Aug 31, 2023
4d0c6e4
Completed group keys C api unit test
dr7ana Aug 31, 2023
3ed91d5
Fix propagation of secret key values
jagerman Aug 31, 2023
5854c4f
Fix name typo in groups_keys_size
jagerman Aug 31, 2023
194f972
Fix user_groups iterators with new closed groups
jagerman Sep 1, 2023
364f8d3
formatter
dr7ana Sep 1, 2023
8ed090e
C method to return groups keys
dr7ana Sep 1, 2023
bb7a2cf
Add current hash tracking
jagerman Sep 2, 2023
c272e06
Doc fix: `push()` should be `pending_config()`
jagerman Sep 5, 2023
2adb20c
Add invited and name to groups; add kicked methods
jagerman Sep 6, 2023
f61bc4a
Fix info/members key lists when loading from dump
jagerman Sep 6, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .drone.jsonnet
Original file line number Diff line number Diff line change
Expand Up @@ -285,9 +285,9 @@ local static_build(name,
'echo "Building on ${DRONE_STAGE_MACHINE}"',
apt_get_quiet + ' update',
apt_get_quiet + ' install -y python3-requests rsync',
'npm i docsify-cli -g',
'npm i docsify -g',
'npm i docsify-cli docsify-themeable [email protected] katex marked@4',
'cd docs/api/',
'export NODE_PATH=node_modules',
'make',
'../../utils/ci/drone-docs-upload.sh',
],
Expand Down
12 changes: 10 additions & 2 deletions docs/api/api-to-markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
#
# "Inputs: none."
# "Outputs: none."
# "Member variable."
# "Inputs:" followed by markdown (typically an unordered list) until the next match from this list.
# "Outputs:" followed by markdown
# "Example input:" followed by a code block (i.e. containing json)
Expand Down Expand Up @@ -91,6 +92,7 @@
DEV_RPC_START = re.compile(r"^Dev-API:\s*([\w/:]+)(.*)$")
IN_NONE = re.compile(r"^Inputs?: *[nN]one\.?$")
IN_SOME = re.compile(r"^Inputs?:\s*$")
MEMBER_VAR = re.compile(r"^Member +[vV]ar(?:iable)?\.?$")
DECL_SOME = re.compile(r"^Declaration?:\s*$")
OUT_SOME = re.compile(r"^Outputs?:\s*$")
EXAMPLE_IN = re.compile(r"^Example [iI]nputs?:\s*$")
Expand Down Expand Up @@ -159,6 +161,7 @@ class Parsing(Enum):
description, decl, inputs, outputs = "", "", "", ""
done_desc = False
no_inputs = False
member_var = False
examples = []
cur_ex_in = None
old_names = []
Expand Down Expand Up @@ -194,6 +197,9 @@ class Parsing(Enum):
error("found multiple Inputs:")
inputs, no_inputs, mode = MD_NO_INPUT, True, Parsing.NONE

elif re.search(MEMBER_VAR, line):
member_var, no_inputs, mode = True, True, Parsing.DESC

elif re.search(DECL_SOME, line):
if inputs:
error("found multiple Syntax:")
Expand Down Expand Up @@ -285,7 +291,7 @@ class Parsing(Enum):
# We hit the end of the commented section
if not description or inputs.isspace():
problems.append("endpoint has no description")
if not inputs or inputs.isspace():
if (not inputs or inputs.isspace()) and not member_var:
problems.append(
"endpoint has no inputs description; perhaps you need to add 'Inputs: none.'?"
)
Expand Down Expand Up @@ -321,7 +327,9 @@ class Parsing(Enum):
{MD_DECL_HEADER}

{decl}

"""
if not member_var:
md = md + f"""
{MD_INPUT_HEADER}

{inputs}
Expand Down
1 change: 1 addition & 0 deletions docs/api/static/sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- [Convo Info Volatile](convo_info_volatile.md)
- [Encrypt](encrypt.md)
- [Error](error.md)
- [Groups](groups.md)
- [User Groups](user_groups.md)
- [User Profile](user_profile.md)
- [Utils](util.md)
2 changes: 1 addition & 1 deletion external/oxen-encoding
71 changes: 57 additions & 14 deletions include/session/config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ struct missing_signature : signature_error {
struct config_parse_error : config_error {
using config_error::config_error;
};
/// Type thrown for some bad value in a config (e.g. missing required key, or key with an
/// unexpected/unhandled value).
struct config_value_error : config_parse_error {
using config_parse_error::config_parse_error;
};

/// Class for a parsed, read-only config message; also serves as the base class of a
/// MutableConfigMessage which allows setting values.
Expand Down Expand Up @@ -87,7 +92,7 @@ class ConfigMessage {
/// (so that they can return a reference to it).
seqno_hash_t seqno_hash_{0, {0}};

bool verified_signature_ = false;
std::optional<std::array<unsigned char, 64>> verified_signature_;

// This will be set during construction from configs based on the merge result:
// -1 means we had to merge one or more configs together into a new merged config
Expand Down Expand Up @@ -123,7 +128,7 @@ class ConfigMessage {
verify_callable verifier = nullptr,
sign_callable signer = nullptr,
int lag = DEFAULT_DIFF_LAGS,
bool signature_optional = false);
bool trust_signature = false);

/// Constructs a new ConfigMessage by loading and potentially merging multiple serialized
/// ConfigMessages together, according to the config conflict resolution rules. The result
Expand All @@ -147,10 +152,6 @@ class ConfigMessage {
/// diffs that exceeding this lag value will have those early lagged diffs dropping during
/// loading.
///
/// signature_optional - if true then accept a message with no signature even when a verifier is
/// set, thus allowing unsigned messages (though messages with an invalid signature are still
/// not allowed). This option is ignored when verifier is not set.
///
/// error_handler - if set then any config message parsing error will be passed to this function
/// for handling with the index of `configs` that failed and the error exception: the callback
/// typically warns and, if the overall construction should abort, rethrows the error. If this
Expand All @@ -163,7 +164,6 @@ class ConfigMessage {
verify_callable verifier = nullptr,
sign_callable signer = nullptr,
int lag = DEFAULT_DIFF_LAGS,
bool signature_optional = false,
std::function<void(size_t, const config_error&)> error_handler = nullptr);

/// Returns a read-only reference to the contained data. (To get a mutable config object use
Expand Down Expand Up @@ -211,10 +211,13 @@ class ConfigMessage {
/// data), this will return -1.
int unmerged_index() const { return unmerged_; }

/// Returns true if this message contained a valid, verified signature when it was parsed.
/// Returns false otherwise (e.g. not loaded from verification at all; loaded without a
/// verification function; or had no signature and a signature wasn't required).
bool verified_signature() const { return verified_signature_; }
/// Read-only access to the optional verified signature if this message contained a valid,
/// verified signature when it was parsed. Returns nullopt otherwise (e.g. not loaded from
/// verification at all; loaded without a verification function; or had no signature and a
/// signature wasn't required).
const std::optional<std::array<unsigned char, 64>>& verified_signature() {
return verified_signature_;
}

/// Constructs a new MutableConfigMessage from this config message with an incremented seqno.
/// The new config message's diff will reflect changes made after this construction.
Expand Down Expand Up @@ -283,7 +286,6 @@ class MutableConfigMessage : public ConfigMessage {
verify_callable verifier = nullptr,
sign_callable signer = nullptr,
int lag = DEFAULT_DIFF_LAGS,
bool signature_optional = false,
std::function<void(size_t, const config_error&)> error_handler = nullptr);

/// Wrapper around the above that takes a single string view to load a single message, doesn't
Expand All @@ -293,8 +295,7 @@ class MutableConfigMessage : public ConfigMessage {
ustring_view config,
verify_callable verifier = nullptr,
sign_callable signer = nullptr,
int lag = DEFAULT_DIFF_LAGS,
bool signature_optional = false);
int lag = DEFAULT_DIFF_LAGS);

/// Does the same as the base incrementing, but also records any diff info from the current
/// MutableConfigMessage. *this* object gets pruned and signed as part of this call. If the
Expand Down Expand Up @@ -342,6 +343,48 @@ class MutableConfigMessage : public ConfigMessage {
void increment_impl();
};

/// API: base/verify_config_sig
///
/// Verifies a config message signature, throwing a missing_signature or signature_error exception
/// if the signature is missing or invalid.
///
/// A config message signature is always in the "~" key of a config message, which must be the
/// very last key of the message, and signs the config value up to (but not including) the ~
/// key-value pair in the serialized config message.
///
/// For instance, for a config message of:
///
/// d[...configdata...]1:~64:[sigdata]e
///
/// the signature signs the value `d[...configdata...]` (i.e. the `1:~64:[sigdata]` signature
/// pair, and the final closing `e` of the config message, are not included). No keys may
/// follow the signature key/value.
///
/// Inputs:
/// - `dict` -- a `bt_dict_consumer` positioned at or before the "~" key where the signature is
/// expected. (If the bt_dict_consumer has already consumed the "~" key then this call will fail
/// as if the signature was missing).
/// - `config_msg` -- the full config message; this must be a view of the same data in memory that
/// `dict` is parsing (i.e. it cannot be a copy).
/// - `verifier` -- a callback to invoke to verify the signature of the message. If the callback is
/// empty then the signature will be ignored (it is neither required nor verified).
/// - `verified_signature` is a pointer to a std::optional array of signature data; if this is
/// specified and not nullptr then the optional with be emplaced with the signature bytes if the
/// signature successfully validates.
/// - `trust_signature` bypasses the verification and signature requirements, blinding trusting a
/// signature if present. This is intended for use when restoring from a dump (along with a
/// nullptr verifier).
///
/// Outputs:
/// - returns with no value on success
/// - throws on failure
void verify_config_sig(
oxenc::bt_dict_consumer dict,
ustring_view config_msg,
const ConfigMessage::verify_callable& verifier,
std::optional<std::array<unsigned char, 64>>* verified_signature = nullptr,
bool trust_signature = false);

} // namespace session::config

namespace oxenc::detail {
Expand Down
74 changes: 74 additions & 0 deletions include/session/config/base.h
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,27 @@ typedef struct config_string_list {
/// - `config_string_list*` -- point to the list of hashes, pointer belongs to the caller
LIBSESSION_EXPORT config_string_list* config_current_hashes(const config_object* conf);

/// API: base/config_get_keys
///
/// Obtains the current group decryption keys.
///
/// Returns a buffer where each consecutive 32 bytes is an encryption key for the object, in
/// priority order (i.e. the key at 0 is the encryption key, and the first decryption key).
///
/// This function is mainly for debugging/diagnostics purposes; most config types have one single
/// key (based on the secret key), and multi-keyed configs such as groups have their own methods for
/// encryption/decryption that are already aware of the multiple keys.
///
/// Inputs:
/// - `conf` -- [in] Pointer to the config_object object
/// - `len` -- [out] Pointer where the number of keys will be written (that is: the returned pointer
/// will be to a buffer which has a size of of this value times 32).
///
/// Outputs:
/// - `unsigned char*` -- pointer to newly malloced key data (a multiple of 32 bytes); the pointer
/// belongs to the caller and must be `free()`d when done with it.
LIBSESSION_EXPORT unsigned char* config_get_keys(const config_object* conf, size_t* len);

/// Config key management; see the corresponding method docs in base.hpp. All `key` arguments here
/// are 32-byte binary buffers (and since fixed-length, there is no keylen argument).

Expand Down Expand Up @@ -446,6 +467,59 @@ LIBSESSION_EXPORT const unsigned char* config_key(const config_object* conf, siz
/// - `char*` -- encryption domain C-str used to encrypt values
LIBSESSION_EXPORT const char* config_encryption_domain(const config_object* conf);

/// API: base/config_set_sig_keys
///
/// Sets an Ed25519 keypair pair for signing and verifying config messages. When set, this adds an
/// additional signature for verification into the config message (*after* decryption) that
/// validates a config message.
///
/// This is used in config contexts where the encryption/decryption keys are insufficient for
/// permission verification to produce new messages, such as in groups where non-admins need to be
/// able to decrypt group data, but are not permitted to push new group data. In such a case only
/// the admins have the secret key with which messages can be signed; regular users can only read,
/// but cannot write, config messages.
///
/// When a signature public key (with or without a secret key) is set the config object enters a
/// "signing-required" mode, which has some implications worth noting:
/// - incoming messages must contain a signature that verifies with the public key; messages
/// without such a signature will be dropped as invalid.
/// - because of the above, a config object cannot push config updates without the secret key:
/// thus any attempt to modify the config message with a pubkey-only config object will raise
/// an exception.
///
/// Inputs:
/// - `secret` -- pointer to a 64-byte sodium-style Ed25519 "secret key" buffer (technically the
/// seed+precomputed pubkey concatenated together) that sets both the secret key and public key.
LIBSESSION_EXPORT void config_set_sig_keys(config_object* conf, const unsigned char* secret);

/// API: base/config_set_sig_pubkey
///
/// Sets a Ed25519 signing pubkey which incoming messages must be signed by to be acceptable. This
/// is intended for use when the secret key is not known (see `config_set_sig_keys()` to set both
/// secret and pubkey keys together).
///
/// Inputs:
/// - `pubkey` -- pointer to the 32-byte Ed25519 pubkey that must have signed incoming messages.
LIBSESSION_EXPORT void config_set_sig_pubkey(config_object* conf, const unsigned char* pubkey);

/// API: base/config_get_sig_pubkey
///
/// Returns a pointer to the 32-byte Ed25519 signing pubkey, if set. Returns nullptr if there is no
/// current signing pubkey.
///
/// Inputs: none.
///
/// Outputs:
/// - pointer to the 32-byte pubkey, or NULL if not set.
LIBSESSION_EXPORT const unsigned char* config_get_sig_pubkey(const config_object* conf);

/// API: base/config_clear_sig_keys
///
/// Drops the signature pubkey and/or secret key, if the object has them.
///
/// Inputs: none.
LIBSESSION_EXPORT void config_clear_sig_keys(config_object* conf);

#ifdef __cplusplus
} // extern "C"
#endif
Loading