spank-olm

WIP Do not look
git clone git://archive.git.mtrnord.blog/MTRNord/spank-olm.git
Log | Files | Refs | README | LICENSE

commit cec50f7a274d09d95ca9e00020b6b59c44f336a8
parent a147d1b9bec2045f5d08065c98400bf8aef81586
Author: MTRNord <mtrnord1@gmail.com>
Date:   Mon, 14 Apr 2025 16:13:59 +0200

Add rest of pickling, clang-format and fix fuzzer

Diffstat:
A.clang-format | 54++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mfuzz/StandaloneFuzzTargetMain.c | 16++++++++--------
Mfuzz/olm_sign_fuzzer.cpp | 16++++++----------
Ainclude/megolm.hpp | 60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ainclude/pickle.hpp | 181+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mmeson.build | 8++++++--
Msrc/account.cpp | 401++++++-------------------------------------------------------------------------
Asrc/megolm.cpp | 168+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/pickle.cpp | 365+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
9 files changed, 878 insertions(+), 391 deletions(-)

diff --git a/.clang-format b/.clang-format @@ -0,0 +1,54 @@ +# Generated from CLion C/C++ Code Style settings +--- +Language: Cpp +BasedOnStyle: LLVM +AccessModifierOffset: -4 +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignOperands: false +AlignTrailingComments: false +AlwaysBreakTemplateDeclarations: Yes +BraceWrapping: + AfterCaseLabel: true + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterStruct: true + AfterUnion: true + AfterExternBlock: false + BeforeCatch: true + BeforeElse: true + BeforeLambdaBody: true + BeforeWhile: true + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBraces: Custom +BreakConstructorInitializers: AfterColon +BreakConstructorInitializersBeforeComma: false +ColumnLimit: 120 +ConstructorInitializerAllOnOneLineOrOnePerLine: false +IncludeCategories: + - Regex: '^<.*' + Priority: 1 + - Regex: '^".*' + Priority: 2 + - Regex: '.*' + Priority: 3 +IncludeIsMainRegex: '([-_](test|unittest))?$' +IndentCaseBlocks: true +IndentWidth: 4 +InsertNewlineAtEOF: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: All +SpaceInEmptyParentheses: false +SpacesInAngles: false +SpacesInConditionalStatement: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +TabWidth: 4 +... diff --git a/fuzz/StandaloneFuzzTargetMain.c b/fuzz/StandaloneFuzzTargetMain.c @@ -18,12 +18,12 @@ #include <stdlib.h> #include <string.h> -extern int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size); -extern int LLVMFuzzerInitialize(int* argc, char*** argv); +extern int LLVMFuzzerTestOneInput(const unsigned char *data, size_t size); +extern int LLVMFuzzerInitialize(int *argc, char ***argv); -int main(int argc, char** argv) +int main(int argc, char **argv) { - const char* progname; + const char *progname; if ((progname = strrchr(argv[0], '/'))) progname++; else @@ -33,13 +33,13 @@ int main(int argc, char** argv) for (int i = 1; i < argc; i++) { fprintf(stderr, "Running: %s\n", argv[i]); - FILE* f = fopen(argv[i], "r+"); + FILE *f = fopen(argv[i], "r+"); assert(f); fseek(f, 0, SEEK_END); - long len = ftell(f); + const long len = ftell(f); fseek(f, 0, SEEK_SET); - unsigned char* buf = (unsigned char*)malloc(len); - size_t n_read = fread(buf, 1, len, f); + unsigned char *buf = (unsigned char *)malloc(len); + const size_t n_read = fread(buf, 1, len, f); fclose(f); assert(n_read == len); LLVMFuzzerTestOneInput(buf, len); diff --git a/fuzz/olm_sign_fuzzer.cpp b/fuzz/olm_sign_fuzzer.cpp @@ -1,18 +1,14 @@ +#include <botan/auto_rng.h> #include <cassert> -#include <cstdint> #include <cstddef> -#include <botan/auto_rng.h> +#include <cstdint> #include "account.hpp" #include "botan/pubkey.h" // Just needed for compiling reasons -extern "C" int -LLVMFuzzerInitialize(int* argc, char*** argv) -{ - return 0; -} +extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) { return 0; } -extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { if (Size == 0) { @@ -23,8 +19,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* Data, size_t Size) Botan::AutoSeeded_RNG rng; account.new_account(rng); - const std::string_view message(reinterpret_cast<const char*>(Data), Size); - auto signature = account.sign(message); + const std::string_view message(reinterpret_cast<const char *>(Data), Size); + auto signature = account.sign(rng, message); // Verify the signature Botan::PK_Verifier verifier(account.identity_keys->ed25519_key, "Ed25519ph"); diff --git a/include/megolm.hpp b/include/megolm.hpp @@ -0,0 +1,60 @@ +#pragma once +#include <array> +#include <botan/rng.h> + +/** + * number of bytes in each part of the ratchet; this should be the same as + * the length of the hash function used in the HMAC (32 bytes for us, as we + * use HMAC-SHA-256) + */ +#define MEGOLM_RATCHET_PART_LENGTH 32 /* SHA256_OUTPUT_LENGTH */ + +/** + * number of parts in the ratchet; the advance() implementations rely on + * this being 4. + */ +#define MEGOLM_RATCHET_PARTS 4 + +#define MEGOLM_RATCHET_LENGTH (MEGOLM_RATCHET_PARTS * MEGOLM_RATCHET_PART_LENGTH) + +namespace spank_olm +{ + struct Megolm + { + std::array<std::array<std::uint8_t, MEGOLM_RATCHET_PART_LENGTH>, MEGOLM_RATCHET_PARTS> data; + std::uint32_t counter; + + + /** + * \brief Initialize the megolm ratchet. + */ + void init(Botan::RandomNumberGenerator &rng, unsigned int counter); + + /** + * \brief Returns the number of bytes needed to store a megolm + */ + [[nodiscard]] size_t pickle_length() const; + + /** + * \brief Pickle the megolm. + */ + std::uint8_t *pickle(std::uint8_t *pos) const; + + /** + * \brief Unpickle the megolm. + */ + std::uint8_t const *unpickle(std::uint8_t const *pos, const std::uint8_t *end); + + /** + * \brief Advance the megolm ratchet by one step. + */ + void advance(); + + /** + * \brief Advance the megolm ratchet by a given number of steps. + */ + void advance(unsigned int advance_to); + + [[nodiscard]] const uint8_t *get_data() const { return reinterpret_cast<const uint8_t *>(data.data()); } + }; +} // namespace spank_olm diff --git a/include/pickle.hpp b/include/pickle.hpp @@ -0,0 +1,181 @@ +#pragma once +#include <account.hpp> + +namespace spank_olm +{ + + /** + * Serializes a 32-bit unsigned integer into a byte array. + * + * @param pos Pointer to the current position in the byte array. + * @param value The 32-bit unsigned integer to serialize. + * @return Pointer to the position in the byte array after the serialized data. + */ + std::uint8_t *pickle(std::uint8_t *pos, std::uint32_t value); + + /** + * Deserializes a 32-bit unsigned integer from a byte array. + * + * @param pos Pointer to the current position in the byte array. + * @param end Pointer to the end of the byte array. + * @param value Reference to the 32-bit unsigned integer to store the deserialized value. + * @return Pointer to the position in the byte array after the deserialized data, or + * nullptr on failure. + */ + std::uint8_t const *unpickle(std::uint8_t const *pos, std::uint8_t const *end, std::uint32_t &value); + + /** + * Serializes a boolean value into a byte array. + * + * @param pos Pointer to the current position in the byte array. + * @param value The boolean value to serialize. + * @return Pointer to the position in the byte array after the serialized data. + */ + std::uint8_t *pickle(std::uint8_t *pos, bool value); + + /** + * Deserializes a boolean value from a byte array. + * + * @param pos Pointer to the current position in the byte array. + * @param end Pointer to the end of the byte array. + * @param value Reference to the boolean value to store the deserialized value. + * @return Pointer to the position in the byte array after the deserialized data, or nullptr on failure. + */ + std::uint8_t const *unpickle(std::uint8_t const *pos, std::uint8_t const *end, bool &value); + + /** + * Serializes a Botan::secure_vector<uint8_t> into a byte array. + * + * @param pos Pointer to the current position in the byte array. + * @param value The Botan::secure_vector<uint8_t> to serialize. + * @return Pointer to the position in the byte array after the serialized data. + */ + std::uint8_t *pickle(std::uint8_t *pos, const Botan::secure_vector<uint8_t> &value); + + /** + * Deserializes a Botan::secure_vector<uint8_t> from a byte array. + * + * @param pos Pointer to the current position in the byte array. + * @param end Pointer to the end of the byte array. + * @param value Reference to the Botan::secure_vector<uint8_t> to store the deserialized value. + * @return Pointer to the position in the byte array after the deserialized data, or nullptr on failure. + */ + std::uint8_t const *unpickle(std::uint8_t const *pos, std::uint8_t const *end, + Botan::secure_vector<uint8_t> &value); + + /** + * Serializes a std::vector<uint8_t> into a byte array. + * + * @param pos Pointer to the current position in the byte array. + * @param value The Botan::secure_vector<uint8_t> to serialize. + * @return Pointer to the position in the byte array after the serialized data. + */ + std::uint8_t *pickle(std::uint8_t *pos, const std::vector<uint8_t> &value); + + /** + * Deserializes a std::vector<uint8_t> from a byte array. + * + * @param pos Pointer to the current position in the byte array. + * @param end Pointer to the end of the byte array. + * @param value Reference to the Botan::secure_vector<uint8_t> to store the deserialized value. + * @return Pointer to the position in the byte array after the deserialized data, or nullptr on failure. + */ + std::uint8_t const *unpickle(std::uint8_t const *pos, std::uint8_t const *end, std::vector<uint8_t> &value); + + /** + * + * @param pos Pointer to the current position in the byte array. + * @param value A OneTimeKey object to serialize. + * @return Pointer to the position in the byte array after the serialized data. + */ + std::uint8_t *pickle(std::uint8_t *pos, const std::optional<OneTimeKey> &value); + + /** + * Deserializes a OneTimeKey object from a byte array. + * + * @param pos Pointer to the current position in the byte array. + * @param end Pointer to the end of the byte array. + * @return A pair containing the pointer to the position in the byte array after the deserialized data and the + * deserialized OneTimeKey object. + */ + static std::pair<std::uint8_t const *, std::optional<OneTimeKey>> unpickle_otk(std::uint8_t const *pos, + std::uint8_t const *end); + + /** + * Serializes a FixedSizeArray object into a byte array. + * + * @tparam T The type of elements in the FixedSizeArray. + * @tparam max_size The maximum size of the FixedSizeArray. + * @param pos Pointer to the current position in the byte array. + * @param list The FixedSizeArray object to serialize. + * @return Pointer to the position in the byte array after the serialized data. + */ + template <typename T, std::size_t max_size> + std::uint8_t *pickle(std::uint8_t *pos, FixedSizeArray<T, max_size> const &list); + + /** + * Deserializes a FixedSizeArray object from a byte array. + * + * @tparam max_size The maximum size of the FixedSizeArray. + * @param pos Pointer to the current position in the byte array. + * @param end Pointer to the end of the byte array. + * @param list Reference to the FixedSizeArray object to store the deserialized values. + * @return Pointer to the position in the byte array after the deserialized data, or nullptr on failure. + */ + template <std::size_t max_size> + std::uint8_t const *unpickle(std::uint8_t const *pos, std::uint8_t const *end, + FixedSizeArray<OneTimeKey, max_size> &list); + + /** + * Serializes a uint8_t value into a byte array. + * + * @param pos Pointer to the current position in the byte array. + * @param value The uint8_t value to serialize. + * @return Pointer to the position in the byte array after the serialized data. + */ + std::uint8_t *pickle(std::uint8_t *pos, std::uint8_t value); + + /** + * Deserializes a uint8_t value from a byte array. + * + * @param pos Pointer to the current position in the byte array. + * @param end Pointer to the end of the byte array. + * @param value Reference to the uint8_t value to store the deserialized value. + * @return Pointer to the position in the byte array after the deserialized data, or nullptr on failure. + */ + std::uint8_t const *unpickle(std::uint8_t const *pos, std::uint8_t const *end, std::uint8_t &value); + + /** + * Serializes an optional IdentityKeys object into a byte array. + * + * @param pos Pointer to the current position in the byte array. + * @param value The optional IdentityKeys object to serialize. + * @return Pointer to the position in the byte array after the serialized data. + */ + std::uint8_t *pickle(std::uint8_t *pos, const std::optional<IdentityKeys> &value); + + /** + * Deserializes an optional IdentityKeys object from a byte array. + * + * @param pos Pointer to the current position in the byte array. + * @param end Pointer to the end of the byte array. + * @param value Reference to the optional IdentityKeys object to store the deserialized value. + * @return Pointer to the position in the byte array after the deserialized data, or nullptr on failure. + */ + std::uint8_t const *unpickle(std::uint8_t const *pos, std::uint8_t const *end, std::optional<IdentityKeys> &value); + + /** + * Deserializes an optional OneTimeKey object from a byte array. + * + * @param pos Pointer to the current position in the byte array. + * @param end Pointer to the end of the byte array. + * @param value Reference to the optional OneTimeKey object to store the deserialized value. + * @return Pointer to the position in the byte array after the deserialized data, or nullptr on failure. + */ + std::uint8_t const *unpickle(std::uint8_t const *pos, std::uint8_t const *end, std::optional<OneTimeKey> &value); + + std::uint8_t *pickle_bytes(std::uint8_t *pos, const std::uint8_t *bytes, const std::size_t bytes_length); + + std::uint8_t const *unpickle_bytes(const std::uint8_t *pos, const std::uint8_t *end, std::uint8_t *bytes, + const std::size_t bytes_length); +} // namespace spank_olm diff --git a/meson.build b/meson.build @@ -27,7 +27,7 @@ is_wasm = host_machine.system() == 'emscripten' # Add specific arguments for WASM if is_wasm add_project_arguments('-flto', language : 'cpp') - add_project_link_arguments('-flto', '-lembind', '-sEMBIND_AOT=1', '-sEXPORT_ES6=1', '-sMODULARIZE=1', '-sENVIRONMENT=web', '-sEXPORT_NAME=SpankOlmLibrarys', '-sFILESYSTEM=0', '-sEXPORT_ALL=1', '--emit-tsd=interface.d.ts', language : 'cpp') + add_project_link_arguments('-flto', '-lembind', '-sEMBIND_AOT=1', '-sEXPORT_ES6=1', '-sMODULARIZE=1', '-sENVIRONMENT=web,worker', '-sEXPORT_NAME=SpankOlmLibrarys', '-sFILESYSTEM=0', '-sEXPORT_ALL=1', '--emit-tsd=interface.d.ts', language : 'cpp') endif # Cmake doesnt work with meson, so we need to require pkg-config @@ -47,7 +47,11 @@ spank_olm_deps = [botan_dep] incdir = include_directories('include') # List of source files -src_files = files('src/spank-olm.cpp', 'src/account.cpp') +src_files = files( + 'src/spank-olm.cpp', + 'src/account.cpp', + 'src/megolm.cpp', + 'src/pickle.cpp', ) if is_wasm spank_olm = executable('spank_olm', src_files, install : true, dependencies : spank_olm_deps, include_directories : incdir, override_options : ['b_lto=false']) diff --git a/src/account.cpp b/src/account.cpp @@ -1,371 +1,27 @@ #include "account.hpp" #include "errors.hpp" +#include "pickle.hpp" #include <botan/pubkey.h> #include <botan/rng.h> -/* Convenience macro for checking the return value of internal unpickling - * functions and returning early on failure. */ -#ifndef UNPICKLE_OK -#define UNPICKLE_OK(x) do { if (!(x)) return nullptr; } while(0) -#endif - - namespace spank_olm { - /** - * Serializes a 32-bit unsigned integer into a byte array. - * - * @param pos Pointer to the current position in the byte array. - * @param value The 32-bit unsigned integer to serialize. - * @return Pointer to the position in the byte array after the serialized data. - */ - std::uint8_t* pickle(std::uint8_t* pos, std::uint32_t value) - { - for (int i = 3; i >= 0; --i) - { - pos[i] = value & 0xFF; - value >>= 8; - } - return pos + 4; - } - - /** - * Deserializes a 32-bit unsigned integer from a byte array. - * - * @param pos Pointer to the current position in the byte array. - * @param end Pointer to the end of the byte array. - * @param value Reference to the 32-bit unsigned integer to store the deserialized value. - * @return Pointer to the position in the byte array after the deserialized data, or nullptr on failure. - */ - std::uint8_t const* unpickle(std::uint8_t const* pos, std::uint8_t const* end, std::uint32_t& value) - { - if (!pos || end < pos + 4) return nullptr; - value = (pos[0] << 24) | (pos[1] << 16) | (pos[2] << 8) | pos[3]; - return pos + 4; - } - - /** - * Serializes a boolean value into a byte array. - * - * @param pos Pointer to the current position in the byte array. - * @param value The boolean value to serialize. - * @return Pointer to the position in the byte array after the serialized data. - */ - std::uint8_t* pickle(std::uint8_t* pos, const bool value) - { - *(pos++) = value ? 1 : 0; - return pos; - } - - /** - * Deserializes a boolean value from a byte array. - * - * @param pos Pointer to the current position in the byte array. - * @param end Pointer to the end of the byte array. - * @param value Reference to the boolean value to store the deserialized value. - * @return Pointer to the position in the byte array after the deserialized data, or nullptr on failure. - */ - std::uint8_t const* unpickle(std::uint8_t const* pos, std::uint8_t const* end, bool& value) - { - if (!pos || end <= pos) return nullptr; - value = *(pos++) != 0; - return pos; - } - - /** - * Serializes a Botan::secure_vector<uint8_t> into a byte array. - * - * @param pos Pointer to the current position in the byte array. - * @param value The Botan::secure_vector<uint8_t> to serialize. - * @return Pointer to the position in the byte array after the serialized data. - */ - std::uint8_t* pickle(std::uint8_t* pos, const Botan::secure_vector<uint8_t>& value) - { - pos = pickle(pos, static_cast<std::uint32_t>(value.size())); - for (const auto byte : value) - { - *(pos++) = byte; - } - return pos; - } - - /** - * Deserializes a Botan::secure_vector<uint8_t> from a byte array. - * - * @param pos Pointer to the current position in the byte array. - * @param end Pointer to the end of the byte array. - * @param value Reference to the Botan::secure_vector<uint8_t> to store the deserialized value. - * @return Pointer to the position in the byte array after the deserialized data, or nullptr on failure. - */ - std::uint8_t const* unpickle(std::uint8_t const* pos, std::uint8_t const* end, Botan::secure_vector<uint8_t>& value) - { - std::uint32_t size; - pos = unpickle(pos, end, size); - if (!pos || end < pos + size) return nullptr; - value.assign(pos, pos + size); - return pos + size; - } - - /** - * Serializes a std::vector<uint8_t> into a byte array. - * - * @param pos Pointer to the current position in the byte array. - * @param value The Botan::secure_vector<uint8_t> to serialize. - * @return Pointer to the position in the byte array after the serialized data. - */ - std::uint8_t* pickle(std::uint8_t* pos, const std::vector<uint8_t>& value) + void Account::new_account(Botan::RandomNumberGenerator &rng) { - pos = pickle(pos, static_cast<std::uint32_t>(value.size())); - for (const auto byte : value) - { - *(pos++) = byte; - } - return pos; - } - - /** - * Deserializes a std::vector<uint8_t> from a byte array. - * - * @param pos Pointer to the current position in the byte array. - * @param end Pointer to the end of the byte array. - * @param value Reference to the Botan::secure_vector<uint8_t> to store the deserialized value. - * @return Pointer to the position in the byte array after the deserialized data, or nullptr on failure. - */ - std::uint8_t const* unpickle(std::uint8_t const* pos, std::uint8_t const* end, std::vector<uint8_t>& value) - { - std::uint32_t size; - pos = unpickle(pos, end, size); - if (!pos || end < pos + size) return nullptr; - value.assign(pos, pos + size); - return pos + size; - } - - std::uint8_t* pickle( - std::uint8_t* pos, - const std::optional<OneTimeKey>& value) - { - pos = pickle(pos, value->id); - pos = pickle(pos, value->published); - return pickle(pos, value->key.raw_private_key_bits()); - } - - /** - * Deserializes a OneTimeKey object from a byte array. - * - * @param pos Pointer to the current position in the byte array. - * @param end Pointer to the end of the byte array. - * @return A pair containing the pointer to the position in the byte array after the deserialized data and the deserialized OneTimeKey object. - */ - static std::pair<std::uint8_t const*, std::optional<OneTimeKey>> unpickle_otk( - std::uint8_t const* pos, std::uint8_t const* end - ) - { - std::uint32_t id; ///< The unique identifier for the one-time key. - bool published; ///< Indicates whether the key has been published. - - pos = unpickle(pos, end, id); - if (!pos || !id) - { - return {nullptr, std::nullopt}; - } - pos = unpickle(pos, end, published); - if (!pos) return {nullptr, std::nullopt}; - - Botan::secure_vector<uint8_t> key_bits; - pos = unpickle(pos, end, key_bits); - if (!pos) return {nullptr, std::nullopt}; - - auto otk = OneTimeKey{id, published, Botan::X25519_PrivateKey(key_bits)}; - - return {pos, otk}; - } - - /** - * Serializes a FixedSizeArray object into a byte array. - * - * @tparam T The type of elements in the FixedSizeArray. - * @tparam max_size The maximum size of the FixedSizeArray. - * @param pos Pointer to the current position in the byte array. - * @param list The FixedSizeArray object to serialize. - * @return Pointer to the position in the byte array after the serialized data. - */ - template <typename T, std::size_t max_size> - std::uint8_t* pickle( - std::uint8_t* pos, - FixedSizeArray<T, max_size> const& list - ) - { - pos = pickle(pos, static_cast<std::uint32_t>(list.size())); - for (auto const& value : list) - { - pos = pickle(pos, *value); - } - return pos; - } - - /** - * Deserializes a FixedSizeArray object from a byte array. - * - * @tparam max_size The maximum size of the FixedSizeArray. - * @param pos Pointer to the current position in the byte array. - * @param end Pointer to the end of the byte array. - * @param list Reference to the FixedSizeArray object to store the deserialized values. - * @return Pointer to the position in the byte array after the deserialized data, or nullptr on failure. - */ - template <std::size_t max_size> - std::uint8_t const* unpickle( - std::uint8_t const* pos, std::uint8_t const* end, - FixedSizeArray<OneTimeKey, max_size>& list - ) - { - std::uint32_t size; - pos = unpickle(pos, end, size); - if (!pos) - { - return nullptr; - } - - while (size-- && pos != end) - { - auto [temp_pos, value] = unpickle_otk(pos, end); - if (!((pos = temp_pos))) return nullptr; - list.insert(value.value()); - } - - return pos; - } - - /** - * Serializes a uint8_t value into a byte array. - * - * @param pos Pointer to the current position in the byte array. - * @param value The uint8_t value to serialize. - * @return Pointer to the position in the byte array after the serialized data. - */ - std::uint8_t* pickle( - std::uint8_t* pos, - const std::uint8_t value - ) - { - *(pos++) = value; - return pos; - } - - /** - * Deserializes a uint8_t value from a byte array. - * - * @param pos Pointer to the current position in the byte array. - * @param end Pointer to the end of the byte array. - * @param value Reference to the uint8_t value to store the deserialized value. - * @return Pointer to the position in the byte array after the deserialized data, or nullptr on failure. - */ - std::uint8_t const* unpickle( - std::uint8_t const* pos, std::uint8_t const* end, - std::uint8_t& value - ) - { - if (!pos || pos == end) return nullptr; - value = *(pos++); - return pos; - } - - /** - * Serializes an optional IdentityKeys object into a byte array. - * - * @param pos Pointer to the current position in the byte array. - * @param value The optional IdentityKeys object to serialize. - * @return Pointer to the position in the byte array after the serialized data. - */ - std::uint8_t* pickle( - std::uint8_t* pos, - const std::optional<IdentityKeys>& value) - { - pos = pickle(pos, value->ed25519_key.public_key()->raw_public_key_bits()); - pos = pickle(pos, value->ed25519_key.raw_private_key_bits()); - pos = pickle(pos, value->curve25519_key.public_key()->raw_public_key_bits()); - return pickle(pos, value->curve25519_key.raw_private_key_bits()); - } - - /** - * Deserializes an optional IdentityKeys object from a byte array. - * - * @param pos Pointer to the current position in the byte array. - * @param end Pointer to the end of the byte array. - * @param value Reference to the optional IdentityKeys object to store the deserialized value. - * @return Pointer to the position in the byte array after the deserialized data, or nullptr on failure. - */ - std::uint8_t const* unpickle( - std::uint8_t const* pos, std::uint8_t const* end, - std::optional<IdentityKeys>& value) - { - if (!pos || pos == end) return nullptr; - Botan::secure_vector<uint8_t> ed25519_public_key_bits; - Botan::secure_vector<uint8_t> curve25519_public_key_bits; - Botan::secure_vector<uint8_t> ed25519_private_key_bits; - Botan::secure_vector<uint8_t> curve25519_private_key_bits; - - pos = unpickle(pos, end, ed25519_public_key_bits); - UNPICKLE_OK(pos); - pos = unpickle(pos, end, ed25519_private_key_bits); - UNPICKLE_OK(pos); - pos = unpickle(pos, end, curve25519_public_key_bits); - UNPICKLE_OK(pos); - pos = unpickle(pos, end, curve25519_private_key_bits); - UNPICKLE_OK(pos); - value = IdentityKeys{ - Botan::Ed25519_PrivateKey(ed25519_private_key_bits), - Botan::X25519_PrivateKey(curve25519_private_key_bits) - }; - return pos; - } - - /** - * Deserializes an optional OneTimeKey object from a byte array. - * - * @param pos Pointer to the current position in the byte array. - * @param end Pointer to the end of the byte array. - * @param value Reference to the optional OneTimeKey object to store the deserialized value. - * @return Pointer to the position in the byte array after the deserialized data, or nullptr on failure. - */ - std::uint8_t const* unpickle( - std::uint8_t const* pos, std::uint8_t const* end, - std::optional<OneTimeKey>& value) - { - if (!pos || pos == end) return nullptr; - std::uint32_t id; - bool published; - Botan::secure_vector<uint8_t> key_bits; - pos = unpickle(pos, end, id); - UNPICKLE_OK(pos); - pos = unpickle(pos, end, published); - UNPICKLE_OK(pos); - pos = unpickle(pos, end, key_bits); - UNPICKLE_OK(pos); - value = OneTimeKey{id, published, Botan::X25519_PrivateKey(key_bits)}; - return pos; - } - - void Account::new_account(Botan::RandomNumberGenerator& rng) - { - identity_keys = IdentityKeys{ - Botan::Ed25519_PrivateKey(rng), - Botan::X25519_PrivateKey(rng) - }; + identity_keys = IdentityKeys{Botan::Ed25519_PrivateKey(rng), Botan::X25519_PrivateKey(rng)}; // Make sure we check the key pairs. - if (!identity_keys->ed25519_key.check_key(rng, false) || - !identity_keys->curve25519_key.check_key(rng, false) || + if (!identity_keys->ed25519_key.check_key(rng, false) || !identity_keys->curve25519_key.check_key(rng, false) || !identity_keys->ed25519_key.public_key()->check_key(rng, false) || - !identity_keys->curve25519_key.public_key()->check_key(rng, false) - ) + !identity_keys->curve25519_key.public_key()->check_key(rng, false)) { throw SpankOlmErrorKeyGeneration(); } } - std::vector<uint8_t> Account::sign(Botan::RandomNumberGenerator& rng, const std::string_view message) const + std::vector<uint8_t> Account::sign(Botan::RandomNumberGenerator &rng, const std::string_view message) const { // According to https://botan.randombit.net/handbook/api_ref/pubkey.html#ed25519-ed448-variants const std::string padding_scheme = "Ed25519ph"; @@ -383,7 +39,7 @@ namespace spank_olm std::size_t Account::mark_keys_as_published() { auto count = 0; - for (const auto& key : one_time_keys) + for (const auto &key : one_time_keys) { if (!key->published) { @@ -396,7 +52,7 @@ namespace spank_olm return count; } - void Account::generate_one_time_keys(Botan::RandomNumberGenerator& rng, const std::size_t number_of_keys) + void Account::generate_one_time_keys(Botan::RandomNumberGenerator &rng, const std::size_t number_of_keys) { for (std::size_t i = 0; i < number_of_keys; ++i) { @@ -404,7 +60,7 @@ namespace spank_olm } } - void Account::generate_fallback_key(Botan::RandomNumberGenerator& rng) + void Account::generate_fallback_key(Botan::RandomNumberGenerator &rng) { prev_fallback_key = current_fallback_key; current_fallback_key = OneTimeKey{++next_one_time_key_id, false, Botan::X25519_PrivateKey(rng)}; @@ -419,32 +75,32 @@ namespace spank_olm } } - std::optional<OneTimeKey const*> Account::lookup_key(Botan::Public_Key const& key) const + std::optional<OneTimeKey const *> Account::lookup_key(Botan::Public_Key const &key) const { - for (const auto& one_time_key : one_time_keys) + for (const auto &one_time_key : one_time_keys) { if (one_time_key->key.public_key()->raw_public_key_bits() == key.raw_public_key_bits()) { return one_time_key; } } - if (current_fallback_key && current_fallback_key->key.public_key()->raw_public_key_bits() == key. - raw_public_key_bits()) + if (current_fallback_key && + current_fallback_key->key.public_key()->raw_public_key_bits() == key.raw_public_key_bits()) { return &current_fallback_key.value(); } - if (prev_fallback_key && prev_fallback_key->key.public_key()->raw_public_key_bits() == key. - raw_public_key_bits()) + if (prev_fallback_key && + prev_fallback_key->key.public_key()->raw_public_key_bits() == key.raw_public_key_bits()) { return &current_fallback_key.value(); } return std::nullopt; } - void Account::remove_key(Botan::Public_Key const& key) + void Account::remove_key(Botan::Public_Key const &key) { // Use iterator to find and remove the key. - for (const auto& one_time_key : one_time_keys) + for (const auto &one_time_key : one_time_keys) { if (one_time_key->key.public_key()->raw_public_key_bits() == key.raw_public_key_bits()) { @@ -461,19 +117,20 @@ namespace spank_olm * \brief The current version of the account pickle format. * * \details - * - Version 1 used only 32 bytes for the ed25519 private key. Any keys thus used should be considered compromised. + * - Version 1 used only 32 bytes for the ed25519 private key. Any keys thus used should be considered + * compromised. * - Version 2 does not have fallback keys. * - Version 3 does not store whether the current fallback key is published. */ constexpr std::uint32_t ACCOUNT_PICKLE_VERSION = 4; - } + } // namespace /** - * Serializes the Account object into a byte array. - * - * @return A vector of uint8_t containing the serialized data. - */ + * Serializes the Account object into a byte array. + * + * @return A vector of uint8_t containing the serialized data. + */ std::vector<uint8_t> Account::pickle() const { std::vector<uint8_t> buffer(1024); // Initial buffer size, can be adjusted @@ -487,8 +144,10 @@ namespace spank_olm // Calculate the number of fallback keys std::uint8_t fallback_key_count = 0; - if (current_fallback_key && current_fallback_key->published) fallback_key_count++; - if (prev_fallback_key && prev_fallback_key->published) fallback_key_count++; + if (current_fallback_key && current_fallback_key->published) + fallback_key_count++; + if (prev_fallback_key && prev_fallback_key->published) + fallback_key_count++; // Serialize the fallback key count pos = spank_olm::pickle(pos, fallback_key_count); @@ -518,7 +177,7 @@ namespace spank_olm * @throws SpankOlmErrorUnknownPickleVersion if the pickle version is unknown. * @throws SpankOlmErrorCorruptedAccountPickle if the pickle data is corrupted. */ - Account Account::unpickle(std::vector<uint8_t> const& data) + Account Account::unpickle(std::vector<uint8_t> const &data) { Account value; auto pos = data.data(); @@ -605,4 +264,4 @@ namespace spank_olm return value; } -} +} // namespace spank_olm diff --git a/src/megolm.cpp b/src/megolm.cpp @@ -0,0 +1,168 @@ +#include "megolm.hpp" +#include "pickle.hpp" + +#include <botan/auto_rng.h> +#include <botan/hash.h> +#include <botan/mac.h> + + +/* Convenience macro for checking the return value of internal unpickling + * functions and returning early on failure. */ +#ifndef UNPICKLE_OK +#define UNPICKLE_OK(x) \ + do \ + { \ + if (!(x)) \ + return nullptr; \ + } \ + while (0) +#endif + +/* the seeds used in the HMAC-SHA-256 functions for each part of the ratchet. + */ +#define HASH_KEY_SEED_LENGTH 1 +static uint8_t HASH_KEY_SEEDS[MEGOLM_RATCHET_PARTS][HASH_KEY_SEED_LENGTH] = {{0x00}, {0x01}, {0x02}, {0x03}}; + + +namespace spank_olm +{ + constexpr size_t UINT32_LENGTH = 4; + + std::uint8_t *_olm_pickle_uint32(std::uint8_t *pos, uint32_t const value) { return pickle(pos, value); } + + + std::uint8_t const *_olm_unpickle_uint32(std::uint8_t const *pos, std::uint8_t const *end, std::uint32_t *value) + { + return unpickle(pos, end, *value); + } + + + std::uint8_t *_olm_pickle_bytes(std::uint8_t *pos, std::uint8_t const *bytes, size_t bytes_length) + { + return pickle_bytes(pos, bytes, bytes_length); + } + + std::uint8_t const *_olm_unpickle_bytes(std::uint8_t const *pos, std::uint8_t const *end, std::uint8_t *bytes, + size_t bytes_length) + { + return unpickle_bytes(pos, end, bytes, bytes_length); + } + + + static void + rehash_part(std::array<std::array<std::uint8_t, MEGOLM_RATCHET_PART_LENGTH>, MEGOLM_RATCHET_PARTS> &data, + const int rehash_from_part, const int rehash_to_part) + { + const auto hmac = Botan::MessageAuthenticationCode::create_or_throw("HMAC(SHA-256)"); + + hmac->set_key(HASH_KEY_SEEDS[rehash_to_part], HASH_KEY_SEED_LENGTH); + hmac->update(data[rehash_from_part].data(), MEGOLM_RATCHET_PART_LENGTH); + hmac->final(data[rehash_to_part].data()); + } + + + void Megolm::init(Botan::RandomNumberGenerator &rng, const unsigned int counter) + { + this->counter = counter; + for (auto &part : data) + { + rng.randomize(part.data(), part.size()); + } + } + + size_t Megolm::pickle_length() const { return data.size() * data[0].size() + UINT32_LENGTH; } + + std::uint8_t *Megolm::pickle(std::uint8_t *pos) const + { + + pos = _olm_pickle_bytes(pos, get_data(), MEGOLM_RATCHET_LENGTH); + pos = _olm_pickle_uint32(pos, counter); + return pos; + } + + std::uint8_t const *Megolm::unpickle(std::uint8_t const *pos, const std::uint8_t *end) + { + pos = _olm_unpickle_bytes(pos, end, const_cast<std::uint8_t *>(get_data()), MEGOLM_RATCHET_LENGTH); + UNPICKLE_OK(pos); + + pos = _olm_unpickle_uint32(pos, end, &counter); + UNPICKLE_OK(pos); + + return pos; + } + + void Megolm::advance() + { + counter++; + uint32_t mask = 0x00FFFFFF; + int h = 0; + + /* figure out how much we need to rekey */ + while (h < MEGOLM_RATCHET_PARTS && (counter & mask)) + { + h++; + mask >>= 8; + } + + /* now update R(h)...R(3) based on R(h) */ + for (int i = MEGOLM_RATCHET_PARTS - 1; i >= h; i--) + { + rehash_part(data, h, i); + } + } + + void Megolm::advance(const unsigned int advance_to) + { + /* starting with R0, see if we need to update each part of the hash */ + for (int j = 0; j < MEGOLM_RATCHET_PARTS; j++) + { + const int shift = (MEGOLM_RATCHET_PARTS - j - 1) * 8; + const uint32_t mask = (~static_cast<uint32_t>(0)) << shift; + + + /* how many times do we need to rehash this part? + * + * '& 0xff' ensures we handle integer wraparound correctly + */ + unsigned int steps = ((advance_to >> shift) - (counter >> shift)) & 0xff; + + if (steps == 0) + { + /* deal with the edge case where megolm->counter is slightly larger + * than advance_to. This should only happen for R(0), and implies + * that advance_to has wrapped around and we need to advance R(0) + * 256 times. + */ + if (advance_to < counter) + { + steps = 0x100; + } + else + { + continue; + } + } + + /* for all but the last step, we can just bump R(j) without regard + * to R(j+1)...R(3). + */ + while (steps-- > 1) + { + rehash_part(data, j, j); + } + + /* on the last step we also need to bump R(j+1)...R(3). + * + * (Theoretically, we could skip bumping R(j+2) if we're going to bump + * R(j+1) again, but the code to figure that out is a bit baroque and + * doesn't save us much). + */ + for (int k = 3; k >= j; k--) + { + rehash_part(data, j, k); + } + counter = advance_to & mask; + } + } + +} // namespace spank_olm diff --git a/src/pickle.cpp b/src/pickle.cpp @@ -0,0 +1,365 @@ +#include "pickle.hpp" + +#include <botan/x25519.h> + +/* Convenience macro for checking the return value of internal unpickling + * functions and returning early on failure. */ +#ifndef UNPICKLE_OK +#define UNPICKLE_OK(x) \ + do \ + { \ + if (!(x)) \ + return nullptr; \ + } \ + while (0) +#endif + + +namespace spank_olm +{ + + /** + * Serializes a 32-bit unsigned integer into a byte array. + * + * @param pos Pointer to the current position in the byte array. + * @param value The 32-bit unsigned integer to serialize. + * @return Pointer to the position in the byte array after the serialized data. + */ + std::uint8_t *pickle(std::uint8_t *pos, std::uint32_t value) + { + for (int i = 3; i >= 0; --i) + { + pos[i] = value & 0xFF; + value >>= 8; + } + return pos + 4; + } + + /** + * Deserializes a 32-bit unsigned integer from a byte array. + * + * @param pos Pointer to the current position in the byte array. + * @param end Pointer to the end of the byte array. + * @param value Reference to the 32-bit unsigned integer to store the deserialized value. + * @return Pointer to the position in the byte array after the deserialized data, or nullptr on failure. + */ + std::uint8_t const *unpickle(std::uint8_t const *pos, std::uint8_t const *end, std::uint32_t &value) + { + if (!pos || end < pos + 4) + return nullptr; + value = (pos[0] << 24) | (pos[1] << 16) | (pos[2] << 8) | pos[3]; + return pos + 4; + } + + /** + * Serializes a boolean value into a byte array. + * + * @param pos Pointer to the current position in the byte array. + * @param value The boolean value to serialize. + * @return Pointer to the position in the byte array after the serialized data. + */ + std::uint8_t *pickle(std::uint8_t *pos, const bool value) + { + *(pos++) = value ? 1 : 0; + return pos; + } + + /** + * Deserializes a boolean value from a byte array. + * + * @param pos Pointer to the current position in the byte array. + * @param end Pointer to the end of the byte array. + * @param value Reference to the boolean value to store the deserialized value. + * @return Pointer to the position in the byte array after the deserialized data, or nullptr on failure. + */ + std::uint8_t const *unpickle(std::uint8_t const *pos, std::uint8_t const *end, bool &value) + { + if (!pos || end <= pos) + return nullptr; + value = *(pos++) != 0; + return pos; + } + + /** + * Serializes a Botan::secure_vector<uint8_t> into a byte array. + * + * @param pos Pointer to the current position in the byte array. + * @param value The Botan::secure_vector<uint8_t> to serialize. + * @return Pointer to the position in the byte array after the serialized data. + */ + std::uint8_t *pickle(std::uint8_t *pos, const Botan::secure_vector<uint8_t> &value) + { + pos = pickle(pos, static_cast<std::uint32_t>(value.size())); + for (const auto byte : value) + { + *(pos++) = byte; + } + return pos; + } + + /** + * Deserializes a Botan::secure_vector<uint8_t> from a byte array. + * + * @param pos Pointer to the current position in the byte array. + * @param end Pointer to the end of the byte array. + * @param value Reference to the Botan::secure_vector<uint8_t> to store the deserialized value. + * @return Pointer to the position in the byte array after the deserialized data, or nullptr on failure. + */ + std::uint8_t const *unpickle(std::uint8_t const *pos, std::uint8_t const *end, Botan::secure_vector<uint8_t> &value) + { + std::uint32_t size; + pos = unpickle(pos, end, size); + if (!pos || end < pos + size) + return nullptr; + value.assign(pos, pos + size); + return pos + size; + } + + + /** + * Serializes a std::vector<uint8_t> into a byte array. + * + * @param pos Pointer to the current position in the byte array. + * @param value The Botan::secure_vector<uint8_t> to serialize. + * @return Pointer to the position in the byte array after the serialized data. + */ + std::uint8_t *pickle(std::uint8_t *pos, const std::vector<uint8_t> &value) + { + pos = pickle(pos, static_cast<std::uint32_t>(value.size())); + for (const auto byte : value) + { + *(pos++) = byte; + } + return pos; + } + + /** + * Deserializes a std::vector<uint8_t> from a byte array. + * + * @param pos Pointer to the current position in the byte array. + * @param end Pointer to the end of the byte array. + * @param value Reference to the Botan::secure_vector<uint8_t> to store the deserialized value. + * @return Pointer to the position in the byte array after the deserialized data, or nullptr on failure. + */ + std::uint8_t const *unpickle(std::uint8_t const *pos, std::uint8_t const *end, std::vector<uint8_t> &value) + { + std::uint32_t size; + pos = unpickle(pos, end, size); + if (!pos || end < pos + size) + return nullptr; + value.assign(pos, pos + size); + return pos + size; + } + + /** + * + * @param pos Pointer to the current position in the byte array. + * @param value A OneTimeKey object to serialize. + * @return Pointer to the position in the byte array after the serialized data. + */ + std::uint8_t *pickle(std::uint8_t *pos, const std::optional<OneTimeKey> &value) + { + pos = pickle(pos, value->id); + pos = pickle(pos, value->published); + return pickle(pos, value->key.raw_private_key_bits()); + } + + /** + * Deserializes a OneTimeKey object from a byte array. + * + * @param pos Pointer to the current position in the byte array. + * @param end Pointer to the end of the byte array. + * @return A pair containing the pointer to the position in the byte array after the deserialized data and the + * deserialized OneTimeKey object. + */ + static std::pair<std::uint8_t const *, std::optional<OneTimeKey>> unpickle_otk(std::uint8_t const *pos, + std::uint8_t const *end) + { + std::uint32_t id; ///< The unique identifier for the one-time key. + bool published; ///< Indicates whether the key has been published. + + pos = unpickle(pos, end, id); + if (!pos || !id) + { + return {nullptr, std::nullopt}; + } + pos = unpickle(pos, end, published); + if (!pos) + return {nullptr, std::nullopt}; + + Botan::secure_vector<uint8_t> key_bits; + pos = unpickle(pos, end, key_bits); + if (!pos) + return {nullptr, std::nullopt}; + + auto otk = OneTimeKey{id, published, Botan::X25519_PrivateKey(key_bits)}; + + return {pos, otk}; + } + + /** + * Serializes a FixedSizeArray object into a byte array. + * + * @tparam T The type of elements in the FixedSizeArray. + * @tparam max_size The maximum size of the FixedSizeArray. + * @param pos Pointer to the current position in the byte array. + * @param list The FixedSizeArray object to serialize. + * @return Pointer to the position in the byte array after the serialized data. + */ + template <typename T, std::size_t max_size> + std::uint8_t *pickle(std::uint8_t *pos, FixedSizeArray<T, max_size> const &list) + { + pos = pickle(pos, static_cast<std::uint32_t>(list.size())); + for (auto const &value : list) + { + pos = pickle(pos, *value); + } + return pos; + } + + /** + * Deserializes a FixedSizeArray object from a byte array. + * + * @tparam max_size The maximum size of the FixedSizeArray. + * @param pos Pointer to the current position in the byte array. + * @param end Pointer to the end of the byte array. + * @param list Reference to the FixedSizeArray object to store the deserialized values. + * @return Pointer to the position in the byte array after the deserialized data, or nullptr on failure. + */ + template <std::size_t max_size> + std::uint8_t const *unpickle(std::uint8_t const *pos, std::uint8_t const *end, + FixedSizeArray<OneTimeKey, max_size> &list) + { + std::uint32_t size; + pos = unpickle(pos, end, size); + if (!pos) + { + return nullptr; + } + + while (size-- && pos != end) + { + auto [temp_pos, value] = unpickle_otk(pos, end); + if (!((pos = temp_pos))) + return nullptr; + list.insert(value.value()); + } + + return pos; + } + + /** + * Serializes a uint8_t value into a byte array. + * + * @param pos Pointer to the current position in the byte array. + * @param value The uint8_t value to serialize. + * @return Pointer to the position in the byte array after the serialized data. + */ + std::uint8_t *pickle(std::uint8_t *pos, const std::uint8_t value) + { + *(pos++) = value; + return pos; + } + + /** + * Deserializes a uint8_t value from a byte array. + * + * @param pos Pointer to the current position in the byte array. + * @param end Pointer to the end of the byte array. + * @param value Reference to the uint8_t value to store the deserialized value. + * @return Pointer to the position in the byte array after the deserialized data, or nullptr on failure. + */ + std::uint8_t const *unpickle(std::uint8_t const *pos, std::uint8_t const *end, std::uint8_t &value) + { + if (!pos || pos == end) + return nullptr; + value = *(pos++); + return pos; + } + + /** + * Serializes an optional IdentityKeys object into a byte array. + * + * @param pos Pointer to the current position in the byte array. + * @param value The optional IdentityKeys object to serialize. + * @return Pointer to the position in the byte array after the serialized data. + */ + std::uint8_t *pickle(std::uint8_t *pos, const std::optional<IdentityKeys> &value) + { + pos = pickle(pos, value->ed25519_key.public_key()->raw_public_key_bits()); + pos = pickle(pos, value->ed25519_key.raw_private_key_bits()); + pos = pickle(pos, value->curve25519_key.public_key()->raw_public_key_bits()); + return pickle(pos, value->curve25519_key.raw_private_key_bits()); + } + + /** + * Deserializes an optional IdentityKeys object from a byte array. + * + * @param pos Pointer to the current position in the byte array. + * @param end Pointer to the end of the byte array. + * @param value Reference to the optional IdentityKeys object to store the deserialized value. + * @return Pointer to the position in the byte array after the deserialized data, or nullptr on failure. + */ + std::uint8_t const *unpickle(std::uint8_t const *pos, std::uint8_t const *end, std::optional<IdentityKeys> &value) + { + if (!pos || pos == end) + return nullptr; + Botan::secure_vector<uint8_t> ed25519_public_key_bits; + Botan::secure_vector<uint8_t> curve25519_public_key_bits; + Botan::secure_vector<uint8_t> ed25519_private_key_bits; + Botan::secure_vector<uint8_t> curve25519_private_key_bits; + + pos = unpickle(pos, end, ed25519_public_key_bits); + UNPICKLE_OK(pos); + pos = unpickle(pos, end, ed25519_private_key_bits); + UNPICKLE_OK(pos); + pos = unpickle(pos, end, curve25519_public_key_bits); + UNPICKLE_OK(pos); + pos = unpickle(pos, end, curve25519_private_key_bits); + UNPICKLE_OK(pos); + value = IdentityKeys{Botan::Ed25519_PrivateKey::from_bytes(ed25519_private_key_bits), + Botan::X25519_PrivateKey(curve25519_private_key_bits)}; + return pos; + } + + /** + * Deserializes an optional OneTimeKey object from a byte array. + * + * @param pos Pointer to the current position in the byte array. + * @param end Pointer to the end of the byte array. + * @param value Reference to the optional OneTimeKey object to store the deserialized value. + * @return Pointer to the position in the byte array after the deserialized data, or nullptr on failure. + */ + std::uint8_t const *unpickle(std::uint8_t const *pos, std::uint8_t const *end, std::optional<OneTimeKey> &value) + { + if (!pos || pos == end) + return nullptr; + std::uint32_t id; + bool published; + Botan::secure_vector<uint8_t> key_bits; + pos = unpickle(pos, end, id); + UNPICKLE_OK(pos); + pos = unpickle(pos, end, published); + UNPICKLE_OK(pos); + pos = unpickle(pos, end, key_bits); + UNPICKLE_OK(pos); + value = OneTimeKey{id, published, Botan::X25519_PrivateKey(key_bits)}; + return pos; + } + + std::uint8_t *pickle_bytes(std::uint8_t *pos, const std::uint8_t *bytes, const std::size_t bytes_length) + { + std::memcpy(pos, bytes, bytes_length); + return pos + bytes_length; + } + + std::uint8_t const *unpickle_bytes(const std::uint8_t *pos, const std::uint8_t *end, std::uint8_t *bytes, + const std::size_t bytes_length) + { + if (!pos || end < pos + bytes_length) + return nullptr; + std::memcpy(bytes, pos, bytes_length); + return pos + bytes_length; + } + +} // namespace spank_olm