Skip to content

Commit

Permalink
Swarm subaccount authentication
Browse files Browse the repository at this point in the history
This adds methods to `Keys` that generates subaccount tokens and
signatures as needed to do storage server subaccount authentication
(which currently requires testnet as the subaccount code is not yet
active on mainnet), along with test code to test it.

Also adds a tests/swarm-auth-test binary that spits out storage requests
for store/retrieve testing.
  • Loading branch information
jagerman committed Aug 30, 2023
1 parent 390faa8 commit 8cb26be
Show file tree
Hide file tree
Showing 17 changed files with 807 additions and 93 deletions.
12 changes: 7 additions & 5 deletions include/session/config/groups/info.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,14 +73,16 @@ class Info final : public ConfigBase {
/// - `const char*` - Will return "groups::Info"
const char* encryption_domain() const override { return "groups::Info"; }

/// Returns the subaccount masking value. This is based on the group's seed and thus is only
/// obtainable by an admin account.
/// API: groups/Info::id
///
/// Inputs: none
/// Contains the (read-only) id of this group, that is, 03 followed by the pubkey in hex. (This
/// is equivalent to a 05-prefixed session_id, but is the group-specific identifier).
///
/// Inputs: None
///
/// Outputs:
/// - `ustring_view` - the 32-byte masking value.
std::array<unsigned char, 32> subaccount_mask() const;
/// - `std::string` containing the hex group id/pubkey
const std::string id;

/// API: groups/Info::get_name
///
Expand Down
143 changes: 143 additions & 0 deletions include/session/config/groups/keys.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ class Keys final : public ConfigSig {
// if it already existed.
bool insert_key(const 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. 75 is chosen because it's a decently large human-round number
Expand Down Expand Up @@ -297,6 +302,144 @@ class Keys final : public ConfigSig {
return key_supplement(std::vector{{std::move(sid)}});
}

/// 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:
/// - `ustring` -- 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.
ustring 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,
ustring_view session_ed25519_secretkey,
ustring_view signing_value,
bool write = false,
bool del = false);
bool swarm_verify_subaccount(
ustring_view 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 hex/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_auth_key`).
///
/// 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, the
/// returned struct values will be hex-encoded (subaccount token) or base64-encoded
/// (signatures) suitable for direct passing as JSON values to the storage server.
///
/// Outputs:
/// - struct containing three binary values enabling swarm authentication (see description
/// above).
swarm_auth swarm_subaccount_sign(
ustring_view msg, ustring_view 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.
ustring 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
Expand Down
7 changes: 4 additions & 3 deletions include/session/config/user_groups.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,10 @@ typedef struct ugroups_group_info {
bool have_secretkey; // Will be true if the `secretkey` is populated
unsigned char secretkey[64]; // If `have_secretkey` is set then this is the libsodium-style
// "secret key" for the group (i.e. 32 byte seed + 32 byte pubkey)
bool have_auth_sig; // Will be true if the `auth_sig` is populated
unsigned char auth_sig[64]; // If `have_auth_sig` is set then this is the authentication
// signature that can be used to access the swarm.
bool have_auth_data; // Will be true if the `auth_data` is populated
unsigned char auth_data[100]; // If `have_auth_data` is set then this is the authentication
// signing value that can be used to produce signature values to
// access the swarm.

int priority; // pinned message priority; 0 = unpinned, negative = hidden, positive = pinned
// (with higher meaning pinned higher).
Expand Down
11 changes: 7 additions & 4 deletions include/session/config/user_groups.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,10 +181,13 @@ struct group_info : base_group_info {
/// Group secret key (64 bytes); this is only possessed by admins.
ustring secretkey;

/// Group authentication signature; this is possessed by non-admins. (This value will be
/// dropped when serializing if secretkey is non-empty, and so does not need to be explicitly
/// cleared when being promoted to admin)
ustring auth_sig;
/// Group authentication signing value (100 bytes); this is used by non-admins to authenticate
/// (using the swarm key generation functions in config::groups::Keys). This value will be
/// dropped when serializing an updated config message if `secretkey` is non-empty (i.e. if it
/// is an admin), and so does not need to be explicitly cleared when being promoted to admin.
///
/// Producing and using this value is done with the groups::Keys `swarm` methods.
ustring auth_data;

/// Constructs a new group info from an hex id (03 + pubkey). Throws if id is invalid.
explicit group_info(std::string gid);
Expand Down
14 changes: 14 additions & 0 deletions include/session/xed25519.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,18 @@ std::array<unsigned char, 32> pubkey(ustring_view curve25519_pubkey);
/// "Softer" version that takes/returns strings of regular chars
std::string pubkey(std::string_view curve25519_pubkey);

/// Utility function that provides a constant-time `if (b) f = g;` implementation for byte arrays.
template <size_t N>
void constant_time_conditional_assign(
std::array<unsigned char, N>& f, const std::array<unsigned char, N>& g, bool b) {
std::array<unsigned char, N> x;
for (size_t i = 0; i < x.size(); i++)
x[i] = f[i] ^ g[i];
unsigned char mask = (unsigned char)(-(signed char)b);
for (size_t i = 0; i < x.size(); i++)
x[i] &= mask;
for (size_t i = 0; i < x.size(); i++)
f[i] ^= x[i];
}

} // namespace session::xed25519
7 changes: 2 additions & 5 deletions src/config/groups/info.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,8 @@ Info::Info(
ustring_view ed25519_pubkey,
std::optional<ustring_view> ed25519_secretkey,
std::optional<ustring_view> dumped) :
ConfigBase{dumped, ed25519_pubkey, ed25519_secretkey} {}

std::array<unsigned char, 32> Info::subaccount_mask() const {
return seed_hash("SessionGroupSubaccountMask");
}
ConfigBase{dumped, ed25519_pubkey, ed25519_secretkey},
id{"03" + oxenc::to_hex(ed25519_pubkey.begin(), ed25519_pubkey.end())} {}

std::optional<std::string_view> Info::get_name() const {
if (auto* s = data["n"].string(); s && !s->empty())
Expand Down

0 comments on commit 8cb26be

Please sign in to comment.