add basic discord support
This commit is contained in:
274
DPP/mlspp/include/mls/common.h
Executable file
274
DPP/mlspp/include/mls/common.h
Executable file
@@ -0,0 +1,274 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <iomanip>
|
||||
#include <iterator>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
using namespace std::literals::string_literals;
|
||||
|
||||
// Expose the bytes library globally
|
||||
#include <bytes/bytes.h>
|
||||
using namespace mlspp::bytes_ns;
|
||||
|
||||
// Expose the compatibility library globally
|
||||
#include <tls/compat.h>
|
||||
namespace var = mlspp::tls::var;
|
||||
namespace opt = mlspp::tls::opt;
|
||||
|
||||
namespace mlspp {
|
||||
|
||||
// Make variant equality work in the same way as optional equality, with
|
||||
// automatic unwrapping. In other words
|
||||
//
|
||||
// v == T(x) <=> hold_alternative<T>(v) && get<T>(v) == x
|
||||
//
|
||||
// For consistency, we also define symmetric and negated version. In this
|
||||
// house, we obey the symmetric law of equivalence relations!
|
||||
template<typename T, typename... Ts>
|
||||
bool
|
||||
operator==(const var::variant<Ts...>& v, const T& t)
|
||||
{
|
||||
return var::visit(
|
||||
[&](const auto& arg) {
|
||||
using U = std::decay_t<decltype(arg)>;
|
||||
if constexpr (std::is_same_v<U, T>) {
|
||||
return arg == t;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
v);
|
||||
}
|
||||
|
||||
template<typename T, typename... Ts>
|
||||
bool
|
||||
operator==(const T& t, const var::variant<Ts...>& v)
|
||||
{
|
||||
return v == t;
|
||||
}
|
||||
|
||||
template<typename T, typename... Ts>
|
||||
bool
|
||||
operator!=(const var::variant<Ts...>& v, const T& t)
|
||||
{
|
||||
return !(v == t);
|
||||
}
|
||||
|
||||
template<typename T, typename... Ts>
|
||||
bool
|
||||
operator!=(const T& t, const var::variant<Ts...>& v)
|
||||
{
|
||||
return !(v == t);
|
||||
}
|
||||
|
||||
using epoch_t = uint64_t;
|
||||
|
||||
///
|
||||
/// Get the current system clock time in the format MLS expects
|
||||
///
|
||||
|
||||
uint64_t
|
||||
seconds_since_epoch();
|
||||
|
||||
///
|
||||
/// Easy construction of overloaded lambdas
|
||||
///
|
||||
|
||||
template<class... Ts>
|
||||
struct overloaded : Ts...
|
||||
{
|
||||
using Ts::operator()...;
|
||||
|
||||
// XXX(RLB) MSVC has a bug where it incorrectly computes the size of this
|
||||
// type. Microsoft claims they have fixed it in the latest MSVC, and GitHub
|
||||
// claims they are running a version with the fix. But in practice, we still
|
||||
// hit it. Including this dummy variable is a work-around.
|
||||
//
|
||||
// https://developercommunity.visualstudio.com/t/runtime-stack-corruption-using-stdvisit/346200
|
||||
int dummy = 0;
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
// XXX(RLB): For some reason, different versions of clang-format disagree on how
|
||||
// this should be formatted. Probably because it's new syntax with C++17?
|
||||
// Exempting it from clang-format for now.
|
||||
template<class... Ts> overloaded(Ts...) -> overloaded<Ts...>;
|
||||
// clang-format on
|
||||
|
||||
///
|
||||
/// Auto-generate equality and inequality operators for TLS-serializable things
|
||||
///
|
||||
|
||||
template<typename T>
|
||||
inline typename std::enable_if<T::_tls_serializable, bool>::type
|
||||
operator==(const T& lhs, const T& rhs)
|
||||
{
|
||||
return lhs._tls_fields_w() == rhs._tls_fields_w();
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
inline typename std::enable_if<T::_tls_serializable, bool>::type
|
||||
operator!=(const T& lhs, const T& rhs)
|
||||
{
|
||||
return lhs._tls_fields_w() != rhs._tls_fields_w();
|
||||
}
|
||||
|
||||
///
|
||||
/// Error types
|
||||
///
|
||||
|
||||
// The `using parent = X` / `using parent::parent` construction here
|
||||
// imports the constructors of the parent.
|
||||
|
||||
class NotImplementedError : public std::exception
|
||||
{
|
||||
public:
|
||||
using parent = std::exception;
|
||||
using parent::parent;
|
||||
};
|
||||
|
||||
class ProtocolError : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
using parent = std::runtime_error;
|
||||
using parent::parent;
|
||||
};
|
||||
|
||||
class IncompatibleNodesError : public std::invalid_argument
|
||||
{
|
||||
public:
|
||||
using parent = std::invalid_argument;
|
||||
using parent::parent;
|
||||
};
|
||||
|
||||
class InvalidParameterError : public std::invalid_argument
|
||||
{
|
||||
public:
|
||||
using parent = std::invalid_argument;
|
||||
using parent::parent;
|
||||
};
|
||||
|
||||
class InvalidPathError : public std::invalid_argument
|
||||
{
|
||||
public:
|
||||
using parent = std::invalid_argument;
|
||||
using parent::parent;
|
||||
};
|
||||
|
||||
class InvalidIndexError : public std::invalid_argument
|
||||
{
|
||||
public:
|
||||
using parent = std::invalid_argument;
|
||||
using parent::parent;
|
||||
};
|
||||
|
||||
class InvalidMessageTypeError : public std::invalid_argument
|
||||
{
|
||||
public:
|
||||
using parent = std::invalid_argument;
|
||||
using parent::parent;
|
||||
};
|
||||
|
||||
class MissingNodeError : public std::out_of_range
|
||||
{
|
||||
public:
|
||||
using parent = std::out_of_range;
|
||||
using parent::parent;
|
||||
};
|
||||
|
||||
class MissingStateError : public std::out_of_range
|
||||
{
|
||||
public:
|
||||
using parent = std::out_of_range;
|
||||
using parent::parent;
|
||||
};
|
||||
|
||||
// A slightly more elegant way to silence -Werror=unused-variable
|
||||
template<typename T>
|
||||
void
|
||||
silence_unused(const T& val)
|
||||
{
|
||||
(void)val;
|
||||
}
|
||||
|
||||
namespace stdx {
|
||||
|
||||
// XXX(RLB) This method takes any container in, but always puts the resuls in
|
||||
// std::vector. The output could be made generic with a Rust-like syntax,
|
||||
// defining a PendingTransform object that caches the inputs, with a template
|
||||
// `collect()` method that puts them in an output container. Which makes the
|
||||
// calling syntax as follows:
|
||||
//
|
||||
// auto out = stdx::transform(in, f).collect<Container>();
|
||||
//
|
||||
// (You always need the explicit specialization, even if assigning it to an
|
||||
// explicitly typed variable, because C++ won't infer return types.)
|
||||
//
|
||||
// Given that the above syntax is pretty chatty, and we never need anything
|
||||
// other than vectors here anyway, I have left this as-is.
|
||||
template<typename Value, typename Container, typename UnaryOperation>
|
||||
std::vector<Value>
|
||||
transform(const Container& c, const UnaryOperation& op)
|
||||
{
|
||||
auto out = std::vector<Value>{};
|
||||
auto ins = std::inserter(out, out.begin());
|
||||
std::transform(c.begin(), c.end(), ins, op);
|
||||
return out;
|
||||
}
|
||||
|
||||
template<typename Container, typename UnaryPredicate>
|
||||
bool
|
||||
any_of(const Container& c, const UnaryPredicate& pred)
|
||||
{
|
||||
return std::any_of(c.begin(), c.end(), pred);
|
||||
}
|
||||
|
||||
template<typename Container, typename UnaryPredicate>
|
||||
bool
|
||||
all_of(const Container& c, const UnaryPredicate& pred)
|
||||
{
|
||||
return std::all_of(c.begin(), c.end(), pred);
|
||||
}
|
||||
|
||||
template<typename Container, typename UnaryPredicate>
|
||||
auto
|
||||
count_if(const Container& c, const UnaryPredicate& pred)
|
||||
{
|
||||
return std::count_if(c.begin(), c.end(), pred);
|
||||
}
|
||||
|
||||
template<typename Container, typename Value>
|
||||
bool
|
||||
contains(const Container& c, const Value& val)
|
||||
{
|
||||
return std::find(c.begin(), c.end(), val) != c.end();
|
||||
}
|
||||
|
||||
template<typename Container, typename UnaryPredicate>
|
||||
auto
|
||||
find_if(Container& c, const UnaryPredicate& pred)
|
||||
{
|
||||
return std::find_if(c.begin(), c.end(), pred);
|
||||
}
|
||||
|
||||
template<typename Container, typename UnaryPredicate>
|
||||
auto
|
||||
find_if(const Container& c, const UnaryPredicate& pred)
|
||||
{
|
||||
return std::find_if(c.begin(), c.end(), pred);
|
||||
}
|
||||
|
||||
template<typename Container, typename Value>
|
||||
auto
|
||||
upper_bound(const Container& c, const Value& val)
|
||||
{
|
||||
return std::upper_bound(c.begin(), c.end(), val);
|
||||
}
|
||||
|
||||
} // namespace stdx
|
||||
|
||||
} // namespace mlspp
|
||||
380
DPP/mlspp/include/mls/core_types.h
Executable file
380
DPP/mlspp/include/mls/core_types.h
Executable file
@@ -0,0 +1,380 @@
|
||||
#pragma once
|
||||
|
||||
#include "mls/credential.h"
|
||||
#include "mls/crypto.h"
|
||||
#include "mls/tree_math.h"
|
||||
|
||||
namespace mlspp {
|
||||
|
||||
// enum {
|
||||
// reserved(0),
|
||||
// mls10(1),
|
||||
// (255)
|
||||
// } ProtocolVersion;
|
||||
enum class ProtocolVersion : uint16_t
|
||||
{
|
||||
mls10 = 0x01,
|
||||
};
|
||||
|
||||
extern const std::array<ProtocolVersion, 1> all_supported_versions;
|
||||
|
||||
// struct {
|
||||
// ExtensionType extension_type;
|
||||
// opaque extension_data<V>;
|
||||
// } Extension;
|
||||
struct Extension
|
||||
{
|
||||
using Type = uint16_t;
|
||||
|
||||
Type type;
|
||||
bytes data;
|
||||
|
||||
TLS_SERIALIZABLE(type, data)
|
||||
};
|
||||
|
||||
struct ExtensionType
|
||||
{
|
||||
static constexpr Extension::Type application_id = 1;
|
||||
static constexpr Extension::Type ratchet_tree = 2;
|
||||
static constexpr Extension::Type required_capabilities = 3;
|
||||
static constexpr Extension::Type external_pub = 4;
|
||||
static constexpr Extension::Type external_senders = 5;
|
||||
|
||||
// XXX(RLB) There is no IANA-registered type for this extension yet, so we use
|
||||
// a value from the vendor-specific space
|
||||
static constexpr Extension::Type sframe_parameters = 0xff02;
|
||||
};
|
||||
|
||||
struct ExtensionList
|
||||
{
|
||||
std::vector<Extension> extensions;
|
||||
|
||||
// XXX(RLB) It would be good if this maintained extensions in order. It might
|
||||
// be possible to do this automatically by changing the storage to a
|
||||
// map<ExtensionType, bytes> and extending the TLS code to marshal that type.
|
||||
template<typename T>
|
||||
inline void add(const T& obj)
|
||||
{
|
||||
auto data = tls::marshal(obj);
|
||||
add(T::type, std::move(data));
|
||||
}
|
||||
|
||||
void add(Extension::Type type, bytes data);
|
||||
|
||||
template<typename T>
|
||||
std::optional<T> find() const
|
||||
{
|
||||
for (const auto& ext : extensions) {
|
||||
if (ext.type == T::type) {
|
||||
return tls::get<T>(ext.data);
|
||||
}
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
bool has(uint16_t type) const;
|
||||
|
||||
TLS_SERIALIZABLE(extensions)
|
||||
};
|
||||
|
||||
// enum {
|
||||
// reserved(0),
|
||||
// key_package(1),
|
||||
// update(2),
|
||||
// commit(3),
|
||||
// (255)
|
||||
// } LeafNodeSource;
|
||||
enum struct LeafNodeSource : uint8_t
|
||||
{
|
||||
key_package = 1,
|
||||
update = 2,
|
||||
commit = 3,
|
||||
};
|
||||
|
||||
// struct {
|
||||
// ProtocolVersion versions<V>;
|
||||
// CipherSuite ciphersuites<V>;
|
||||
// ExtensionType extensions<V>;
|
||||
// ProposalType proposals<V>;
|
||||
// CredentialType credentials<V>;
|
||||
// } Capabilities;
|
||||
struct Capabilities
|
||||
{
|
||||
std::vector<ProtocolVersion> versions;
|
||||
std::vector<CipherSuite::ID> cipher_suites;
|
||||
std::vector<Extension::Type> extensions;
|
||||
std::vector<uint16_t> proposals;
|
||||
std::vector<CredentialType> credentials;
|
||||
|
||||
static Capabilities create_default();
|
||||
bool extensions_supported(const std::vector<Extension::Type>& required) const;
|
||||
bool proposals_supported(const std::vector<uint16_t>& required) const;
|
||||
bool credential_supported(const Credential& credential) const;
|
||||
|
||||
template<typename Container>
|
||||
bool credentials_supported(const Container& required) const
|
||||
{
|
||||
return stdx::all_of(required, [&](CredentialType type) {
|
||||
return stdx::contains(credentials, type);
|
||||
});
|
||||
}
|
||||
|
||||
TLS_SERIALIZABLE(versions, cipher_suites, extensions, proposals, credentials)
|
||||
};
|
||||
|
||||
// struct {
|
||||
// uint64 not_before;
|
||||
// uint64 not_after;
|
||||
// } Lifetime;
|
||||
struct Lifetime
|
||||
{
|
||||
uint64_t not_before;
|
||||
uint64_t not_after;
|
||||
|
||||
static Lifetime create_default();
|
||||
|
||||
TLS_SERIALIZABLE(not_before, not_after)
|
||||
};
|
||||
|
||||
// struct {
|
||||
// HPKEPublicKey encryption_key;
|
||||
// SignaturePublicKey signature_key;
|
||||
// Credential credential;
|
||||
// Capabilities capabilities;
|
||||
//
|
||||
// LeafNodeSource leaf_node_source;
|
||||
// select (leaf_node_source) {
|
||||
// case add:
|
||||
// Lifetime lifetime;
|
||||
//
|
||||
// case update:
|
||||
// struct {}
|
||||
//
|
||||
// case commit:
|
||||
// opaque parent_hash<V>;
|
||||
// }
|
||||
//
|
||||
// Extension extensions<V>;
|
||||
// // SignWithLabel(., "LeafNodeTBS", LeafNodeTBS)
|
||||
// opaque signature<V>;
|
||||
// } LeafNode;
|
||||
struct Empty
|
||||
{
|
||||
TLS_SERIALIZABLE()
|
||||
};
|
||||
|
||||
struct ParentHash
|
||||
{
|
||||
bytes parent_hash;
|
||||
TLS_SERIALIZABLE(parent_hash);
|
||||
};
|
||||
|
||||
struct LeafNodeOptions
|
||||
{
|
||||
std::optional<Credential> credential;
|
||||
std::optional<Capabilities> capabilities;
|
||||
std::optional<ExtensionList> extensions;
|
||||
};
|
||||
|
||||
// TODO Move this to treekem.h
|
||||
struct LeafNode
|
||||
{
|
||||
HPKEPublicKey encryption_key;
|
||||
SignaturePublicKey signature_key;
|
||||
Credential credential;
|
||||
Capabilities capabilities;
|
||||
|
||||
var::variant<Lifetime, Empty, ParentHash> content;
|
||||
|
||||
ExtensionList extensions;
|
||||
bytes signature;
|
||||
|
||||
LeafNode() = default;
|
||||
LeafNode(const LeafNode&) = default;
|
||||
LeafNode(LeafNode&&) = default;
|
||||
LeafNode& operator=(const LeafNode&) = default;
|
||||
LeafNode& operator=(LeafNode&&) = default;
|
||||
|
||||
LeafNode(CipherSuite cipher_suite,
|
||||
HPKEPublicKey encryption_key_in,
|
||||
SignaturePublicKey signature_key_in,
|
||||
Credential credential_in,
|
||||
Capabilities capabilities_in,
|
||||
Lifetime lifetime_in,
|
||||
ExtensionList extensions_in,
|
||||
const SignaturePrivateKey& sig_priv);
|
||||
|
||||
LeafNode for_update(CipherSuite cipher_suite,
|
||||
const bytes& group_id,
|
||||
LeafIndex leaf_index,
|
||||
HPKEPublicKey encryption_key,
|
||||
const LeafNodeOptions& opts,
|
||||
const SignaturePrivateKey& sig_priv_in) const;
|
||||
|
||||
LeafNode for_commit(CipherSuite cipher_suite,
|
||||
const bytes& group_id,
|
||||
LeafIndex leaf_index,
|
||||
HPKEPublicKey encryption_key,
|
||||
const bytes& parent_hash,
|
||||
const LeafNodeOptions& opts,
|
||||
const SignaturePrivateKey& sig_priv_in) const;
|
||||
|
||||
void set_capabilities(Capabilities capabilities_in);
|
||||
|
||||
LeafNodeSource source() const;
|
||||
|
||||
struct MemberBinding
|
||||
{
|
||||
bytes group_id;
|
||||
LeafIndex leaf_index;
|
||||
TLS_SERIALIZABLE(group_id, leaf_index);
|
||||
};
|
||||
|
||||
void sign(CipherSuite cipher_suite,
|
||||
const SignaturePrivateKey& sig_priv,
|
||||
const std::optional<MemberBinding>& binding);
|
||||
bool verify(CipherSuite cipher_suite,
|
||||
const std::optional<MemberBinding>& binding) const;
|
||||
|
||||
bool verify_expiry(uint64_t now) const;
|
||||
bool verify_extension_support(const ExtensionList& ext_list) const;
|
||||
|
||||
TLS_SERIALIZABLE(encryption_key,
|
||||
signature_key,
|
||||
credential,
|
||||
capabilities,
|
||||
content,
|
||||
extensions,
|
||||
signature)
|
||||
TLS_TRAITS(tls::pass,
|
||||
tls::pass,
|
||||
tls::pass,
|
||||
tls::pass,
|
||||
tls::variant<LeafNodeSource>,
|
||||
tls::pass,
|
||||
tls::pass)
|
||||
|
||||
private:
|
||||
LeafNode clone_with_options(HPKEPublicKey encryption_key,
|
||||
const LeafNodeOptions& opts) const;
|
||||
bytes to_be_signed(const std::optional<MemberBinding>& binding) const;
|
||||
};
|
||||
|
||||
// Concrete extension types
|
||||
struct RequiredCapabilitiesExtension
|
||||
{
|
||||
std::vector<Extension::Type> extensions;
|
||||
std::vector<uint16_t> proposals;
|
||||
|
||||
static const Extension::Type type;
|
||||
TLS_SERIALIZABLE(extensions, proposals)
|
||||
};
|
||||
|
||||
struct ApplicationIDExtension
|
||||
{
|
||||
bytes id;
|
||||
|
||||
static const Extension::Type type;
|
||||
TLS_SERIALIZABLE(id)
|
||||
};
|
||||
|
||||
///
|
||||
/// NodeType, ParentNode, and KeyPackage
|
||||
///
|
||||
|
||||
// TODO move this to treekem.h
|
||||
struct ParentNode
|
||||
{
|
||||
HPKEPublicKey public_key;
|
||||
bytes parent_hash;
|
||||
std::vector<LeafIndex> unmerged_leaves;
|
||||
|
||||
bytes hash(CipherSuite suite) const;
|
||||
|
||||
TLS_SERIALIZABLE(public_key, parent_hash, unmerged_leaves)
|
||||
};
|
||||
|
||||
// TODO Move this to messages.h
|
||||
// struct {
|
||||
// ProtocolVersion version;
|
||||
// CipherSuite cipher_suite;
|
||||
// HPKEPublicKey init_key;
|
||||
// LeafNode leaf_node;
|
||||
// Extension extensions<V>;
|
||||
// // SignWithLabel(., "KeyPackageTBS", KeyPackageTBS)
|
||||
// opaque signature<V>;
|
||||
// } KeyPackage;
|
||||
struct KeyPackage
|
||||
{
|
||||
ProtocolVersion version;
|
||||
CipherSuite cipher_suite;
|
||||
HPKEPublicKey init_key;
|
||||
LeafNode leaf_node;
|
||||
ExtensionList extensions;
|
||||
bytes signature;
|
||||
|
||||
KeyPackage();
|
||||
KeyPackage(CipherSuite suite_in,
|
||||
HPKEPublicKey init_key_in,
|
||||
LeafNode leaf_node_in,
|
||||
ExtensionList extensions_in,
|
||||
const SignaturePrivateKey& sig_priv_in);
|
||||
|
||||
KeyPackageRef ref() const;
|
||||
|
||||
void sign(const SignaturePrivateKey& sig_priv);
|
||||
bool verify() const;
|
||||
|
||||
TLS_SERIALIZABLE(version,
|
||||
cipher_suite,
|
||||
init_key,
|
||||
leaf_node,
|
||||
extensions,
|
||||
signature)
|
||||
|
||||
private:
|
||||
bytes to_be_signed() const;
|
||||
};
|
||||
|
||||
///
|
||||
/// UpdatePath
|
||||
///
|
||||
|
||||
// struct {
|
||||
// HPKEPublicKey public_key;
|
||||
// HPKECiphertext encrypted_path_secret<V>;
|
||||
// } UpdatePathNode;
|
||||
struct UpdatePathNode
|
||||
{
|
||||
HPKEPublicKey public_key;
|
||||
std::vector<HPKECiphertext> encrypted_path_secret;
|
||||
|
||||
TLS_SERIALIZABLE(public_key, encrypted_path_secret)
|
||||
};
|
||||
|
||||
// struct {
|
||||
// LeafNode leaf_node;
|
||||
// UpdatePathNode nodes<V>;
|
||||
// } UpdatePath;
|
||||
struct UpdatePath
|
||||
{
|
||||
LeafNode leaf_node;
|
||||
std::vector<UpdatePathNode> nodes;
|
||||
|
||||
TLS_SERIALIZABLE(leaf_node, nodes)
|
||||
};
|
||||
|
||||
} // namespace mlspp
|
||||
|
||||
namespace mlspp::tls {
|
||||
|
||||
TLS_VARIANT_MAP(mlspp::LeafNodeSource,
|
||||
mlspp::Lifetime,
|
||||
key_package)
|
||||
TLS_VARIANT_MAP(mlspp::LeafNodeSource, mlspp::Empty, update)
|
||||
TLS_VARIANT_MAP(mlspp::LeafNodeSource,
|
||||
mlspp::ParentHash,
|
||||
commit)
|
||||
|
||||
} // namespace mlspp::tls
|
||||
228
DPP/mlspp/include/mls/credential.h
Executable file
228
DPP/mlspp/include/mls/credential.h
Executable file
@@ -0,0 +1,228 @@
|
||||
#pragma once
|
||||
|
||||
#include <mls/common.h>
|
||||
#include <mls/crypto.h>
|
||||
|
||||
namespace mlspp {
|
||||
|
||||
namespace hpke {
|
||||
struct UserInfoVC;
|
||||
}
|
||||
|
||||
// struct {
|
||||
// opaque identity<0..2^16-1>;
|
||||
// SignaturePublicKey public_key;
|
||||
// } BasicCredential;
|
||||
struct BasicCredential
|
||||
{
|
||||
BasicCredential() {}
|
||||
|
||||
BasicCredential(bytes identity_in)
|
||||
: identity(std::move(identity_in))
|
||||
{
|
||||
}
|
||||
|
||||
bytes identity;
|
||||
|
||||
TLS_SERIALIZABLE(identity)
|
||||
};
|
||||
|
||||
struct X509Credential
|
||||
{
|
||||
struct CertData
|
||||
{
|
||||
bytes data;
|
||||
|
||||
TLS_SERIALIZABLE(data)
|
||||
};
|
||||
|
||||
X509Credential() = default;
|
||||
explicit X509Credential(const std::vector<bytes>& der_chain_in);
|
||||
|
||||
SignatureScheme signature_scheme() const;
|
||||
SignaturePublicKey public_key() const;
|
||||
bool valid_for(const SignaturePublicKey& pub) const;
|
||||
|
||||
// TODO(rlb) This should be const or exposed via a method
|
||||
std::vector<CertData> der_chain;
|
||||
|
||||
private:
|
||||
SignaturePublicKey _public_key;
|
||||
SignatureScheme _signature_scheme;
|
||||
};
|
||||
|
||||
tls::ostream&
|
||||
operator<<(tls::ostream& str, const X509Credential& obj);
|
||||
|
||||
tls::istream&
|
||||
operator>>(tls::istream& str, X509Credential& obj);
|
||||
|
||||
struct UserInfoVCCredential
|
||||
{
|
||||
UserInfoVCCredential() = default;
|
||||
explicit UserInfoVCCredential(std::string userinfo_vc_jwt_in);
|
||||
|
||||
std::string userinfo_vc_jwt;
|
||||
|
||||
bool valid_for(const SignaturePublicKey& pub) const;
|
||||
bool valid_from(const PublicJWK& pub) const;
|
||||
|
||||
friend tls::ostream operator<<(tls::ostream& str,
|
||||
const UserInfoVCCredential& obj);
|
||||
friend tls::istream operator>>(tls::istream& str, UserInfoVCCredential& obj);
|
||||
friend bool operator==(const UserInfoVCCredential& lhs,
|
||||
const UserInfoVCCredential& rhs);
|
||||
friend bool operator!=(const UserInfoVCCredential& lhs,
|
||||
const UserInfoVCCredential& rhs);
|
||||
|
||||
private:
|
||||
std::shared_ptr<hpke::UserInfoVC> _vc;
|
||||
};
|
||||
|
||||
bool
|
||||
operator==(const X509Credential& lhs, const X509Credential& rhs);
|
||||
|
||||
enum struct CredentialType : uint16_t
|
||||
{
|
||||
reserved = 0,
|
||||
basic = 1,
|
||||
x509 = 2,
|
||||
|
||||
userinfo_vc_draft_00 = 0xFE00,
|
||||
multi_draft_00 = 0xFF00,
|
||||
|
||||
// GREASE values, included here mainly so that debugger output looks nice
|
||||
GREASE_0 = 0x0A0A,
|
||||
GREASE_1 = 0x1A1A,
|
||||
GREASE_2 = 0x2A2A,
|
||||
GREASE_3 = 0x3A3A,
|
||||
GREASE_4 = 0x4A4A,
|
||||
GREASE_5 = 0x5A5A,
|
||||
GREASE_6 = 0x6A6A,
|
||||
GREASE_7 = 0x7A7A,
|
||||
GREASE_8 = 0x8A8A,
|
||||
GREASE_9 = 0x9A9A,
|
||||
GREASE_A = 0xAAAA,
|
||||
GREASE_B = 0xBABA,
|
||||
GREASE_C = 0xCACA,
|
||||
GREASE_D = 0xDADA,
|
||||
GREASE_E = 0xEAEA,
|
||||
};
|
||||
|
||||
// struct {
|
||||
// Credential credential;
|
||||
// SignaturePublicKey credential_key;
|
||||
// opaque signature<V>;
|
||||
// } CredentialBinding
|
||||
//
|
||||
// struct {
|
||||
// CredentialBinding bindings<V>;
|
||||
// } MultiCredential;
|
||||
struct CredentialBinding;
|
||||
struct CredentialBindingInput;
|
||||
|
||||
struct MultiCredential
|
||||
{
|
||||
MultiCredential() = default;
|
||||
MultiCredential(const std::vector<CredentialBindingInput>& binding_inputs,
|
||||
const SignaturePublicKey& signature_key);
|
||||
|
||||
std::vector<CredentialBinding> bindings;
|
||||
|
||||
bool valid_for(const SignaturePublicKey& pub) const;
|
||||
|
||||
TLS_SERIALIZABLE(bindings)
|
||||
};
|
||||
|
||||
// struct {
|
||||
// CredentialType credential_type;
|
||||
// select (credential_type) {
|
||||
// case basic:
|
||||
// BasicCredential;
|
||||
//
|
||||
// case x509:
|
||||
// opaque cert_data<1..2^24-1>;
|
||||
// };
|
||||
// } Credential;
|
||||
struct Credential
|
||||
{
|
||||
Credential() = default;
|
||||
|
||||
CredentialType type() const;
|
||||
|
||||
template<typename T>
|
||||
const T& get() const
|
||||
{
|
||||
return var::get<T>(_cred);
|
||||
}
|
||||
|
||||
static Credential basic(const bytes& identity);
|
||||
static Credential x509(const std::vector<bytes>& der_chain);
|
||||
static Credential userinfo_vc(const std::string& userinfo_vc_jwt);
|
||||
static Credential multi(
|
||||
const std::vector<CredentialBindingInput>& binding_inputs,
|
||||
const SignaturePublicKey& signature_key);
|
||||
|
||||
bool valid_for(const SignaturePublicKey& pub) const;
|
||||
|
||||
TLS_SERIALIZABLE(_cred)
|
||||
TLS_TRAITS(tls::variant<CredentialType>)
|
||||
|
||||
private:
|
||||
using SpecificCredential = var::variant<BasicCredential,
|
||||
X509Credential,
|
||||
UserInfoVCCredential,
|
||||
MultiCredential>;
|
||||
|
||||
Credential(SpecificCredential specific);
|
||||
SpecificCredential _cred;
|
||||
};
|
||||
|
||||
// XXX(RLB): This struct needs to appear below Credential so that all types are
|
||||
// concrete at the appropriate points.
|
||||
struct CredentialBindingInput
|
||||
{
|
||||
CipherSuite cipher_suite;
|
||||
Credential credential;
|
||||
const SignaturePrivateKey& credential_priv;
|
||||
};
|
||||
|
||||
struct CredentialBinding
|
||||
{
|
||||
CipherSuite cipher_suite;
|
||||
Credential credential;
|
||||
SignaturePublicKey credential_key;
|
||||
bytes signature;
|
||||
|
||||
CredentialBinding() = default;
|
||||
CredentialBinding(CipherSuite suite_in,
|
||||
Credential credential_in,
|
||||
const SignaturePrivateKey& credential_priv,
|
||||
const SignaturePublicKey& signature_key);
|
||||
|
||||
bool valid_for(const SignaturePublicKey& signature_key) const;
|
||||
|
||||
TLS_SERIALIZABLE(cipher_suite, credential, credential_key, signature)
|
||||
|
||||
private:
|
||||
bytes to_be_signed(const SignaturePublicKey& signature_key) const;
|
||||
};
|
||||
|
||||
} // namespace mlspp
|
||||
|
||||
namespace mlspp::tls {
|
||||
|
||||
TLS_VARIANT_MAP(mlspp::CredentialType,
|
||||
mlspp::BasicCredential,
|
||||
basic)
|
||||
TLS_VARIANT_MAP(mlspp::CredentialType,
|
||||
mlspp::X509Credential,
|
||||
x509)
|
||||
TLS_VARIANT_MAP(mlspp::CredentialType,
|
||||
mlspp::UserInfoVCCredential,
|
||||
userinfo_vc_draft_00)
|
||||
TLS_VARIANT_MAP(mlspp::CredentialType,
|
||||
mlspp::MultiCredential,
|
||||
multi_draft_00)
|
||||
|
||||
} // namespace mlspp::tls
|
||||
266
DPP/mlspp/include/mls/crypto.h
Executable file
266
DPP/mlspp/include/mls/crypto.h
Executable file
@@ -0,0 +1,266 @@
|
||||
#pragma once
|
||||
|
||||
#include <hpke/digest.h>
|
||||
#include <hpke/hpke.h>
|
||||
#include <hpke/random.h>
|
||||
#include <hpke/signature.h>
|
||||
#include <mls/common.h>
|
||||
#include <tls/tls_syntax.h>
|
||||
#include <vector>
|
||||
|
||||
namespace mlspp {
|
||||
|
||||
/// Signature Code points, borrowed from RFC 8446
|
||||
enum struct SignatureScheme : uint16_t
|
||||
{
|
||||
ecdsa_secp256r1_sha256 = 0x0403,
|
||||
ecdsa_secp384r1_sha384 = 0x0805,
|
||||
ecdsa_secp521r1_sha512 = 0x0603,
|
||||
ed25519 = 0x0807,
|
||||
ed448 = 0x0808,
|
||||
rsa_pkcs1_sha256 = 0x0401,
|
||||
};
|
||||
|
||||
SignatureScheme
|
||||
tls_signature_scheme(hpke::Signature::ID id);
|
||||
|
||||
/// Cipher suites
|
||||
|
||||
struct KeyAndNonce
|
||||
{
|
||||
bytes key;
|
||||
bytes nonce;
|
||||
};
|
||||
|
||||
// opaque HashReference<V>;
|
||||
// HashReference KeyPackageRef;
|
||||
// HashReference ProposalRef;
|
||||
using HashReference = bytes;
|
||||
using KeyPackageRef = HashReference;
|
||||
using ProposalRef = HashReference;
|
||||
|
||||
struct CipherSuite
|
||||
{
|
||||
enum struct ID : uint16_t
|
||||
{
|
||||
unknown = 0x0000,
|
||||
X25519_AES128GCM_SHA256_Ed25519 = 0x0001,
|
||||
P256_AES128GCM_SHA256_P256 = 0x0002,
|
||||
X25519_CHACHA20POLY1305_SHA256_Ed25519 = 0x0003,
|
||||
X448_AES256GCM_SHA512_Ed448 = 0x0004,
|
||||
P521_AES256GCM_SHA512_P521 = 0x0005,
|
||||
X448_CHACHA20POLY1305_SHA512_Ed448 = 0x0006,
|
||||
P384_AES256GCM_SHA384_P384 = 0x0007,
|
||||
|
||||
// GREASE values, included here mainly so that debugger output looks nice
|
||||
GREASE_0 = 0x0A0A,
|
||||
GREASE_1 = 0x1A1A,
|
||||
GREASE_2 = 0x2A2A,
|
||||
GREASE_3 = 0x3A3A,
|
||||
GREASE_4 = 0x4A4A,
|
||||
GREASE_5 = 0x5A5A,
|
||||
GREASE_6 = 0x6A6A,
|
||||
GREASE_7 = 0x7A7A,
|
||||
GREASE_8 = 0x8A8A,
|
||||
GREASE_9 = 0x9A9A,
|
||||
GREASE_A = 0xAAAA,
|
||||
GREASE_B = 0xBABA,
|
||||
GREASE_C = 0xCACA,
|
||||
GREASE_D = 0xDADA,
|
||||
GREASE_E = 0xEAEA,
|
||||
};
|
||||
|
||||
CipherSuite();
|
||||
CipherSuite(ID id_in);
|
||||
|
||||
ID cipher_suite() const { return id; }
|
||||
SignatureScheme signature_scheme() const;
|
||||
|
||||
size_t secret_size() const { return get().digest.hash_size; }
|
||||
size_t key_size() const { return get().hpke.aead.key_size; }
|
||||
size_t nonce_size() const { return get().hpke.aead.nonce_size; }
|
||||
|
||||
bytes zero() const { return bytes(secret_size(), 0); }
|
||||
const hpke::HPKE& hpke() const { return get().hpke; }
|
||||
const hpke::Digest& digest() const { return get().digest; }
|
||||
const hpke::Signature& sig() const { return get().sig; }
|
||||
|
||||
bytes expand_with_label(const bytes& secret,
|
||||
const std::string& label,
|
||||
const bytes& context,
|
||||
size_t length) const;
|
||||
bytes derive_secret(const bytes& secret, const std::string& label) const;
|
||||
bytes derive_tree_secret(const bytes& secret,
|
||||
const std::string& label,
|
||||
uint32_t generation,
|
||||
size_t length) const;
|
||||
|
||||
template<typename T>
|
||||
bytes ref(const T& value) const
|
||||
{
|
||||
return raw_ref(reference_label<T>(), tls::marshal(value));
|
||||
}
|
||||
|
||||
bytes raw_ref(const bytes& label, const bytes& value) const
|
||||
{
|
||||
// RefHash(label, value) = Hash(RefHashInput)
|
||||
//
|
||||
// struct {
|
||||
// opaque label<V>;
|
||||
// opaque value<V>;
|
||||
// } RefHashInput;
|
||||
auto w = tls::ostream();
|
||||
w << label << value;
|
||||
return digest().hash(w.bytes());
|
||||
}
|
||||
|
||||
TLS_SERIALIZABLE(id)
|
||||
|
||||
private:
|
||||
ID id;
|
||||
|
||||
struct Ciphers
|
||||
{
|
||||
hpke::HPKE hpke;
|
||||
const hpke::Digest& digest;
|
||||
const hpke::Signature& sig;
|
||||
};
|
||||
|
||||
const Ciphers& get() const;
|
||||
|
||||
template<typename T>
|
||||
static const bytes& reference_label();
|
||||
};
|
||||
|
||||
#if WITH_BORINGSSL
|
||||
extern const std::array<CipherSuite::ID, 5> all_supported_suites;
|
||||
#else
|
||||
extern const std::array<CipherSuite::ID, 7> all_supported_suites;
|
||||
#endif
|
||||
|
||||
// Utilities
|
||||
using mlspp::hpke::random_bytes;
|
||||
|
||||
// HPKE Keys
|
||||
namespace encrypt_label {
|
||||
extern const std::string update_path_node;
|
||||
extern const std::string welcome;
|
||||
} // namespace encrypt_label
|
||||
|
||||
struct HPKECiphertext
|
||||
{
|
||||
bytes kem_output;
|
||||
bytes ciphertext;
|
||||
|
||||
TLS_SERIALIZABLE(kem_output, ciphertext)
|
||||
};
|
||||
|
||||
struct HPKEPublicKey
|
||||
{
|
||||
bytes data;
|
||||
|
||||
HPKECiphertext encrypt(CipherSuite suite,
|
||||
const std::string& label,
|
||||
const bytes& context,
|
||||
const bytes& pt) const;
|
||||
|
||||
std::tuple<bytes, bytes> do_export(CipherSuite suite,
|
||||
const bytes& info,
|
||||
const std::string& label,
|
||||
size_t size) const;
|
||||
|
||||
TLS_SERIALIZABLE(data)
|
||||
};
|
||||
|
||||
struct HPKEPrivateKey
|
||||
{
|
||||
static HPKEPrivateKey generate(CipherSuite suite);
|
||||
static HPKEPrivateKey parse(CipherSuite suite, const bytes& data);
|
||||
static HPKEPrivateKey derive(CipherSuite suite, const bytes& secret);
|
||||
|
||||
HPKEPrivateKey() = default;
|
||||
|
||||
bytes data;
|
||||
HPKEPublicKey public_key;
|
||||
|
||||
bytes decrypt(CipherSuite suite,
|
||||
const std::string& label,
|
||||
const bytes& context,
|
||||
const HPKECiphertext& ct) const;
|
||||
|
||||
bytes do_export(CipherSuite suite,
|
||||
const bytes& info,
|
||||
const bytes& kem_output,
|
||||
const std::string& label,
|
||||
size_t size) const;
|
||||
|
||||
void set_public_key(CipherSuite suite);
|
||||
|
||||
TLS_SERIALIZABLE(data)
|
||||
|
||||
private:
|
||||
HPKEPrivateKey(bytes priv_data, bytes pub_data);
|
||||
};
|
||||
|
||||
// Signature Keys
|
||||
namespace sign_label {
|
||||
extern const std::string mls_content;
|
||||
extern const std::string leaf_node;
|
||||
extern const std::string key_package;
|
||||
extern const std::string group_info;
|
||||
extern const std::string multi_credential;
|
||||
} // namespace sign_label
|
||||
|
||||
struct SignaturePublicKey
|
||||
{
|
||||
static SignaturePublicKey from_jwk(CipherSuite suite,
|
||||
const std::string& json_str);
|
||||
|
||||
bytes data;
|
||||
|
||||
bool verify(const CipherSuite& suite,
|
||||
const std::string& label,
|
||||
const bytes& message,
|
||||
const bytes& signature) const;
|
||||
|
||||
std::string to_jwk(CipherSuite suite) const;
|
||||
|
||||
TLS_SERIALIZABLE(data)
|
||||
};
|
||||
|
||||
struct PublicJWK
|
||||
{
|
||||
SignatureScheme signature_scheme;
|
||||
std::optional<std::string> key_id;
|
||||
SignaturePublicKey public_key;
|
||||
|
||||
static PublicJWK parse(const std::string& jwk_json);
|
||||
};
|
||||
|
||||
struct SignaturePrivateKey
|
||||
{
|
||||
static SignaturePrivateKey generate(CipherSuite suite);
|
||||
static SignaturePrivateKey parse(CipherSuite suite, const bytes& data);
|
||||
static SignaturePrivateKey derive(CipherSuite suite, const bytes& secret);
|
||||
static SignaturePrivateKey from_jwk(CipherSuite suite,
|
||||
const std::string& json_str);
|
||||
|
||||
SignaturePrivateKey() = default;
|
||||
|
||||
bytes data;
|
||||
SignaturePublicKey public_key;
|
||||
|
||||
bytes sign(const CipherSuite& suite,
|
||||
const std::string& label,
|
||||
const bytes& message) const;
|
||||
|
||||
void set_public_key(CipherSuite suite);
|
||||
std::string to_jwk(CipherSuite suite) const;
|
||||
|
||||
TLS_SERIALIZABLE(data)
|
||||
|
||||
private:
|
||||
SignaturePrivateKey(bytes priv_data, bytes pub_data);
|
||||
};
|
||||
|
||||
} // namespace mlspp
|
||||
205
DPP/mlspp/include/mls/key_schedule.h
Executable file
205
DPP/mlspp/include/mls/key_schedule.h
Executable file
@@ -0,0 +1,205 @@
|
||||
#pragma once
|
||||
|
||||
#include <map>
|
||||
#include <mls/common.h>
|
||||
#include <mls/crypto.h>
|
||||
#include <mls/messages.h>
|
||||
#include <mls/tree_math.h>
|
||||
|
||||
namespace mlspp {
|
||||
|
||||
struct HashRatchet
|
||||
{
|
||||
CipherSuite suite;
|
||||
bytes next_secret;
|
||||
uint32_t next_generation;
|
||||
std::map<uint32_t, KeyAndNonce> cache;
|
||||
|
||||
size_t key_size;
|
||||
size_t nonce_size;
|
||||
size_t secret_size;
|
||||
|
||||
// These defaults are necessary for use with containers
|
||||
HashRatchet() = default;
|
||||
HashRatchet(const HashRatchet& other) = default;
|
||||
HashRatchet(HashRatchet&& other) = default;
|
||||
HashRatchet& operator=(const HashRatchet& other) = default;
|
||||
HashRatchet& operator=(HashRatchet&& other) = default;
|
||||
|
||||
HashRatchet(CipherSuite suite_in, bytes base_secret_in);
|
||||
|
||||
std::tuple<uint32_t, KeyAndNonce> next();
|
||||
KeyAndNonce get(uint32_t generation);
|
||||
void erase(uint32_t generation);
|
||||
};
|
||||
|
||||
struct SecretTree
|
||||
{
|
||||
SecretTree() = default;
|
||||
SecretTree(CipherSuite suite_in,
|
||||
LeafCount group_size_in,
|
||||
bytes encryption_secret_in);
|
||||
|
||||
bool has_leaf(LeafIndex sender) { return sender < group_size; }
|
||||
|
||||
bytes get(LeafIndex sender);
|
||||
|
||||
private:
|
||||
CipherSuite suite;
|
||||
LeafCount group_size;
|
||||
NodeIndex root;
|
||||
std::map<NodeIndex, bytes> secrets;
|
||||
size_t secret_size;
|
||||
};
|
||||
|
||||
using ReuseGuard = std::array<uint8_t, 4>;
|
||||
|
||||
struct GroupKeySource
|
||||
{
|
||||
enum struct RatchetType
|
||||
{
|
||||
handshake,
|
||||
application,
|
||||
};
|
||||
|
||||
GroupKeySource() = default;
|
||||
GroupKeySource(CipherSuite suite_in,
|
||||
LeafCount group_size,
|
||||
bytes encryption_secret);
|
||||
|
||||
bool has_leaf(LeafIndex sender) { return secret_tree.has_leaf(sender); }
|
||||
|
||||
std::tuple<uint32_t, ReuseGuard, KeyAndNonce> next(ContentType content_type,
|
||||
LeafIndex sender);
|
||||
KeyAndNonce get(ContentType content_type,
|
||||
LeafIndex sender,
|
||||
uint32_t generation,
|
||||
ReuseGuard reuse_guard);
|
||||
void erase(ContentType type, LeafIndex sender, uint32_t generation);
|
||||
|
||||
private:
|
||||
CipherSuite suite;
|
||||
SecretTree secret_tree;
|
||||
|
||||
using Key = std::tuple<RatchetType, LeafIndex>;
|
||||
std::map<Key, HashRatchet> chains;
|
||||
|
||||
HashRatchet& chain(RatchetType type, LeafIndex sender);
|
||||
HashRatchet& chain(ContentType type, LeafIndex sender);
|
||||
|
||||
static const std::array<RatchetType, 2> all_ratchet_types;
|
||||
};
|
||||
|
||||
struct KeyScheduleEpoch
|
||||
{
|
||||
private:
|
||||
CipherSuite suite;
|
||||
|
||||
public:
|
||||
bytes joiner_secret;
|
||||
bytes epoch_secret;
|
||||
|
||||
bytes sender_data_secret;
|
||||
bytes encryption_secret;
|
||||
bytes exporter_secret;
|
||||
bytes epoch_authenticator;
|
||||
bytes external_secret;
|
||||
bytes confirmation_key;
|
||||
bytes membership_key;
|
||||
bytes resumption_psk;
|
||||
bytes init_secret;
|
||||
|
||||
HPKEPrivateKey external_priv;
|
||||
|
||||
KeyScheduleEpoch() = default;
|
||||
|
||||
// Full initializer, used by invited joiner
|
||||
static KeyScheduleEpoch joiner(CipherSuite suite_in,
|
||||
const bytes& joiner_secret,
|
||||
const std::vector<PSKWithSecret>& psks,
|
||||
const bytes& context);
|
||||
|
||||
// Ciphersuite-only initializer, used by external joiner
|
||||
KeyScheduleEpoch(CipherSuite suite_in);
|
||||
|
||||
// Initial epoch
|
||||
KeyScheduleEpoch(CipherSuite suite_in,
|
||||
const bytes& init_secret,
|
||||
const bytes& context);
|
||||
|
||||
static std::tuple<bytes, bytes> external_init(
|
||||
CipherSuite suite,
|
||||
const HPKEPublicKey& external_pub);
|
||||
bytes receive_external_init(const bytes& kem_output) const;
|
||||
|
||||
KeyScheduleEpoch next(const bytes& commit_secret,
|
||||
const std::vector<PSKWithSecret>& psks,
|
||||
const std::optional<bytes>& force_init_secret,
|
||||
const bytes& context) const;
|
||||
|
||||
GroupKeySource encryption_keys(LeafCount size) const;
|
||||
bytes confirmation_tag(const bytes& confirmed_transcript_hash) const;
|
||||
bytes do_export(const std::string& label,
|
||||
const bytes& context,
|
||||
size_t size) const;
|
||||
PSKWithSecret resumption_psk_w_secret(ResumptionPSKUsage usage,
|
||||
const bytes& group_id,
|
||||
epoch_t epoch);
|
||||
|
||||
static bytes make_psk_secret(CipherSuite suite,
|
||||
const std::vector<PSKWithSecret>& psks);
|
||||
static bytes welcome_secret(CipherSuite suite,
|
||||
const bytes& joiner_secret,
|
||||
const std::vector<PSKWithSecret>& psks);
|
||||
static KeyAndNonce sender_data_keys(CipherSuite suite,
|
||||
const bytes& sender_data_secret,
|
||||
const bytes& ciphertext);
|
||||
|
||||
// TODO(RLB) make these methods private, but accessible to test vectors
|
||||
KeyScheduleEpoch(CipherSuite suite_in,
|
||||
const bytes& init_secret,
|
||||
const bytes& commit_secret,
|
||||
const bytes& psk_secret,
|
||||
const bytes& context);
|
||||
KeyScheduleEpoch next_raw(const bytes& commit_secret,
|
||||
const bytes& psk_secret,
|
||||
const std::optional<bytes>& force_init_secret,
|
||||
const bytes& context) const;
|
||||
static bytes welcome_secret_raw(CipherSuite suite,
|
||||
const bytes& joiner_secret,
|
||||
const bytes& psk_secret);
|
||||
|
||||
private:
|
||||
KeyScheduleEpoch(CipherSuite suite_in,
|
||||
const bytes& joiner_secret,
|
||||
const bytes& psk_secret,
|
||||
const bytes& context);
|
||||
};
|
||||
|
||||
bool
|
||||
operator==(const KeyScheduleEpoch& lhs, const KeyScheduleEpoch& rhs);
|
||||
|
||||
struct TranscriptHash
|
||||
{
|
||||
CipherSuite suite;
|
||||
bytes confirmed;
|
||||
bytes interim;
|
||||
|
||||
// For a new group
|
||||
TranscriptHash(CipherSuite suite_in);
|
||||
|
||||
// For joining a group
|
||||
TranscriptHash(CipherSuite suite_in,
|
||||
bytes confirmed_in,
|
||||
const bytes& confirmation_tag);
|
||||
|
||||
void update(const AuthenticatedContent& content_auth);
|
||||
void update_confirmed(const AuthenticatedContent& content_auth);
|
||||
void update_interim(const bytes& confirmation_tag);
|
||||
void update_interim(const AuthenticatedContent& content_auth);
|
||||
};
|
||||
|
||||
bool
|
||||
operator==(const TranscriptHash& lhs, const TranscriptHash& rhs);
|
||||
|
||||
} // namespace mlspp
|
||||
752
DPP/mlspp/include/mls/messages.h
Executable file
752
DPP/mlspp/include/mls/messages.h
Executable file
@@ -0,0 +1,752 @@
|
||||
#pragma once
|
||||
|
||||
#include "mls/common.h"
|
||||
#include "mls/core_types.h"
|
||||
#include "mls/credential.h"
|
||||
#include "mls/crypto.h"
|
||||
#include "mls/treekem.h"
|
||||
#include <optional>
|
||||
#include <tls/tls_syntax.h>
|
||||
|
||||
namespace mlspp {
|
||||
|
||||
struct ExternalPubExtension
|
||||
{
|
||||
HPKEPublicKey external_pub;
|
||||
|
||||
static const uint16_t type;
|
||||
TLS_SERIALIZABLE(external_pub)
|
||||
};
|
||||
|
||||
struct RatchetTreeExtension
|
||||
{
|
||||
TreeKEMPublicKey tree;
|
||||
|
||||
static const uint16_t type;
|
||||
TLS_SERIALIZABLE(tree)
|
||||
};
|
||||
|
||||
struct ExternalSender
|
||||
{
|
||||
SignaturePublicKey signature_key;
|
||||
Credential credential;
|
||||
|
||||
TLS_SERIALIZABLE(signature_key, credential);
|
||||
};
|
||||
|
||||
struct ExternalSendersExtension
|
||||
{
|
||||
std::vector<ExternalSender> senders;
|
||||
|
||||
static const uint16_t type;
|
||||
TLS_SERIALIZABLE(senders);
|
||||
};
|
||||
|
||||
struct SFrameParameters
|
||||
{
|
||||
uint16_t cipher_suite;
|
||||
uint8_t epoch_bits;
|
||||
|
||||
static const uint16_t type;
|
||||
TLS_SERIALIZABLE(cipher_suite, epoch_bits)
|
||||
};
|
||||
|
||||
struct SFrameCapabilities
|
||||
{
|
||||
std::vector<uint16_t> cipher_suites;
|
||||
|
||||
bool compatible(const SFrameParameters& params) const;
|
||||
|
||||
static const uint16_t type;
|
||||
TLS_SERIALIZABLE(cipher_suites)
|
||||
};
|
||||
|
||||
///
|
||||
/// PSKs
|
||||
///
|
||||
enum struct PSKType : uint8_t
|
||||
{
|
||||
reserved = 0,
|
||||
external = 1,
|
||||
resumption = 2,
|
||||
};
|
||||
|
||||
struct ExternalPSK
|
||||
{
|
||||
bytes psk_id;
|
||||
TLS_SERIALIZABLE(psk_id)
|
||||
};
|
||||
|
||||
enum struct ResumptionPSKUsage : uint8_t
|
||||
{
|
||||
reserved = 0,
|
||||
application = 1,
|
||||
reinit = 2,
|
||||
branch = 3,
|
||||
};
|
||||
|
||||
struct ResumptionPSK
|
||||
{
|
||||
ResumptionPSKUsage usage;
|
||||
bytes psk_group_id;
|
||||
epoch_t psk_epoch;
|
||||
TLS_SERIALIZABLE(usage, psk_group_id, psk_epoch)
|
||||
};
|
||||
|
||||
struct PreSharedKeyID
|
||||
{
|
||||
var::variant<ExternalPSK, ResumptionPSK> content;
|
||||
bytes psk_nonce;
|
||||
TLS_SERIALIZABLE(content, psk_nonce)
|
||||
TLS_TRAITS(tls::variant<PSKType>, tls::pass)
|
||||
};
|
||||
|
||||
struct PreSharedKeys
|
||||
{
|
||||
std::vector<PreSharedKeyID> psks;
|
||||
TLS_SERIALIZABLE(psks)
|
||||
};
|
||||
|
||||
struct PSKWithSecret
|
||||
{
|
||||
PreSharedKeyID id;
|
||||
bytes secret;
|
||||
};
|
||||
|
||||
// struct {
|
||||
// ProtocolVersion version = mls10;
|
||||
// CipherSuite cipher_suite;
|
||||
// opaque group_id<V>;
|
||||
// uint64 epoch;
|
||||
// opaque tree_hash<V>;
|
||||
// opaque confirmed_transcript_hash<V>;
|
||||
// Extension extensions<V>;
|
||||
// } GroupContext;
|
||||
struct GroupContext
|
||||
{
|
||||
ProtocolVersion version{ ProtocolVersion::mls10 };
|
||||
CipherSuite cipher_suite;
|
||||
bytes group_id;
|
||||
epoch_t epoch;
|
||||
bytes tree_hash;
|
||||
bytes confirmed_transcript_hash;
|
||||
ExtensionList extensions;
|
||||
|
||||
GroupContext() = default;
|
||||
GroupContext(CipherSuite cipher_suite_in,
|
||||
bytes group_id_in,
|
||||
epoch_t epoch_in,
|
||||
bytes tree_hash_in,
|
||||
bytes confirmed_transcript_hash_in,
|
||||
ExtensionList extensions_in);
|
||||
|
||||
TLS_SERIALIZABLE(version,
|
||||
cipher_suite,
|
||||
group_id,
|
||||
epoch,
|
||||
tree_hash,
|
||||
confirmed_transcript_hash,
|
||||
extensions)
|
||||
};
|
||||
|
||||
// struct {
|
||||
// GroupContext group_context;
|
||||
// Extension extensions<V>;
|
||||
// MAC confirmation_tag;
|
||||
// uint32 signer;
|
||||
// // SignWithLabel(., "GroupInfoTBS", GroupInfoTBS)
|
||||
// opaque signature<V>;
|
||||
// } GroupInfo;
|
||||
struct GroupInfo
|
||||
{
|
||||
GroupContext group_context;
|
||||
ExtensionList extensions;
|
||||
bytes confirmation_tag;
|
||||
LeafIndex signer;
|
||||
bytes signature;
|
||||
|
||||
GroupInfo() = default;
|
||||
GroupInfo(GroupContext group_context_in,
|
||||
ExtensionList extensions_in,
|
||||
bytes confirmation_tag_in);
|
||||
|
||||
bytes to_be_signed() const;
|
||||
void sign(const TreeKEMPublicKey& tree,
|
||||
LeafIndex signer_index,
|
||||
const SignaturePrivateKey& priv);
|
||||
bool verify(const TreeKEMPublicKey& tree) const;
|
||||
|
||||
// These methods exist only to simplify unit testing
|
||||
void sign(LeafIndex signer_index, const SignaturePrivateKey& priv);
|
||||
bool verify(const SignaturePublicKey& pub) const;
|
||||
|
||||
TLS_SERIALIZABLE(group_context,
|
||||
extensions,
|
||||
confirmation_tag,
|
||||
signer,
|
||||
signature)
|
||||
};
|
||||
|
||||
// struct {
|
||||
// opaque joiner_secret<1..255>;
|
||||
// optional<PathSecret> path_secret;
|
||||
// PreSharedKeys psks;
|
||||
// } GroupSecrets;
|
||||
struct GroupSecrets
|
||||
{
|
||||
struct PathSecret
|
||||
{
|
||||
bytes secret;
|
||||
|
||||
TLS_SERIALIZABLE(secret)
|
||||
};
|
||||
|
||||
bytes joiner_secret;
|
||||
std::optional<PathSecret> path_secret;
|
||||
PreSharedKeys psks;
|
||||
|
||||
TLS_SERIALIZABLE(joiner_secret, path_secret, psks)
|
||||
};
|
||||
|
||||
// struct {
|
||||
// opaque key_package_hash<1..255>;
|
||||
// HPKECiphertext encrypted_group_secrets;
|
||||
// } EncryptedGroupSecrets;
|
||||
struct EncryptedGroupSecrets
|
||||
{
|
||||
KeyPackageRef new_member;
|
||||
HPKECiphertext encrypted_group_secrets;
|
||||
|
||||
TLS_SERIALIZABLE(new_member, encrypted_group_secrets)
|
||||
};
|
||||
|
||||
// struct {
|
||||
// ProtocolVersion version = mls10;
|
||||
// CipherSuite cipher_suite;
|
||||
// EncryptedGroupSecrets group_secretss<1..2^32-1>;
|
||||
// opaque encrypted_group_info<1..2^32-1>;
|
||||
// } Welcome;
|
||||
struct Welcome
|
||||
{
|
||||
CipherSuite cipher_suite;
|
||||
std::vector<EncryptedGroupSecrets> secrets;
|
||||
bytes encrypted_group_info;
|
||||
|
||||
Welcome();
|
||||
Welcome(CipherSuite suite,
|
||||
const bytes& joiner_secret,
|
||||
const std::vector<PSKWithSecret>& psks,
|
||||
const GroupInfo& group_info);
|
||||
|
||||
void encrypt(const KeyPackage& kp, const std::optional<bytes>& path_secret);
|
||||
std::optional<int> find(const KeyPackage& kp) const;
|
||||
GroupSecrets decrypt_secrets(int kp_index,
|
||||
const HPKEPrivateKey& init_priv) const;
|
||||
GroupInfo decrypt(const bytes& joiner_secret,
|
||||
const std::vector<PSKWithSecret>& psks) const;
|
||||
|
||||
TLS_SERIALIZABLE(cipher_suite, secrets, encrypted_group_info)
|
||||
|
||||
private:
|
||||
bytes _joiner_secret;
|
||||
PreSharedKeys _psks;
|
||||
static KeyAndNonce group_info_key_nonce(
|
||||
CipherSuite suite,
|
||||
const bytes& joiner_secret,
|
||||
const std::vector<PSKWithSecret>& psks);
|
||||
};
|
||||
|
||||
///
|
||||
/// Proposals & Commit
|
||||
///
|
||||
|
||||
// Add
|
||||
struct Add
|
||||
{
|
||||
KeyPackage key_package;
|
||||
TLS_SERIALIZABLE(key_package)
|
||||
};
|
||||
|
||||
// Update
|
||||
struct Update
|
||||
{
|
||||
LeafNode leaf_node;
|
||||
TLS_SERIALIZABLE(leaf_node)
|
||||
};
|
||||
|
||||
// Remove
|
||||
struct Remove
|
||||
{
|
||||
LeafIndex removed;
|
||||
TLS_SERIALIZABLE(removed)
|
||||
};
|
||||
|
||||
// PreSharedKey
|
||||
struct PreSharedKey
|
||||
{
|
||||
PreSharedKeyID psk;
|
||||
TLS_SERIALIZABLE(psk)
|
||||
};
|
||||
|
||||
// ReInit
|
||||
struct ReInit
|
||||
{
|
||||
bytes group_id;
|
||||
ProtocolVersion version;
|
||||
CipherSuite cipher_suite;
|
||||
ExtensionList extensions;
|
||||
|
||||
TLS_SERIALIZABLE(group_id, version, cipher_suite, extensions)
|
||||
};
|
||||
|
||||
// ExternalInit
|
||||
struct ExternalInit
|
||||
{
|
||||
bytes kem_output;
|
||||
TLS_SERIALIZABLE(kem_output)
|
||||
};
|
||||
|
||||
// GroupContextExtensions
|
||||
struct GroupContextExtensions
|
||||
{
|
||||
ExtensionList group_context_extensions;
|
||||
TLS_SERIALIZABLE(group_context_extensions)
|
||||
};
|
||||
|
||||
struct ProposalType;
|
||||
|
||||
struct Proposal
|
||||
{
|
||||
using Type = uint16_t;
|
||||
|
||||
var::variant<Add,
|
||||
Update,
|
||||
Remove,
|
||||
PreSharedKey,
|
||||
ReInit,
|
||||
ExternalInit,
|
||||
GroupContextExtensions>
|
||||
content;
|
||||
|
||||
Type proposal_type() const;
|
||||
|
||||
TLS_SERIALIZABLE(content)
|
||||
TLS_TRAITS(tls::variant<ProposalType>)
|
||||
};
|
||||
|
||||
struct ProposalType
|
||||
{
|
||||
static constexpr Proposal::Type invalid = 0;
|
||||
static constexpr Proposal::Type add = 1;
|
||||
static constexpr Proposal::Type update = 2;
|
||||
static constexpr Proposal::Type remove = 3;
|
||||
static constexpr Proposal::Type psk = 4;
|
||||
static constexpr Proposal::Type reinit = 5;
|
||||
static constexpr Proposal::Type external_init = 6;
|
||||
static constexpr Proposal::Type group_context_extensions = 7;
|
||||
|
||||
constexpr ProposalType()
|
||||
: val(invalid)
|
||||
{
|
||||
}
|
||||
|
||||
constexpr ProposalType(Proposal::Type pt)
|
||||
: val(pt)
|
||||
{
|
||||
}
|
||||
|
||||
Proposal::Type val;
|
||||
TLS_SERIALIZABLE(val)
|
||||
};
|
||||
|
||||
enum struct ProposalOrRefType : uint8_t
|
||||
{
|
||||
reserved = 0,
|
||||
value = 1,
|
||||
reference = 2,
|
||||
};
|
||||
|
||||
struct ProposalOrRef
|
||||
{
|
||||
var::variant<Proposal, ProposalRef> content;
|
||||
|
||||
TLS_SERIALIZABLE(content)
|
||||
TLS_TRAITS(tls::variant<ProposalOrRefType>)
|
||||
};
|
||||
|
||||
// struct {
|
||||
// ProposalOrRef proposals<0..2^32-1>;
|
||||
// optional<UpdatePath> path;
|
||||
// } Commit;
|
||||
struct Commit
|
||||
{
|
||||
std::vector<ProposalOrRef> proposals;
|
||||
std::optional<UpdatePath> path;
|
||||
|
||||
// Validate that the commit is acceptable as an external commit, and if so,
|
||||
// produce the public key from the ExternalInit proposal
|
||||
std::optional<bytes> valid_external() const;
|
||||
|
||||
TLS_SERIALIZABLE(proposals, path)
|
||||
};
|
||||
|
||||
// struct {
|
||||
// opaque group_id<0..255>;
|
||||
// uint32 epoch;
|
||||
// uint32 sender;
|
||||
// ContentType content_type;
|
||||
//
|
||||
// select (PublicMessage.content_type) {
|
||||
// case handshake:
|
||||
// GroupOperation operation;
|
||||
// opaque confirmation<0..255>;
|
||||
//
|
||||
// case application:
|
||||
// opaque application_data<0..2^32-1>;
|
||||
// }
|
||||
//
|
||||
// opaque signature<0..2^16-1>;
|
||||
// } PublicMessage;
|
||||
struct ApplicationData
|
||||
{
|
||||
bytes data;
|
||||
TLS_SERIALIZABLE(data)
|
||||
};
|
||||
|
||||
struct GroupContext;
|
||||
|
||||
enum struct WireFormat : uint16_t
|
||||
{
|
||||
reserved = 0,
|
||||
mls_public_message = 1,
|
||||
mls_private_message = 2,
|
||||
mls_welcome = 3,
|
||||
mls_group_info = 4,
|
||||
mls_key_package = 5,
|
||||
};
|
||||
|
||||
enum struct ContentType : uint8_t
|
||||
{
|
||||
invalid = 0,
|
||||
application = 1,
|
||||
proposal = 2,
|
||||
commit = 3,
|
||||
};
|
||||
|
||||
enum struct SenderType : uint8_t
|
||||
{
|
||||
invalid = 0,
|
||||
member = 1,
|
||||
external = 2,
|
||||
new_member_proposal = 3,
|
||||
new_member_commit = 4,
|
||||
};
|
||||
|
||||
struct MemberSender
|
||||
{
|
||||
LeafIndex sender;
|
||||
TLS_SERIALIZABLE(sender);
|
||||
};
|
||||
|
||||
struct ExternalSenderIndex
|
||||
{
|
||||
uint32_t sender_index;
|
||||
TLS_SERIALIZABLE(sender_index)
|
||||
};
|
||||
|
||||
struct NewMemberProposalSender
|
||||
{
|
||||
TLS_SERIALIZABLE()
|
||||
};
|
||||
|
||||
struct NewMemberCommitSender
|
||||
{
|
||||
TLS_SERIALIZABLE()
|
||||
};
|
||||
|
||||
struct Sender
|
||||
{
|
||||
var::variant<MemberSender,
|
||||
ExternalSenderIndex,
|
||||
NewMemberProposalSender,
|
||||
NewMemberCommitSender>
|
||||
sender;
|
||||
|
||||
SenderType sender_type() const;
|
||||
|
||||
TLS_SERIALIZABLE(sender)
|
||||
TLS_TRAITS(tls::variant<SenderType>)
|
||||
};
|
||||
|
||||
///
|
||||
/// MLSMessage and friends
|
||||
///
|
||||
struct GroupKeySource;
|
||||
|
||||
struct GroupContent
|
||||
{
|
||||
using RawContent = var::variant<ApplicationData, Proposal, Commit>;
|
||||
|
||||
bytes group_id;
|
||||
epoch_t epoch;
|
||||
Sender sender;
|
||||
bytes authenticated_data;
|
||||
RawContent content;
|
||||
|
||||
GroupContent() = default;
|
||||
GroupContent(bytes group_id_in,
|
||||
epoch_t epoch_in,
|
||||
Sender sender_in,
|
||||
bytes authenticated_data_in,
|
||||
RawContent content_in);
|
||||
GroupContent(bytes group_id_in,
|
||||
epoch_t epoch_in,
|
||||
Sender sender_in,
|
||||
bytes authenticated_data_in,
|
||||
ContentType content_type);
|
||||
|
||||
ContentType content_type() const;
|
||||
|
||||
TLS_SERIALIZABLE(group_id, epoch, sender, authenticated_data, content)
|
||||
TLS_TRAITS(tls::pass,
|
||||
tls::pass,
|
||||
tls::pass,
|
||||
tls::pass,
|
||||
tls::variant<ContentType>)
|
||||
};
|
||||
|
||||
struct GroupContentAuthData
|
||||
{
|
||||
ContentType content_type = ContentType::invalid;
|
||||
bytes signature;
|
||||
std::optional<bytes> confirmation_tag;
|
||||
|
||||
friend tls::ostream& operator<<(tls::ostream& str,
|
||||
const GroupContentAuthData& obj);
|
||||
friend tls::istream& operator>>(tls::istream& str, GroupContentAuthData& obj);
|
||||
friend bool operator==(const GroupContentAuthData& lhs,
|
||||
const GroupContentAuthData& rhs);
|
||||
};
|
||||
|
||||
struct AuthenticatedContent
|
||||
{
|
||||
WireFormat wire_format;
|
||||
GroupContent content;
|
||||
GroupContentAuthData auth;
|
||||
|
||||
AuthenticatedContent() = default;
|
||||
|
||||
static AuthenticatedContent sign(WireFormat wire_format,
|
||||
GroupContent content,
|
||||
CipherSuite suite,
|
||||
const SignaturePrivateKey& sig_priv,
|
||||
const std::optional<GroupContext>& context);
|
||||
bool verify(CipherSuite suite,
|
||||
const SignaturePublicKey& sig_pub,
|
||||
const std::optional<GroupContext>& context) const;
|
||||
|
||||
bytes confirmed_transcript_hash_input() const;
|
||||
bytes interim_transcript_hash_input() const;
|
||||
|
||||
void set_confirmation_tag(const bytes& confirmation_tag);
|
||||
bool check_confirmation_tag(const bytes& confirmation_tag) const;
|
||||
|
||||
friend tls::ostream& operator<<(tls::ostream& str,
|
||||
const AuthenticatedContent& obj);
|
||||
friend tls::istream& operator>>(tls::istream& str, AuthenticatedContent& obj);
|
||||
friend bool operator==(const AuthenticatedContent& lhs,
|
||||
const AuthenticatedContent& rhs);
|
||||
|
||||
private:
|
||||
AuthenticatedContent(WireFormat wire_format_in, GroupContent content_in);
|
||||
AuthenticatedContent(WireFormat wire_format_in,
|
||||
GroupContent content_in,
|
||||
GroupContentAuthData auth_in);
|
||||
|
||||
bytes to_be_signed(const std::optional<GroupContext>& context) const;
|
||||
|
||||
friend struct PublicMessage;
|
||||
friend struct PrivateMessage;
|
||||
};
|
||||
|
||||
struct ValidatedContent
|
||||
{
|
||||
const AuthenticatedContent& authenticated_content() const;
|
||||
|
||||
friend bool operator==(const ValidatedContent& lhs,
|
||||
const ValidatedContent& rhs);
|
||||
|
||||
private:
|
||||
AuthenticatedContent content_auth;
|
||||
|
||||
ValidatedContent(AuthenticatedContent content_auth_in);
|
||||
|
||||
friend struct PublicMessage;
|
||||
friend struct PrivateMessage;
|
||||
friend class State;
|
||||
};
|
||||
|
||||
struct PublicMessage
|
||||
{
|
||||
PublicMessage() = default;
|
||||
|
||||
bytes get_group_id() const { return content.group_id; }
|
||||
epoch_t get_epoch() const { return content.epoch; }
|
||||
|
||||
static PublicMessage protect(AuthenticatedContent content_auth,
|
||||
CipherSuite suite,
|
||||
const std::optional<bytes>& membership_key,
|
||||
const std::optional<GroupContext>& context);
|
||||
std::optional<ValidatedContent> unprotect(
|
||||
CipherSuite suite,
|
||||
const std::optional<bytes>& membership_key,
|
||||
const std::optional<GroupContext>& context) const;
|
||||
|
||||
bool contains(const AuthenticatedContent& content_auth) const;
|
||||
|
||||
// TODO(RLB) Make this private and expose only to tests
|
||||
AuthenticatedContent authenticated_content() const;
|
||||
|
||||
friend tls::ostream& operator<<(tls::ostream& str, const PublicMessage& obj);
|
||||
friend tls::istream& operator>>(tls::istream& str, PublicMessage& obj);
|
||||
friend bool operator==(const PublicMessage& lhs, const PublicMessage& rhs);
|
||||
friend bool operator!=(const PublicMessage& lhs, const PublicMessage& rhs);
|
||||
|
||||
private:
|
||||
GroupContent content;
|
||||
GroupContentAuthData auth;
|
||||
std::optional<bytes> membership_tag;
|
||||
|
||||
PublicMessage(AuthenticatedContent content_auth);
|
||||
|
||||
bytes membership_mac(CipherSuite suite,
|
||||
const bytes& membership_key,
|
||||
const std::optional<GroupContext>& context) const;
|
||||
};
|
||||
|
||||
struct PrivateMessage
|
||||
{
|
||||
PrivateMessage() = default;
|
||||
|
||||
bytes get_group_id() const { return group_id; }
|
||||
epoch_t get_epoch() const { return epoch; }
|
||||
|
||||
static PrivateMessage protect(AuthenticatedContent content_auth,
|
||||
CipherSuite suite,
|
||||
GroupKeySource& keys,
|
||||
const bytes& sender_data_secret,
|
||||
size_t padding_size);
|
||||
std::optional<ValidatedContent> unprotect(
|
||||
CipherSuite suite,
|
||||
GroupKeySource& keys,
|
||||
const bytes& sender_data_secret) const;
|
||||
|
||||
TLS_SERIALIZABLE(group_id,
|
||||
epoch,
|
||||
content_type,
|
||||
authenticated_data,
|
||||
encrypted_sender_data,
|
||||
ciphertext)
|
||||
|
||||
private:
|
||||
bytes group_id;
|
||||
epoch_t epoch;
|
||||
ContentType content_type;
|
||||
bytes authenticated_data;
|
||||
bytes encrypted_sender_data;
|
||||
bytes ciphertext;
|
||||
|
||||
PrivateMessage(GroupContent content,
|
||||
bytes encrypted_sender_data_in,
|
||||
bytes ciphertext_in);
|
||||
};
|
||||
|
||||
struct MLSMessage
|
||||
{
|
||||
ProtocolVersion version = ProtocolVersion::mls10;
|
||||
var::variant<PublicMessage, PrivateMessage, Welcome, GroupInfo, KeyPackage>
|
||||
message;
|
||||
|
||||
bytes group_id() const;
|
||||
epoch_t epoch() const;
|
||||
WireFormat wire_format() const;
|
||||
|
||||
MLSMessage() = default;
|
||||
MLSMessage(PublicMessage public_message);
|
||||
MLSMessage(PrivateMessage private_message);
|
||||
MLSMessage(Welcome welcome);
|
||||
MLSMessage(GroupInfo group_info);
|
||||
MLSMessage(KeyPackage key_package);
|
||||
|
||||
TLS_SERIALIZABLE(version, message)
|
||||
TLS_TRAITS(tls::pass, tls::variant<WireFormat>)
|
||||
};
|
||||
|
||||
MLSMessage
|
||||
external_proposal(CipherSuite suite,
|
||||
const bytes& group_id,
|
||||
epoch_t epoch,
|
||||
const Proposal& proposal,
|
||||
uint32_t signer_index,
|
||||
const SignaturePrivateKey& sig_priv);
|
||||
|
||||
} // namespace mlspp
|
||||
|
||||
namespace mlspp::tls {
|
||||
|
||||
TLS_VARIANT_MAP(mlspp::PSKType, mlspp::ExternalPSK, external)
|
||||
TLS_VARIANT_MAP(mlspp::PSKType,
|
||||
mlspp::ResumptionPSK,
|
||||
resumption)
|
||||
|
||||
TLS_VARIANT_MAP(mlspp::ProposalOrRefType,
|
||||
mlspp::Proposal,
|
||||
value)
|
||||
TLS_VARIANT_MAP(mlspp::ProposalOrRefType,
|
||||
mlspp::ProposalRef,
|
||||
reference)
|
||||
|
||||
TLS_VARIANT_MAP(mlspp::ProposalType, mlspp::Add, add)
|
||||
TLS_VARIANT_MAP(mlspp::ProposalType, mlspp::Update, update)
|
||||
TLS_VARIANT_MAP(mlspp::ProposalType, mlspp::Remove, remove)
|
||||
TLS_VARIANT_MAP(mlspp::ProposalType, mlspp::PreSharedKey, psk)
|
||||
TLS_VARIANT_MAP(mlspp::ProposalType, mlspp::ReInit, reinit)
|
||||
TLS_VARIANT_MAP(mlspp::ProposalType,
|
||||
mlspp::ExternalInit,
|
||||
external_init)
|
||||
TLS_VARIANT_MAP(mlspp::ProposalType,
|
||||
mlspp::GroupContextExtensions,
|
||||
group_context_extensions)
|
||||
|
||||
TLS_VARIANT_MAP(mlspp::ContentType,
|
||||
mlspp::ApplicationData,
|
||||
application)
|
||||
TLS_VARIANT_MAP(mlspp::ContentType, mlspp::Proposal, proposal)
|
||||
TLS_VARIANT_MAP(mlspp::ContentType, mlspp::Commit, commit)
|
||||
|
||||
TLS_VARIANT_MAP(mlspp::SenderType, mlspp::MemberSender, member)
|
||||
TLS_VARIANT_MAP(mlspp::SenderType,
|
||||
mlspp::ExternalSenderIndex,
|
||||
external)
|
||||
TLS_VARIANT_MAP(mlspp::SenderType,
|
||||
mlspp::NewMemberProposalSender,
|
||||
new_member_proposal)
|
||||
TLS_VARIANT_MAP(mlspp::SenderType,
|
||||
mlspp::NewMemberCommitSender,
|
||||
new_member_commit)
|
||||
|
||||
TLS_VARIANT_MAP(mlspp::WireFormat,
|
||||
mlspp::PublicMessage,
|
||||
mls_public_message)
|
||||
TLS_VARIANT_MAP(mlspp::WireFormat,
|
||||
mlspp::PrivateMessage,
|
||||
mls_private_message)
|
||||
TLS_VARIANT_MAP(mlspp::WireFormat, mlspp::Welcome, mls_welcome)
|
||||
TLS_VARIANT_MAP(mlspp::WireFormat,
|
||||
mlspp::GroupInfo,
|
||||
mls_group_info)
|
||||
TLS_VARIANT_MAP(mlspp::WireFormat,
|
||||
mlspp::KeyPackage,
|
||||
mls_key_package)
|
||||
|
||||
} // namespace mlspp::tls
|
||||
98
DPP/mlspp/include/mls/session.h
Executable file
98
DPP/mlspp/include/mls/session.h
Executable file
@@ -0,0 +1,98 @@
|
||||
#pragma once
|
||||
|
||||
#include <mls/common.h>
|
||||
#include <mls/core_types.h>
|
||||
#include <mls/credential.h>
|
||||
#include <mls/crypto.h>
|
||||
#include <mls/state.h>
|
||||
|
||||
namespace mlspp {
|
||||
|
||||
class PendingJoin;
|
||||
class Session;
|
||||
|
||||
class Client
|
||||
{
|
||||
public:
|
||||
Client(CipherSuite suite_in,
|
||||
SignaturePrivateKey sig_priv_in,
|
||||
Credential cred_in);
|
||||
|
||||
Session begin_session(const bytes& group_id) const;
|
||||
|
||||
PendingJoin start_join() const;
|
||||
|
||||
private:
|
||||
const CipherSuite suite;
|
||||
const SignaturePrivateKey sig_priv;
|
||||
const Credential cred;
|
||||
};
|
||||
|
||||
class PendingJoin
|
||||
{
|
||||
public:
|
||||
PendingJoin(PendingJoin&& other) noexcept;
|
||||
PendingJoin& operator=(PendingJoin&& other) noexcept;
|
||||
~PendingJoin();
|
||||
bytes key_package() const;
|
||||
Session complete(const bytes& welcome) const;
|
||||
|
||||
private:
|
||||
struct Inner;
|
||||
std::unique_ptr<Inner> inner;
|
||||
|
||||
PendingJoin(Inner* inner);
|
||||
friend class Client;
|
||||
};
|
||||
|
||||
class Session
|
||||
{
|
||||
public:
|
||||
Session(Session&& other) noexcept;
|
||||
Session& operator=(Session&& other) noexcept;
|
||||
~Session();
|
||||
|
||||
// Settings
|
||||
void encrypt_handshake(bool enabled);
|
||||
|
||||
// Message producers
|
||||
bytes add(const bytes& key_package_data);
|
||||
bytes update();
|
||||
bytes remove(uint32_t index);
|
||||
std::tuple<bytes, bytes> commit(const bytes& proposal);
|
||||
std::tuple<bytes, bytes> commit(const std::vector<bytes>& proposals);
|
||||
std::tuple<bytes, bytes> commit();
|
||||
|
||||
// Message consumers
|
||||
bool handle(const bytes& handshake_data);
|
||||
|
||||
// Information about the current state
|
||||
epoch_t epoch() const;
|
||||
LeafIndex index() const;
|
||||
CipherSuite cipher_suite() const;
|
||||
const ExtensionList& extensions() const;
|
||||
const TreeKEMPublicKey& tree() const;
|
||||
bytes do_export(const std::string& label,
|
||||
const bytes& context,
|
||||
size_t size) const;
|
||||
GroupInfo group_info() const;
|
||||
std::vector<LeafNode> roster() const;
|
||||
bytes epoch_authenticator() const;
|
||||
|
||||
// Application message protection
|
||||
bytes protect(const bytes& plaintext);
|
||||
bytes unprotect(const bytes& ciphertext);
|
||||
|
||||
protected:
|
||||
struct Inner;
|
||||
std::unique_ptr<Inner> inner;
|
||||
|
||||
Session(Inner* inner);
|
||||
friend class Client;
|
||||
friend class PendingJoin;
|
||||
|
||||
friend bool operator==(const Session& lhs, const Session& rhs);
|
||||
friend bool operator!=(const Session& lhs, const Session& rhs);
|
||||
};
|
||||
|
||||
} // namespace mlspp
|
||||
431
DPP/mlspp/include/mls/state.h
Executable file
431
DPP/mlspp/include/mls/state.h
Executable file
@@ -0,0 +1,431 @@
|
||||
#pragma once
|
||||
|
||||
#include "mls/crypto.h"
|
||||
#include "mls/key_schedule.h"
|
||||
#include "mls/messages.h"
|
||||
#include "mls/treekem.h"
|
||||
#include <list>
|
||||
#include <optional>
|
||||
#include <vector>
|
||||
|
||||
namespace mlspp {
|
||||
|
||||
// Index into the session roster
|
||||
struct RosterIndex : public UInt32
|
||||
{
|
||||
using UInt32::UInt32;
|
||||
};
|
||||
|
||||
struct CommitOpts
|
||||
{
|
||||
std::vector<Proposal> 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<TreeKEMPublicKey>& tree,
|
||||
std::map<bytes, bytes> 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<MLSMessage, State> external_join(
|
||||
const bytes& leaf_secret,
|
||||
SignaturePrivateKey sig_priv,
|
||||
const KeyPackage& key_package,
|
||||
const GroupInfo& group_info,
|
||||
const std::optional<TreeKEMPublicKey>& tree,
|
||||
const MessageOpts& msg_opts,
|
||||
std::optional<LeafIndex> remove_prior,
|
||||
const std::map<bytes, bytes>& 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<MLSMessage, Welcome, State> commit(
|
||||
const bytes& leaf_secret,
|
||||
const std::optional<CommitOpts>& opts,
|
||||
const MessageOpts& msg_opts);
|
||||
|
||||
///
|
||||
/// Generic handshake message handlers
|
||||
///
|
||||
std::optional<State> handle(const MLSMessage& msg);
|
||||
std::optional<State> handle(const MLSMessage& msg,
|
||||
std::optional<State> cached_state);
|
||||
|
||||
std::optional<State> handle(const ValidatedContent& content_auth);
|
||||
std::optional<State> handle(const ValidatedContent& content_auth,
|
||||
std::optional<State> 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<LeafNode> 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<bytes, bytes> unprotect(const MLSMessage& ct);
|
||||
|
||||
// Assemble a group context for this state
|
||||
GroupContext group_context() const;
|
||||
|
||||
// Subgroup branching
|
||||
std::tuple<State, Welcome> create_branch(
|
||||
bytes group_id,
|
||||
HPKEPrivateKey enc_priv,
|
||||
SignaturePrivateKey sig_priv,
|
||||
const LeafNode& leaf_node,
|
||||
ExtensionList extensions,
|
||||
const std::vector<KeyPackage>& 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<TreeKEMPublicKey>& tree) const;
|
||||
|
||||
// Reinitialization
|
||||
struct Tombstone
|
||||
{
|
||||
std::tuple<State, Welcome> create_welcome(
|
||||
HPKEPrivateKey enc_priv,
|
||||
SignaturePrivateKey sig_priv,
|
||||
const LeafNode& leaf_node,
|
||||
const std::vector<KeyPackage>& 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<TreeKEMPublicKey>& 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<Tombstone, MLSMessage> reinit_commit(
|
||||
const bytes& leaf_secret,
|
||||
const std::optional<CommitOpts>& 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<bytes, bytes> _external_psks;
|
||||
|
||||
using EpochRef = std::tuple<bytes, epoch_t>;
|
||||
std::map<EpochRef, bytes> _resumption_psks;
|
||||
|
||||
// Cache of Proposals and update secrets
|
||||
struct CachedProposal
|
||||
{
|
||||
ProposalRef ref;
|
||||
Proposal proposal;
|
||||
std::optional<LeafIndex> sender;
|
||||
};
|
||||
std::list<CachedProposal> _pending_proposals;
|
||||
|
||||
struct CachedUpdate
|
||||
{
|
||||
HPKEPrivateKey update_priv;
|
||||
Update proposal;
|
||||
};
|
||||
std::optional<CachedUpdate> _cached_update;
|
||||
|
||||
// Assemble a preliminary, unjoined group state
|
||||
State(SignaturePrivateKey sig_priv,
|
||||
const GroupInfo& group_info,
|
||||
const std::optional<TreeKEMPublicKey>& 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<TreeKEMPublicKey>& tree,
|
||||
std::map<bytes, bytes> external_psks,
|
||||
std::map<EpochRef, bytes> resumption_psks);
|
||||
|
||||
// Import a tree from an externally-provided tree or an extension
|
||||
TreeKEMPublicKey import_tree(const bytes& tree_hash,
|
||||
const std::optional<TreeKEMPublicKey>& 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<NormalCommitParams,
|
||||
ExternalCommitParams,
|
||||
RestartCommitParams,
|
||||
ReInitCommitParams>;
|
||||
|
||||
std::tuple<MLSMessage, Welcome, State> commit(
|
||||
const bytes& leaf_secret,
|
||||
const std::optional<CommitOpts>& opts,
|
||||
const MessageOpts& msg_opts,
|
||||
CommitParams params);
|
||||
|
||||
std::optional<State> handle(
|
||||
const MLSMessage& msg,
|
||||
std::optional<State> cached_state,
|
||||
const std::optional<CommitParams>& expected_params);
|
||||
std::optional<State> handle(
|
||||
const ValidatedContent& val_content,
|
||||
std::optional<State> cached_state,
|
||||
const std::optional<CommitParams>& expected_params);
|
||||
|
||||
// Create an MLSMessage encapsulating some content
|
||||
template<typename Inner>
|
||||
AuthenticatedContent sign(const Sender& sender,
|
||||
Inner&& content,
|
||||
const bytes& authenticated_data,
|
||||
bool encrypt) const;
|
||||
|
||||
MLSMessage protect(AuthenticatedContent&& content_auth, size_t padding_size);
|
||||
|
||||
template<typename Inner>
|
||||
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<LeafIndex> apply(const std::vector<CachedProposal>& proposals,
|
||||
Proposal::Type required_type);
|
||||
std::tuple<std::vector<LeafIndex>, std::vector<PSKWithSecret>> apply(
|
||||
const std::vector<CachedProposal>& 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<CachedProposal> resolve(
|
||||
const ProposalOrRef& id,
|
||||
std::optional<LeafIndex> sender_index) const;
|
||||
std::vector<CachedProposal> must_resolve(
|
||||
const std::vector<ProposalOrRef>& ids,
|
||||
std::optional<LeafIndex> sender_index) const;
|
||||
|
||||
std::vector<PSKWithSecret> resolve(
|
||||
const std::vector<PreSharedKeyID>& psks) const;
|
||||
|
||||
// Check properties of proposals
|
||||
bool valid(const LeafNode& leaf_node,
|
||||
LeafNodeSource required_source,
|
||||
std::optional<LeafIndex> 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<LeafIndex> sender, const Proposal& proposal) const;
|
||||
|
||||
bool valid(const std::vector<CachedProposal>& proposals,
|
||||
LeafIndex commit_sender,
|
||||
const CommitParams& params) const;
|
||||
bool valid_normal(const std::vector<CachedProposal>& proposals,
|
||||
LeafIndex commit_sender) const;
|
||||
bool valid_external(const std::vector<CachedProposal>& proposals) const;
|
||||
static bool valid_reinit(const std::vector<CachedProposal>& proposals);
|
||||
static bool valid_restart(const std::vector<CachedProposal>& proposals,
|
||||
ResumptionPSKUsage allowed_usage);
|
||||
|
||||
static bool valid_external_proposal_type(const Proposal::Type proposal_type);
|
||||
|
||||
CommitParams infer_commit_type(
|
||||
const std::optional<LeafIndex>& sender,
|
||||
const std::vector<CachedProposal>& proposals,
|
||||
const std::optional<CommitParams>& expected_params) const;
|
||||
static bool path_required(const std::vector<CachedProposal>& 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<PSKWithSecret>& psks,
|
||||
const std::optional<bytes>& 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
|
||||
107
DPP/mlspp/include/mls/tree_math.h
Executable file
107
DPP/mlspp/include/mls/tree_math.h
Executable file
@@ -0,0 +1,107 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <tls/tls_syntax.h>
|
||||
#include <vector>
|
||||
|
||||
// The below functions provide the index calculus for the tree
|
||||
// structures used in MLS. They are premised on a "flat"
|
||||
// representation of a balanced binary tree. Leaf nodes are
|
||||
// even-numbered nodes, with the n-th leaf at 2*n. Intermediate
|
||||
// nodes are held in odd-numbered nodes. For example, a 11-element
|
||||
// tree has the following structure:
|
||||
//
|
||||
// X
|
||||
// X
|
||||
// X X X
|
||||
// X X X X X
|
||||
// X X X X X X X X X X X
|
||||
// 0 1 2 3 4 5 6 7 8 9 a b c d e f 10 11 12 13 14
|
||||
//
|
||||
// This allows us to compute relationships between tree nodes simply
|
||||
// by manipulating indices, rather than having to maintain
|
||||
// complicated structures in memory, even for partial trees. (The
|
||||
// storage for a tree can just be a map[int]Node dictionary or an
|
||||
// array.) The basic rule is that the high-order bits of parent and
|
||||
// child nodes have the following relation:
|
||||
//
|
||||
// 01x = <00x, 10x>
|
||||
|
||||
namespace mlspp {
|
||||
|
||||
// Index types go in the overall namespace
|
||||
// XXX(rlb@ipv.sx): Seems like this stuff can probably get
|
||||
// simplified down a fair bit.
|
||||
struct UInt32
|
||||
{
|
||||
uint32_t val;
|
||||
|
||||
UInt32()
|
||||
: val(0)
|
||||
{
|
||||
}
|
||||
|
||||
explicit UInt32(uint32_t val_in)
|
||||
: val(val_in)
|
||||
{
|
||||
}
|
||||
|
||||
TLS_SERIALIZABLE(val)
|
||||
};
|
||||
|
||||
struct NodeCount;
|
||||
|
||||
struct LeafCount : public UInt32
|
||||
{
|
||||
using UInt32::UInt32;
|
||||
explicit LeafCount(const NodeCount w);
|
||||
|
||||
static LeafCount full(const LeafCount n);
|
||||
};
|
||||
|
||||
struct NodeCount : public UInt32
|
||||
{
|
||||
using UInt32::UInt32;
|
||||
explicit NodeCount(const LeafCount n);
|
||||
};
|
||||
|
||||
struct NodeIndex;
|
||||
|
||||
struct LeafIndex : public UInt32
|
||||
{
|
||||
using UInt32::UInt32;
|
||||
explicit LeafIndex(const NodeIndex x);
|
||||
bool operator<(const LeafIndex other) const { return val < other.val; }
|
||||
bool operator<(const LeafCount other) const { return val < other.val; }
|
||||
|
||||
NodeIndex ancestor(LeafIndex other) const;
|
||||
};
|
||||
|
||||
struct NodeIndex : public UInt32
|
||||
{
|
||||
using UInt32::UInt32;
|
||||
explicit NodeIndex(const LeafIndex x);
|
||||
bool operator<(const NodeIndex other) const { return val < other.val; }
|
||||
bool operator<(const NodeCount other) const { return val < other.val; }
|
||||
|
||||
static NodeIndex root(LeafCount n);
|
||||
|
||||
bool is_leaf() const;
|
||||
bool is_below(NodeIndex other) const;
|
||||
|
||||
NodeIndex left() const;
|
||||
NodeIndex right() const;
|
||||
NodeIndex parent() const;
|
||||
NodeIndex sibling() const;
|
||||
|
||||
// Returns the sibling of this node "relative to this ancestor" -- the child
|
||||
// of `ancestor` that is not in the direct path of this node.
|
||||
NodeIndex sibling(NodeIndex ancestor) const;
|
||||
|
||||
std::vector<NodeIndex> dirpath(LeafCount n);
|
||||
std::vector<NodeIndex> copath(LeafCount n);
|
||||
|
||||
uint32_t level() const;
|
||||
};
|
||||
|
||||
} // namespace mlspp
|
||||
255
DPP/mlspp/include/mls/treekem.h
Executable file
255
DPP/mlspp/include/mls/treekem.h
Executable file
@@ -0,0 +1,255 @@
|
||||
#pragma once
|
||||
|
||||
#include "mls/common.h"
|
||||
#include "mls/core_types.h"
|
||||
#include "mls/crypto.h"
|
||||
#include "mls/tree_math.h"
|
||||
#include <tls/tls_syntax.h>
|
||||
|
||||
#define ENABLE_TREE_DUMP 1
|
||||
|
||||
namespace mlspp {
|
||||
|
||||
enum struct NodeType : uint8_t
|
||||
{
|
||||
reserved = 0x00,
|
||||
leaf = 0x01,
|
||||
parent = 0x02,
|
||||
};
|
||||
|
||||
struct Node
|
||||
{
|
||||
var::variant<LeafNode, ParentNode> node;
|
||||
|
||||
const HPKEPublicKey& public_key() const;
|
||||
std::optional<bytes> parent_hash() const;
|
||||
|
||||
TLS_SERIALIZABLE(node)
|
||||
TLS_TRAITS(tls::variant<NodeType>)
|
||||
};
|
||||
|
||||
struct OptionalNode
|
||||
{
|
||||
std::optional<Node> node;
|
||||
|
||||
bool blank() const { return !node.has_value(); }
|
||||
bool leaf() const
|
||||
{
|
||||
return !blank() && var::holds_alternative<LeafNode>(opt::get(node).node);
|
||||
}
|
||||
|
||||
LeafNode& leaf_node() { return var::get<LeafNode>(opt::get(node).node); }
|
||||
|
||||
const LeafNode& leaf_node() const
|
||||
{
|
||||
return var::get<LeafNode>(opt::get(node).node);
|
||||
}
|
||||
|
||||
ParentNode& parent_node()
|
||||
{
|
||||
return var::get<ParentNode>(opt::get(node).node);
|
||||
}
|
||||
|
||||
const ParentNode& parent_node() const
|
||||
{
|
||||
return var::get<ParentNode>(opt::get(node).node);
|
||||
}
|
||||
|
||||
TLS_SERIALIZABLE(node)
|
||||
};
|
||||
|
||||
struct TreeKEMPublicKey;
|
||||
|
||||
struct TreeKEMPrivateKey
|
||||
{
|
||||
CipherSuite suite;
|
||||
LeafIndex index;
|
||||
bytes update_secret;
|
||||
std::map<NodeIndex, bytes> path_secrets;
|
||||
std::map<NodeIndex, HPKEPrivateKey> private_key_cache;
|
||||
|
||||
static TreeKEMPrivateKey solo(CipherSuite suite,
|
||||
LeafIndex index,
|
||||
HPKEPrivateKey leaf_priv);
|
||||
static TreeKEMPrivateKey create(const TreeKEMPublicKey& pub,
|
||||
LeafIndex from,
|
||||
const bytes& leaf_secret);
|
||||
static TreeKEMPrivateKey joiner(const TreeKEMPublicKey& pub,
|
||||
LeafIndex index,
|
||||
HPKEPrivateKey leaf_priv,
|
||||
NodeIndex intersect,
|
||||
const std::optional<bytes>& path_secret);
|
||||
|
||||
void set_leaf_priv(HPKEPrivateKey priv);
|
||||
std::tuple<NodeIndex, bytes, bool> shared_path_secret(LeafIndex to) const;
|
||||
|
||||
bool have_private_key(NodeIndex n) const;
|
||||
std::optional<HPKEPrivateKey> private_key(NodeIndex n);
|
||||
std::optional<HPKEPrivateKey> private_key(NodeIndex n) const;
|
||||
|
||||
void decap(LeafIndex from,
|
||||
const TreeKEMPublicKey& pub,
|
||||
const bytes& context,
|
||||
const UpdatePath& path,
|
||||
const std::vector<LeafIndex>& except);
|
||||
|
||||
void truncate(LeafCount size);
|
||||
|
||||
bool consistent(const TreeKEMPrivateKey& other) const;
|
||||
bool consistent(const TreeKEMPublicKey& other) const;
|
||||
|
||||
#if ENABLE_TREE_DUMP
|
||||
void dump() const;
|
||||
#endif
|
||||
|
||||
// TODO(RLB) Make this private but exposed to test vectors
|
||||
void implant(const TreeKEMPublicKey& pub,
|
||||
NodeIndex start,
|
||||
const bytes& path_secret);
|
||||
};
|
||||
|
||||
struct TreeKEMPublicKey
|
||||
{
|
||||
CipherSuite suite;
|
||||
LeafCount size{ 0 };
|
||||
std::vector<OptionalNode> nodes;
|
||||
|
||||
explicit TreeKEMPublicKey(CipherSuite suite);
|
||||
|
||||
TreeKEMPublicKey() = default;
|
||||
TreeKEMPublicKey(const TreeKEMPublicKey& other) = default;
|
||||
TreeKEMPublicKey(TreeKEMPublicKey&& other) = default;
|
||||
TreeKEMPublicKey& operator=(const TreeKEMPublicKey& other) = default;
|
||||
TreeKEMPublicKey& operator=(TreeKEMPublicKey&& other) = default;
|
||||
|
||||
LeafIndex allocate_leaf();
|
||||
LeafIndex add_leaf(const LeafNode& leaf);
|
||||
void update_leaf(LeafIndex index, const LeafNode& leaf);
|
||||
void blank_path(LeafIndex index);
|
||||
|
||||
TreeKEMPrivateKey update(LeafIndex from,
|
||||
const bytes& leaf_secret,
|
||||
const bytes& group_id,
|
||||
const SignaturePrivateKey& sig_priv,
|
||||
const LeafNodeOptions& opts);
|
||||
UpdatePath encap(const TreeKEMPrivateKey& priv,
|
||||
const bytes& context,
|
||||
const std::vector<LeafIndex>& except) const;
|
||||
|
||||
void merge(LeafIndex from, const UpdatePath& path);
|
||||
void set_hash_all();
|
||||
const bytes& get_hash(NodeIndex index);
|
||||
bytes root_hash() const;
|
||||
|
||||
bool parent_hash_valid(LeafIndex from, const UpdatePath& path) const;
|
||||
bool parent_hash_valid() const;
|
||||
|
||||
bool has_leaf(LeafIndex index) const;
|
||||
std::optional<LeafIndex> find(const LeafNode& leaf) const;
|
||||
std::optional<LeafNode> leaf_node(LeafIndex index) const;
|
||||
std::vector<NodeIndex> resolve(NodeIndex index) const;
|
||||
|
||||
template<typename UnaryPredicate>
|
||||
bool all_leaves(const UnaryPredicate& pred) const
|
||||
{
|
||||
for (LeafIndex i{ 0 }; i < size; i.val++) {
|
||||
const auto& node = node_at(i);
|
||||
if (node.blank()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!pred(i, node.leaf_node())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename UnaryPredicate>
|
||||
bool any_leaf(const UnaryPredicate& pred) const
|
||||
{
|
||||
for (LeafIndex i{ 0 }; i < size; i.val++) {
|
||||
const auto& node = node_at(i);
|
||||
if (node.blank()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pred(i, node.leaf_node())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
using FilteredDirectPath =
|
||||
std::vector<std::tuple<NodeIndex, std::vector<NodeIndex>>>;
|
||||
FilteredDirectPath filtered_direct_path(NodeIndex index) const;
|
||||
|
||||
void truncate();
|
||||
|
||||
OptionalNode& node_at(NodeIndex n);
|
||||
const OptionalNode& node_at(NodeIndex n) const;
|
||||
OptionalNode& node_at(LeafIndex n);
|
||||
const OptionalNode& node_at(LeafIndex n) const;
|
||||
|
||||
TLS_SERIALIZABLE(nodes)
|
||||
|
||||
#if ENABLE_TREE_DUMP
|
||||
void dump() const;
|
||||
#endif
|
||||
|
||||
private:
|
||||
std::map<NodeIndex, bytes> hashes;
|
||||
|
||||
void clear_hash_all();
|
||||
void clear_hash_path(LeafIndex index);
|
||||
|
||||
bool has_parent_hash(NodeIndex child, const bytes& target_ph) const;
|
||||
|
||||
bytes parent_hash(const ParentNode& parent, NodeIndex copath_child) const;
|
||||
std::vector<bytes> parent_hashes(
|
||||
LeafIndex from,
|
||||
const FilteredDirectPath& fdp,
|
||||
const std::vector<UpdatePathNode>& path_nodes) const;
|
||||
|
||||
using TreeHashCache = std::map<NodeIndex, std::pair<size_t, bytes>>;
|
||||
const bytes& original_tree_hash(TreeHashCache& cache,
|
||||
NodeIndex index,
|
||||
std::vector<LeafIndex> parent_except) const;
|
||||
bytes original_parent_hash(TreeHashCache& cache,
|
||||
NodeIndex parent,
|
||||
NodeIndex sibling) const;
|
||||
|
||||
bool exists_in_tree(const HPKEPublicKey& key,
|
||||
std::optional<LeafIndex> except) const;
|
||||
bool exists_in_tree(const SignaturePublicKey& key,
|
||||
std::optional<LeafIndex> except) const;
|
||||
|
||||
OptionalNode blank_node;
|
||||
|
||||
friend struct TreeKEMPrivateKey;
|
||||
};
|
||||
|
||||
tls::ostream&
|
||||
operator<<(tls::ostream& str, const TreeKEMPublicKey& obj);
|
||||
tls::istream&
|
||||
operator>>(tls::istream& str, TreeKEMPublicKey& obj);
|
||||
|
||||
struct LeafNodeHashInput;
|
||||
struct ParentNodeHashInput;
|
||||
|
||||
} // namespace mlspp
|
||||
|
||||
namespace mlspp::tls {
|
||||
|
||||
TLS_VARIANT_MAP(mlspp::NodeType, mlspp::LeafNodeHashInput, leaf)
|
||||
TLS_VARIANT_MAP(mlspp::NodeType,
|
||||
mlspp::ParentNodeHashInput,
|
||||
parent)
|
||||
|
||||
TLS_VARIANT_MAP(mlspp::NodeType, mlspp::LeafNode, leaf)
|
||||
TLS_VARIANT_MAP(mlspp::NodeType, mlspp::ParentNode, parent)
|
||||
|
||||
} // namespace mlspp::tls
|
||||
4
DPP/mlspp/include/namespace.h
Executable file
4
DPP/mlspp/include/namespace.h
Executable file
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
// Configurable top-level MLS namespace
|
||||
#define MLS_NAMESPACE ../include/dpp/mlspp/mls
|
||||
5
DPP/mlspp/include/version.h
Executable file
5
DPP/mlspp/include/version.h
Executable file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
/* Global version strings */
|
||||
extern const char VERSION[];
|
||||
extern const char HASHVAR[];
|
||||
Reference in New Issue
Block a user