spank-olm

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

commit a147d1b9bec2045f5d08065c98400bf8aef81586
parent 0a00ab9eb0d8eda4f63dd42d03fd9d4f6f28ca11
Author: MTRNord <mtrnord1@gmail.com>
Date:   Wed,  7 Aug 2024 21:18:15 +0200

Implement basic wasm interface

Diffstat:
MREADME.md | 7+++++++
Mfuzz/StandaloneFuzzTargetMain.c | 20+++++++++++---------
Minclude/account.hpp | 22+++++++++++++++-------
Minclude/list.hpp | 16++++++++++++++++
Mmeson.build | 73+++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mmeson_options.txt | 11+++++++++++
Msrc/account.cpp | 6++----
Msrc/spank-olm.cpp | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mtests/account_test.cpp | 2+-
Mwasm-cross-file.txt | 2+-
10 files changed, 172 insertions(+), 52 deletions(-)

diff --git a/README.md b/README.md @@ -39,6 +39,13 @@ meson setup build_static --default-library=static meson compile -C build_static ``` +To build wasm libraries: + +```sh +meson setup build-wasm --cross-file wasm-cross-file.txt --default-library=static -Dbotan_wasm_path=../botan/ -Dbotan_include_path=../botan/build/include/public/ -Dcpp_std=c++2a +meson compile -C build-wasm +``` + ## Usage Include `spank_olm` in your C++ project and link against it. Refer to the source code for examples of how to use the diff --git a/fuzz/StandaloneFuzzTargetMain.c b/fuzz/StandaloneFuzzTargetMain.c @@ -18,24 +18,27 @@ #include <stdlib.h> #include <string.h> -extern int LLVMFuzzerTestOneInput(const unsigned char *data, size_t size); -extern int LLVMFuzzerInitialize(int *argc, char ***argv); -int main(int argc, char **argv) { - const char *progname; +extern int LLVMFuzzerTestOneInput(const unsigned char* data, size_t size); +extern int LLVMFuzzerInitialize(int* argc, char*** argv); + +int main(int argc, char** argv) +{ + const char* progname; if ((progname = strrchr(argv[0], '/'))) progname++; else progname = argv[0]; fprintf(stderr, "%s: running %d inputs\n", progname, argc - 1); LLVMFuzzerInitialize(&argc, &argv); - for (int i = 1; i < argc; i++) { + 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); fseek(f, 0, SEEK_SET); - unsigned char *buf = (unsigned char*)malloc(len); + unsigned char* buf = (unsigned char*)malloc(len); size_t n_read = fread(buf, 1, len, f); fclose(f); assert(n_read == len); @@ -43,4 +46,4 @@ int main(int argc, char **argv) { free(buf); fprintf(stderr, "Done: %s: (%zd bytes)\n", argv[i], n_read); } -} -\ No newline at end of file +} diff --git a/include/account.hpp b/include/account.hpp @@ -7,6 +7,13 @@ #include "list.hpp" +// Define a macro to detect Emscripten +#ifdef __EMSCRIPTEN__ +#define EMSCRIPTEN_CONSTEXPR +#else +#define EMSCRIPTEN_CONSTEXPR constexpr +#endif + namespace spank_olm { /** @@ -43,7 +50,7 @@ namespace spank_olm { } - Account(Account const&); + Account(Account const& other) = default; std::optional<IdentityKeys> identity_keys; ///< The identity keys for the account. FixedSizeArray<OneTimeKey, MAX_ONE_TIME_KEYS> one_time_keys; ///< The one-time keys for the account. @@ -74,7 +81,7 @@ namespace spank_olm * * \return The JSON representation of the identity keys. */ - [[nodiscard]] constexpr std::string get_identity_json() const + [[nodiscard]] EMSCRIPTEN_CONSTEXPR std::string get_identity_json() const { auto curve25519_key = identity_keys->curve25519_key.public_key()->raw_public_key_bits(); auto ed25519_key = identity_keys->ed25519_key.public_key()->raw_public_key_bits(); @@ -89,10 +96,11 @@ namespace spank_olm /** * \brief Signs a message using the Ed25519 key. * - * @param message The message to sign. - * @return The signature of the message. + * \param rng The botan random number generator to use. + * \param message The message to sign. + * \return The signature of the message. */ - [[nodiscard]] std::vector<uint8_t> sign(std::string_view message) const; + [[nodiscard]] std::vector<uint8_t> sign(Botan::RandomNumberGenerator& rng, std::string_view message) const; /** @@ -112,7 +120,7 @@ namespace spank_olm * * @return Returns the JSON representation of the one time keys which haven't been published yet. */ - [[nodiscard]] constexpr std::string get_one_time_keys_json() + [[nodiscard]] EMSCRIPTEN_CONSTEXPR std::string get_one_time_keys_json() { std::vector<std::string> stringified_keys; @@ -188,7 +196,7 @@ namespace spank_olm * } * ``` */ - [[nodiscard]] std::string constexpr get_unpublished_fallback_key_json() const + [[nodiscard]] std::string EMSCRIPTEN_CONSTEXPR get_unpublished_fallback_key_json() const { if (!current_fallback_key || current_fallback_key->published) { diff --git a/include/list.hpp b/include/list.hpp @@ -27,6 +27,22 @@ namespace spank_olm } /** + * \brief Copy constructor. + * + * \param other The FixedSizeArray to copy from. + */ + FixedSizeArray(const FixedSizeArray& other) + : current_size(other.current_size) + { + data = std::make_unique<T*[]>(max_size + 1); + for (std::size_t i = 0; i < other.current_size; ++i) + { + data[i] = new T(*other.data[i]); + } + } + + + /** * \brief Destroys the FixedSizeArray and frees allocated memory. */ ~FixedSizeArray() diff --git a/meson.build b/meson.build @@ -26,12 +26,22 @@ is_wasm = host_machine.system() == 'emscripten' # Add specific arguments for WASM if is_wasm - add_project_arguments('-s', 'WASM=1', '-s', 'MODULARIZE=1', '-s', 'EXPORT_NAME="createModule"', '-flto', language : 'cpp') - add_project_link_arguments('-s', 'WASM=1', '-s', 'MODULARIZE=1', '-s', 'EXPORT_NAME="createModule"', '-flto', language : 'cpp') + 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') endif # Cmake doesnt work with meson, so we need to require pkg-config -botan_dep = dependency('botan-3', version : '>=3.6.0', required : true, method : 'pkg-config') +if is_wasm + # Custom path for libbotan-3.a when targeting WASM + botan_wasm_path = get_option('botan_wasm_path') + botan_include_path = get_option('botan_include_path') + botan_incdir = include_directories(botan_include_path) + cc = meson.get_compiler('c') + botan_dep = cc.find_library('botan-3', dirs : meson.global_source_root() / botan_wasm_path, required : true, static : true) + botan_dep = declare_dependency(dependencies : botan_dep, include_directories : botan_incdir) +else + botan_dep = dependency('botan-3', version : '>=3.6.0', required : true, method : 'pkg-config') +endif spank_olm_deps = [botan_dep] @@ -39,7 +49,11 @@ incdir = include_directories('include') # List of source files src_files = files('src/spank-olm.cpp', 'src/account.cpp') -spank_olm = library('spank_olm', src_files, install : true, dependencies : spank_olm_deps, include_directories : incdir) +if is_wasm + spank_olm = executable('spank_olm', src_files, install : true, dependencies : spank_olm_deps, include_directories : incdir, override_options : ['b_lto=false']) +else + spank_olm = library('spank_olm', src_files, install : true, dependencies : spank_olm_deps, include_directories : incdir) +endif spank_olm_dep = declare_dependency( include_directories : incdir, @@ -47,32 +61,35 @@ spank_olm_dep = declare_dependency( dependencies : spank_olm_deps, ) -# Generate and install pkg-config file -pkgconfig = { - 'prefix' : get_option('prefix'), - 'libdir' : get_option('libdir'), - 'includedir' : join_paths(get_option('prefix'), get_option('includedir')), - 'name' : 'spank-olm', - 'description' : 'A C++ library based on libolm', - 'version' : meson.project_version(), - 'requires' : 'botan-3', - 'libs' : '-L${libdir} -lspank_olm', - 'cflags' : '-I${includedir}/spank-olm' -} - -configure_file( - input : 'misc/spank-olm.pc.in', - output : 'spank-olm.pc', - configuration : pkgconfig, - install : true, - install_dir : join_paths(get_option('libdir'), 'pkgconfig') -) - -if get_option('build_tests') +if get_option('build_tests') and not is_wasm snitch_dep = dependency('snitch') test('list_test', executable('list_test', 'tests/list_test.cpp', dependencies : [snitch_dep, spank_olm_dep], include_directories : incdir)) test('account_test', executable('account_test', 'tests/account_test.cpp', dependencies : [snitch_dep, spank_olm_dep], include_directories : incdir)) endif -subdir('fuzz') -\ No newline at end of file +# Only build if we are not building wasm +if not is_wasm + # Generate and install pkg-config file + pkgconfig = { + 'prefix' : get_option('prefix'), + 'libdir' : get_option('libdir'), + 'includedir' : join_paths(get_option('prefix'), get_option('includedir')), + 'name' : 'spank-olm', + 'description' : 'A C++ library based on libolm', + 'version' : meson.project_version(), + 'requires' : 'botan-3', + 'libs' : '-L${libdir} -lspank_olm', + 'cflags' : '-I${includedir}/spank-olm' + } + + configure_file( + input : 'misc/spank-olm.pc.in', + output : 'spank-olm.pc', + configuration : pkgconfig, + install : true, + install_dir : join_paths(get_option('libdir'), 'pkgconfig') + ) + + subdir('fuzz') +endif +\ No newline at end of file diff --git a/meson_options.txt b/meson_options.txt @@ -14,3 +14,13 @@ option('build_tests', type : 'boolean', value : true, description : 'Build unit tests') + +option('botan_wasm_path', + type : 'string', + value : '', + description : 'Custom path for libbotan-3.a when targeting WASM') + +option('botan_include_path', + type : 'string', + value : '', + description : 'Custom path for botan include files') +\ No newline at end of file diff --git a/src/account.cpp b/src/account.cpp @@ -2,7 +2,7 @@ #include "errors.hpp" #include <botan/pubkey.h> -#include <botan/auto_rng.h> +#include <botan/rng.h> /* Convenience macro for checking the return value of internal unpickling * functions and returning early on failure. */ @@ -365,10 +365,8 @@ namespace spank_olm } } - std::vector<uint8_t> Account::sign(const std::string_view message) const + std::vector<uint8_t> Account::sign(Botan::RandomNumberGenerator& rng, const std::string_view message) const { - Botan::AutoSeeded_RNG rng; - // According to https://botan.randombit.net/handbook/api_ref/pubkey.html#ed25519-ed448-variants const std::string padding_scheme = "Ed25519ph"; diff --git a/src/spank-olm.cpp b/src/spank-olm.cpp @@ -1 +1,63 @@ -#include "spank-olm.hpp" -\ No newline at end of file +#include "spank-olm.hpp" + +// Binding code for emscripten using embind. It should only be included when compiling for Emscripten. +#ifdef __EMSCRIPTEN__ +#include <emscripten/bind.h> +#include <botan/rng.h> +#include <botan/der_enc.h> + +EMSCRIPTEN_BINDINGS(spank_olm) +{ + using namespace emscripten; + class_<spank_olm::Account>("Account") + .constructor<>() + .function("new_account", &spank_olm::Account::new_account) + .function("sign", &spank_olm::Account::sign) + .function("mark_keys_as_published", &spank_olm::Account::mark_keys_as_published) + .function("generate_one_time_keys", &spank_olm::Account::generate_one_time_keys) + .function("generate_fallback_key", &spank_olm::Account::generate_fallback_key) + .function("forget_old_fallback_key", &spank_olm::Account::forget_old_fallback_key) + .function("lookup_key", &spank_olm::Account::lookup_key) + .function("remove_key", &spank_olm::Account::remove_key) + .function("pickle", &spank_olm::Account::pickle) + .function("unpickle", &spank_olm::Account::unpickle) + .property("identity_keys", &spank_olm::Account::identity_keys, return_value_policy::reference()) + .property("one_time_keys", &spank_olm::Account::one_time_keys, return_value_policy::reference()) + .property("current_fallback_key", &spank_olm::Account::current_fallback_key, return_value_policy::reference()) + .property("prev_fallback_key", &spank_olm::Account::prev_fallback_key, return_value_policy::reference()) + .property("next_one_time_key_id", &spank_olm::Account::next_one_time_key_id, return_value_policy::reference()); + + constant("MAX_ONE_TIME_KEYS", spank_olm::MAX_ONE_TIME_KEYS); + + register_optional<spank_olm::IdentityKeys>(); + register_optional<spank_olm::OneTimeKey>(); + register_optional<spank_olm::OneTimeKey const*>(); + + class_<Botan::Public_Key>("Public_Key"); + class_<Botan::X25519_PrivateKey>("X25519_PrivateKey"); + class_<Botan::RandomNumberGenerator>("RandomNumberGenerator"); + + + // Register Ed25519_PrivateKey which cant be default constructed + class_<Botan::Ed25519_PrivateKey>("Ed25519_PrivateKey"); + + // Register the secure_vector type for use in the Account struct. + // Note that this is downcasted to a vector of uint8_t instead of the original type. + register_vector<Botan::uint8_t>("SecureVector"); + + // Register string_view for use in the Account struct. + class_<std::string_view>("string_view") + .constructor<>(); + + // Register OneTimeKey which cant be default constructed + class_<spank_olm::OneTimeKey>("OneTimeKey") + .constructor<std::uint32_t, bool, Botan::X25519_PrivateKey>(); + + // Register IdentityKeys which cant be default constructed + class_<spank_olm::IdentityKeys>("IdentityKeys") + .constructor<Botan::Ed25519_PrivateKey, Botan::X25519_PrivateKey>(); + + // Register FixedSizeArray + class_<spank_olm::FixedSizeArray<spank_olm::OneTimeKey, spank_olm::MAX_ONE_TIME_KEYS>>("FixedSizeArrayOneTimeKeys"); +} +#endif diff --git a/tests/account_test.cpp b/tests/account_test.cpp @@ -34,7 +34,7 @@ TEST_CASE("Account sign and verify") account.new_account(rng); const std::string message = "Test message"; - auto signature = account.sign(message); + auto signature = account.sign(rng, message); Botan::PK_Verifier verifier(account.identity_keys->ed25519_key, "Ed25519ph"); verifier.update(message); diff --git a/wasm-cross-file.txt b/wasm-cross-file.txt @@ -3,7 +3,7 @@ c = 'emcc' cpp = 'em++' ar = 'emar' strip = 'llvm-strip' -pkgconfig = 'em-pkg-config' +pkg-config = 'pkg-config' [host_machine] system = 'emscripten'