diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cd0511b..d5abc07 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,6 +19,7 @@ jobs: matrix: os: [windows-latest, ubuntu-latest, macos-latest] crypto: [OPENSSL_1_1, OPENSSL_3, BORINGSSL] + no_alloc: [OFF, ON] include: - os: windows-latest vcpkg-cmake-file: "$env:VCPKG_INSTALLATION_ROOT\\scripts\\buildsystems\\vcpkg.cmake" @@ -58,8 +59,12 @@ jobs: key: ${{ runner.os }}-${{ hashFiles( '**/vcpkg.json' ) }} - name: configure to use clang-tidy and sanitizers - run: > - cmake -B "${{ env.CMAKE_BUILD_DIR }}" -DTESTING=ON -DCLANG_TIDY=ON -DSANITIZERS=ON -DCMAKE_TOOLCHAIN_FILE="${{ matrix.vcpkg-cmake-file}}" -DCRYPTO="${{ matrix.crypto }}" -DVCPKG_MANIFEST_DIR="alternatives/${{ matrix.crypto }}" + run: cmake -B "${{ env.CMAKE_BUILD_DIR }}" + -DTESTING=ON -DCLANG_TIDY=ON -DSANITIZERS=ON + -DCMAKE_TOOLCHAIN_FILE="${{ matrix.vcpkg-cmake-file}}" + -DCRYPTO="${{ matrix.crypto }}" + -DVCPKG_MANIFEST_DIR="alternatives/${{ matrix.crypto }}" + -DNO_ALLOC="${{ matrix.no_alloc }}" - name: build run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index 4520438..b25351b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,7 @@ project(sframe option(TESTING "Build tests" OFF) option(CLANG_TIDY "Perform linting with clang-tidy" OFF) option(SANITIZERS "Enable sanitizers" OFF) +option(NO_ALLOC "Build without needing an allocator" OFF) # Use -DCRYPTO=(OPENSSL_1_1 | OPENSSL_3 | BORINGSSL) to configure crypto if(NOT DEFINED CRYPTO) @@ -49,6 +50,11 @@ if(CLANG_TIDY) endif() endif() +if(NO_ALLOC) + message(STATUS "Configuring no-allocator version") + add_definitions(-DNO_ALLOC) +endif() + ### ### Dependencies ### diff --git a/Makefile b/Makefile index ab4def2..4d032d3 100644 --- a/Makefile +++ b/Makefile @@ -38,6 +38,9 @@ devB: CMakeLists.txt test/CMakeLists.txt -DCLANG_TIDY=ON -DSANITIZERS=ON \ -DCRYPTO=BORINGSSL -DVCPKG_MANIFEST_DIR=${BORINGSSL_MANIFEST} +dev-nostd: CMakeLists.txt test/CMakeLists.txt + cmake -B${BUILD_DIR} -DCMAKE_BUILD_TYPE=Debug -DCLANG_TIDY=ON -DTESTING=ON -DSANITIZERS=ON -DNO_ALLOC=ON . + ${TEST_BIN}: ${LIB} test/* cmake --build ${BUILD_DIR} --target sframe_test diff --git a/include/sframe/map.h b/include/sframe/map.h index fcba567..b404181 100644 --- a/include/sframe/map.h +++ b/include/sframe/map.h @@ -1,7 +1,11 @@ #pragma once +#ifdef NO_ALLOC + #include +namespace sframe { + template class map : private vector>, N> { @@ -64,3 +68,44 @@ class map : private vector>, N> std::replace_if(this->begin(), this->end(), to_erase, std::nullopt); } }; + +} // namespace sframe + +#else // ifdef NO_ALLOC + +#include + +namespace sframe { + +// NOTE: NOT RECOMMENDED FOR USE OUTSIDE THIS LIBRARY +// +// We have used public inheritance from std::map to simplify the interface +// here. This works fine for the use cases we have within this library. If you +// choose to use this map type outside this library, you MUST NOT store it as a +// std::map pointer or reference. This will cause memory leaks, because the +// destructor ~std::map is not virtual. +template +class map : public std::map +{ +private: + using parent = std::map; + +public: + bool contains(const K& key) const { return this->count(key) > 0; } + + template + void erase_if_key(F&& f) + { + for (auto iter = this->begin(); iter != this->end();) { + if (f(iter->first)) { + iter = this->erase(iter); + } else { + ++iter; + } + } + } +}; + +} // namespace sframe + +#endif // def NO_ALLOC diff --git a/include/sframe/sframe.h b/include/sframe/sframe.h index ec87aa8..ba38e2a 100644 --- a/include/sframe/sframe.h +++ b/include/sframe/sframe.h @@ -6,6 +6,22 @@ #include #include +// These constants define the size of certain internal data structures if +// we are configured not to depend on dynamic allocations, i.e., if the NO_ALLOC +// flag is set. If you are using an allocator, you can ignore them. +// +// Note that these constants must be the same at the time when the library is +// built as at the time when it is used. If you are using a pre-built binary, +// you must make sure that these parameters have the same values as when the +// library was built. +#ifndef SFRAME_MAX_KEYS +#define SFRAME_MAX_KEYS 200 +#endif + +#ifndef SFRAME_EPOCH_BITS +#define SFRAME_EPOCH_BITS 4 +#endif + namespace sframe { struct crypto_error : std::runtime_error @@ -58,7 +74,6 @@ using owned_bytes = vector; using KeyID = uint64_t; using Counter = uint64_t; - class Header; enum struct KeyUsage @@ -106,10 +121,8 @@ class Context static constexpr size_t max_metadata_size = 512; protected: - static constexpr size_t max_keys = 200; - CipherSuite suite; - map keys; + map keys; output_bytes protect_inner(const Header& header, output_bytes ciphertext, @@ -185,8 +198,7 @@ class MLSContext : protected Context const size_t epoch_bits; const size_t epoch_mask; - // XXX(RLB) Make this an attribute of the class? - static constexpr size_t max_epochs = 16; + static constexpr size_t max_epochs = 1 << SFRAME_EPOCH_BITS; vector, max_epochs> epoch_cache; }; diff --git a/include/sframe/vector.h b/include/sframe/vector.h index 874793a..649d2c3 100644 --- a/include/sframe/vector.h +++ b/include/sframe/vector.h @@ -2,6 +2,10 @@ #include +#ifdef NO_ALLOC + +namespace sframe { + template class vector { @@ -81,3 +85,58 @@ class vector operator gsl::span() const { return gsl::span(_data).first(_size); } operator gsl::span() { return gsl::span(_data).first(_size); } }; + +} // namespace sframe + +#else // ifdef NO_ALLOC + +#include + +namespace sframe { + +// NOTE: NOT RECOMMENDED FOR USE OUTSIDE THIS LIBRARY +// +// We have used public inheritance from std::vector to simplify the interface +// here. This works fine for the use cases we have within this library. If you +// choose to use this vector type outside this library, you MUST NOT store it as +// a std::vector pointer or reference. This will cause memory leaks, because +// the destructor ~std::vector is not virtual. +template +class vector : public std::vector +{ +private: + using parent = std::vector; + +public: + constexpr vector() + : parent(N) + { + } + + constexpr vector(size_t size) + : parent(size) + { + } + + constexpr vector(gsl::span content) + : parent(content.begin(), content.end()) + { + } + + template + constexpr vector(const vector& content) + : parent(content) + { + } + + void append(gsl::span content) + { + const auto start = this->size(); + this->resize(start + content.size()); + std::copy(content.begin(), content.end(), this->begin() + start); + } +}; + +} // namespace sframe + +#endif // def NO_ALLOC diff --git a/src/crypto.cpp b/src/crypto.cpp index 777ea02..92f9ca0 100644 --- a/src/crypto.cpp +++ b/src/crypto.cpp @@ -1,9 +1,6 @@ #include "crypto.h" #include "header.h" -#include -#include - namespace sframe { size_t diff --git a/src/crypto_openssl11.cpp b/src/crypto_openssl11.cpp index 5218029..b6f650a 100644 --- a/src/crypto_openssl11.cpp +++ b/src/crypto_openssl11.cpp @@ -149,23 +149,22 @@ hkdf_expand(CipherSuite suite, input_bytes prk, input_bytes info, size_t size) auto block = owned_bytes(0); const auto block_size = cipher_digest_size(suite); - auto counter = uint8_t(0x01); + auto counter = owned_bytes<1>(); + counter[0] = 0x01; while (out.size() < size) { - // for (auto start = size_t(0); start < out.size(); start += block_size) { auto h = HMAC(suite, prk); h.write(block); h.write(info); - h.write(owned_bytes<1>{ counter }); + h.write(counter); - block.resize(block.capacity()); - const auto md = h.digest(block); - block.resize(md.size()); + block.resize(block_size); + h.digest(block); const auto remaining = size - out.size(); const auto to_write = (remaining < block_size) ? remaining : block_size; out.append(input_bytes(block).first(to_write)); - counter += 1; + counter[0] += 1; } return out;