add basic discord support

This commit is contained in:
2024-11-09 01:58:06 +03:00
parent c3e7a9c92d
commit 8965b7ee90
869 changed files with 191278 additions and 7 deletions

274
DPP/mlspp/include/mls/common.h Executable file
View 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

View 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

View 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
View 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

View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@@ -0,0 +1,5 @@
#pragma once
/* Global version strings */
extern const char VERSION[];
extern const char HASHVAR[];