commit a147d1b9bec2045f5d08065c98400bf8aef81586
parent 0a00ab9eb0d8eda4f63dd42d03fd9d4f6f28ca11
Author: MTRNord <mtrnord1@gmail.com>
Date: Wed, 7 Aug 2024 21:18:15 +0200
Implement basic wasm interface
Diffstat:
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'