diff --git a/cpp/src/CMakeLists.txt b/cpp/src/CMakeLists.txt index 1ad9784dd73c9b9c756253e4c86cf5dd72fbed2f..520ad243c7ff982e01c06d0d7539274c96135545 100644 --- a/cpp/src/CMakeLists.txt +++ b/cpp/src/CMakeLists.txt @@ -71,6 +71,7 @@ set(VEREIGNLIB_SRC vereign/encoding/base64.cc vereign/encoding/hex.cc + vereign/crypto/rand.cc vereign/crypto/aes.cc vereign/crypto/rsa.cc vereign/crypto/bio.cc @@ -80,6 +81,7 @@ set(VEREIGNLIB_SRC vereign/kvstore/detail/base_crypto_storage.cc vereign/kvstore/sqlite_storage.cc vereign/kvstore/crypto_storage.cc + vereign/kvstore/detail/value_encoder.cc vereign/identity/provider.cc diff --git a/cpp/src/csandbox.cc b/cpp/src/csandbox.cc index b6101f95b0638e92932a58599140295e224982ae..44b33e62bf2e2fef4195d45b73459ebcc0c7a9b2 100644 --- a/cpp/src/csandbox.cc +++ b/cpp/src/csandbox.cc @@ -1,24 +1,8 @@ -#include "vereign/crypto/rand.hh" -#include "vereign/kvstore/sqlite_storage.hh" -#include <boost/core/ignore_unused.hpp> -#include <iostream> -#include <boost/filesystem.hpp> -#include <openssl/digest.h> -#include <openssl/evp.h> -#include <vereign/core/string.hh> -#include <vereign/bytes/view.hh> -#include <vereign/bytes/view_dump.hh> -#include <vereign/fs/util.hh> -#include <vereign/fs/path.hh> -#include <vereign/fs/operations.hh> - - -using namespace vereign; auto main(int argc, char** argv) -> int { - boost::ignore_unused(argc); - boost::ignore_unused(argv); + argc = 0; + argv = nullptr; return 0; } diff --git a/cpp/src/vereign/bytes/buffer.cc b/cpp/src/vereign/bytes/buffer.cc index 30e202dcd66dc63869a99cdfcde147d11a0e663e..546a4228f372ea30a4dc914b5c66f054ce1c1527 100644 --- a/cpp/src/vereign/bytes/buffer.cc +++ b/cpp/src/vereign/bytes/buffer.cc @@ -1,8 +1,9 @@ -#include <memory> -#include <new> -#include <stdexcept> #include <vereign/bytes/buffer.hh> +#include <vereign/bytes/errors.hh> + +#include <memory> + namespace vereign::bytes { Buffer::Buffer() noexcept @@ -111,9 +112,9 @@ void Buffer::Reserve(std::size_t size) { return; } - auto cap = cap_; - while (size > cap - size_) { - cap = cap * 2; + auto cap = cap_ * 2; + if (size > cap - size_) { + cap = size_ + size; } auto newData = reinterpret_cast<uint8_t*>(std::realloc(data_, cap)); @@ -129,12 +130,12 @@ void Buffer::Reset() { size_ = 0; } -void Buffer::IncSize(std::size_t size) { - if (size_ + size > cap_) { - throw std::runtime_error("cannot increment size pass the capacity"); +void Buffer::IncSize(std::size_t val) { + if (size_ + val > cap_) { + throw IncrementOutOfBounds{}; } - size_ += size; + size_ += val; } auto Buffer::WriteWithinCap(bytes::View src) noexcept -> std::size_t { @@ -162,7 +163,7 @@ auto Buffer::View(std::size_t start) const noexcept -> bytes::View { auto Buffer::operator[](std::size_t index) -> uint8_t& { if (index >= cap_) { - throw std::runtime_error("index out of bounds"); + throw IndexOutOfBounds{}; } return data_[index]; @@ -170,7 +171,7 @@ auto Buffer::operator[](std::size_t index) -> uint8_t& { auto Buffer::operator[](std::size_t index) const -> const uint8_t& { if (index >= cap_) { - throw std::runtime_error("index out of bounds"); + throw IndexOutOfBounds{}; } return data_[index]; diff --git a/cpp/src/vereign/bytes/buffer.hh b/cpp/src/vereign/bytes/buffer.hh index c2fe622b87d3d7bb15c9b094b1d458c1bc22f9eb..6d0e5c7a8bddee458283afc810b3b79765e76968 100644 --- a/cpp/src/vereign/bytes/buffer.hh +++ b/cpp/src/vereign/bytes/buffer.hh @@ -6,12 +6,45 @@ namespace vereign::bytes { +/** + * Dynamically expandable memory buffer. + * + * The buffer is a 3-tuple - pointer, size and capacity. + * Typically used in functions for output parameters and return values. + * Provides API that is easy to use with C APIs. + * + * The buffer is move only. + */ class Buffer { public: + /** + * Creates empty buffer. + */ Buffer() noexcept; + + /** + * Creates a buffer with reserved memory capacity. + * + * The size of the buffer is zero. + * + * @param cap The capacity of the buffer. + * + * @throws std::bad_alloc when memory reservation fails. + */ Buffer(std::size_t cap); + + /** + * Creates a buffer by copying from source bytes view. + * + * @param src The source that will be copied from. + * + * @throws std::bad_alloc when memory reservation fails. + */ Buffer(View src); + /** + * The buffer is movable. + */ Buffer(Buffer&& other) noexcept; auto operator=(Buffer&& other) noexcept -> Buffer&; @@ -19,28 +52,216 @@ public: Buffer(const Buffer&) = delete; auto operator=(const Buffer&) -> Buffer& = delete; + /** + * Frees the buffer memory. + */ ~Buffer(); + /** + * Returns a pointer to the first byte of the buffer. + * + * Buffer::begin(), Buffer::end() pair is useful for range loops. + * + * Example: + * @code + * auto buf = bytes::Buffer{bytes::View("foo")}; + * for (const auto& byte : buf) { + * byte = 'x'; + * } + * + * assert(buf.View().String() == "xxx"); + * @endcode + */ auto begin() noexcept -> uint8_t*; + + /** + * Returns a pointer to the first byte of the buffer. + * + * Buffer::begin(), Buffer::end() pair is useful for range loops. + * + * Example: + * @code + * auto buf = bytes::Buffer{bytes::View("foo bar")}; + * std::string s; + * + * for (const auto& byte : buf) { + * s += byte; + * } + * + * assert(s == "foo bar"); + * @endcode + */ auto begin() const noexcept -> const uint8_t*; + /** + * Returns a pointer to the byte following the last byte in the buffer. + * + * Note that this is the last byte in the range [0, size). + * It is often used when calling C APIs. + * + * Example: + * @code + * auto buf = bytes::Buffer{bytes::View("foo bar")}; + * + * buf.Reserve(4); + * std::strncpy((char*)buf.end(), " baz", 4); + * buf.IncSize(4); + * + * assert(buf.View().String() == "foo bar baz"); + * @endcode + */ auto end() noexcept -> uint8_t*; + + /** + * Returns a read only pointer to the byte following the last byte in the buffer. + * + * Note that this is the last byte in the range [0, size). + */ auto end() const noexcept -> const uint8_t*; + /** + * Access a byte in the range of [0, cap). + * + * @param index The index of the byte to access. + * + * @throws std::runtime_error when the passed index is out of bounds. + */ auto operator[](std::size_t index) -> uint8_t&; + + /** + * Read only access a byte in the range of [0, cap). + * + * @param index The index of the byte to access. + * + * @throws std::runtime_error when the passed index is out of bounds. + */ auto operator[](std::size_t index) const -> const uint8_t&; + /** + * Retrieve buffer size. + * + * @returns the buffer size. + */ auto Size() const noexcept -> std::size_t; + + /** + * Retrieve buffer capacity. + * + * @returns the buffer capacity. + */ auto Cap() const noexcept -> std::size_t; + + /** + * Reserve memory, so that there is at least `size` free capacity. + * + * If there is already enough free capacity, no memory allocation is done. + * The allocated memory may be bigger than what is needed. + * + * If the call is successful then it is guaranteed that `this->FreeCap() >= size`. + * + * Example: + * @code + * auto buf = bytes::Buffer{bytes::View("foo bar")}; + * + * buf.Reserve(4); // ensure there will be a free capacity for 4 bytes + * std::strncpy((char*)buf.end(), " baz", 4); // copy the bytes + * buf.IncSize(4); // update the buffer size with the newly written bytes + * + * assert(buf.View().String() == "foo bar baz"); + * @endcode + * + * @param size The desired free capacity. + * + * @throws std::bad_alloc when memory allocation fails. + */ void Reserve(std::size_t size); + + /** + * Sets the buffer size to zero. + * + * This does not free any memory, so the capacity stays the intact, and the buffer can be reused. + */ void Reset(); - void IncSize(std::size_t size); + /** + * Increments the size of the buffer. + * + * It is typically used after some function has written bytes to the end of the buffer. + * + * @param val The value that will be added to the current size. + */ + void IncSize(std::size_t val); + + /** + * Retrieve the buffer free capacity. + * + * This is equal to `this->Cap() - this->Size()`. + */ auto FreeCap() const noexcept -> std::size_t; + + /** + * Adds bytes up to the currently available buffer capacity. + * + * After the operation succeeds, the buffer size will be incremented with the number of bytes + * that have been copied. + * + * Example: + * @code + * auto buf = bytes::Buffer{3}; + * buf.WriteWithinCap(bytes::View("foo bar")); + * + * // only 3 bytes are written + * assert(buf.View.String() == "foo"); + * @endcode + * + * @param src The source that will be appended to the buffer. + * @returns The amount of bytes that were actually copied into the buffer. + */ auto WriteWithinCap(bytes::View src) noexcept -> std::size_t; + + /** + * Adds a source view of bytes to the buffer. + * + * If the buffer does not have enough capacity, it will be expanded. + * + * Example: + * @code + * auto buf = bytes::Buffer{3}; + * buf.WriteWithinCap(bytes::View("foo bar")); + * + * // all bytes are written + * assert(buf.View.String() == "foo bar"); + * @endcode + * + * @param The source that will be appended to the buffer. + * @returns The amount of bytes that were copied into the buffer. That is equal to src.Size(). + */ auto Write(bytes::View src) -> std::size_t; + /** + * Retrieve a read only view of the buffer. + * + * Example: + * @code + * auto buf = bytes::Buffer{bytes::View("123")}; + * assert(buf.View().String() == "123"); + * @endcode + * + * @returns a read only view range [0, this->Size()). + */ auto View() const noexcept -> bytes::View; + + /** + * Retrieve a read only view of the buffer staring from a given offset. + * + * Example: + * @code + * auto buf = bytes::Buffer{bytes::View("123")}; + * assert(buf.View(1).String() == "23"); + * @endcode + * + * @returns a read only view range [start, this->Size()). + */ auto View(std::size_t start) const noexcept -> bytes::View; private: diff --git a/cpp/src/vereign/bytes/errors.hh b/cpp/src/vereign/bytes/errors.hh new file mode 100644 index 0000000000000000000000000000000000000000..ac7e9e66a800a30f340e6b3f0de9c9cbf959cabd --- /dev/null +++ b/cpp/src/vereign/bytes/errors.hh @@ -0,0 +1,35 @@ +#ifndef __VEREIGN_BYTES_ERRORS_HH +#define __VEREIGN_BYTES_ERRORS_HH + +#include <stdexcept> + +namespace vereign::bytes { + +class Error : public std::runtime_error { +public: + Error(const std::string& what) + : std::runtime_error{what} + { + + } +}; + +class IndexOutOfBounds : public Error { +public: + IndexOutOfBounds() + : Error{"index out of bounds"} + { + } +}; + +class IncrementOutOfBounds : public Error { +public: + IncrementOutOfBounds() + : Error{"cannot increment size pass the capacity"} + { + } +}; + +} // namespace vereign::bytes + +#endif // __VEREIGN_BYTES_ERRORS_HH diff --git a/cpp/src/vereign/bytes/view.hh b/cpp/src/vereign/bytes/view.hh index 672b6229ada16d3b2b1a1351ddcc3865d30a2e3e..4520934c927170ed79682ab95d9f9ed5e430dee8 100644 --- a/cpp/src/vereign/bytes/view.hh +++ b/cpp/src/vereign/bytes/view.hh @@ -1,6 +1,8 @@ #ifndef __VEREIGN_BYTES_VIEW_HH #define __VEREIGN_BYTES_VIEW_HH +#include <vereign/bytes/errors.hh> + #include <cstring> #include <string> #include <string_view> @@ -9,37 +11,79 @@ namespace vereign::bytes { +/** + * Bytes view represents a read only access to a range of bytes. + * + * The View is a 2-tuple with pointer and size. + * Typically used in functions for input parameters. + * + * **NOTE: The View does not own the memory that it references.** + */ class View { public: + /** + * Creates empty view. + */ View() = default; + /** + * Creates a view from raw pointer and a size. + * + * @param data Pointer to the memory. + * @param size The size of the memory. + */ View(const uint8_t* data, std::size_t size) noexcept : size_{size}, data_{data} { } + /** + * Create a view from a string view. + * + * @param str The input string view. + */ View(std::string_view str) noexcept : size_{str.length()}, data_{str.length() > 0 ? reinterpret_cast<const uint8_t*>(str.data()): nullptr} { } + /** + * Creates a view from wide string view. + * + * @param str The input string. + */ View(std::wstring_view str) noexcept : size_{str.length() * sizeof(wchar_t)}, data_{size_ > 0 ? reinterpret_cast<const uint8_t*>(str.data()): nullptr} { } + /** + * Creates a view from void pointer and a size. + * + * @param data Pointer to the memory. + * @param size The size of the memory. + */ View(const void* ptr, std::size_t size) noexcept : size_{size}, data_{static_cast<const uint8_t*>(ptr)} { } + // default copyable. View(const View&) = default; auto operator=(const View&) -> View& = default; + /** + * Slice returns a new view in the interval [start, size). + * + * If the start is bigger than the size of the slice it returns empty view. + * + * @param start The beginning of the new View. + * @returns a new view in the interval [start, size). + */ auto Slice(std::size_t start) const -> View { if (start >= size_) { return View(data_, 0); @@ -48,6 +92,15 @@ public: return View(data_ + start, size_ - start); } + /** + * Slice returns a new view in the interval [start, end). + * + * If the start is bigger than the size of the slice it returns empty view. + * If the end is bigger than the size of the slice it returns [start, size). + * + * @param start The beginning of the new View. + * @returns a new view in the interval [start, size). + */ auto Slice(std::size_t start, std::size_t end) const -> View { if (start >= size_) { return View(data_, 0); @@ -56,22 +109,65 @@ public: return View(data_ + start, std::min(size_, end) - start); } + /** + * Retrieve a pointer to the data. + * + * @returns a pointer to the data. + */ auto Data() const noexcept -> const uint8_t* { return data_; } + /** + * Retrieve a char pointer to the data. + * + * @returns a char pointer to the data. + */ auto CharData() const noexcept -> const char* { return reinterpret_cast<const char*>(data_); } + /** + * Retrieve a wide char pointer to the data. + * + * @returns a wide char pointer to the data. + */ + auto WideCharData() const noexcept -> const wchar_t* { + return reinterpret_cast<const wchar_t*>(data_); + } + + /** + * Retrieve view size. + * + * @returns view size. + */ auto Size() const noexcept -> std::size_t { return size_; } + /** + * Retrieve a string view of the data. + * + * @returns a string view of the data. + */ auto String() const noexcept -> std::string_view { return std::string_view{CharData(), size_}; } + /** + * Retrieve a wide string view of the data. + * + * @returns a wide string view of the data. + */ + auto WideString() const noexcept -> std::wstring_view { + return std::wstring_view{WideCharData(), size_/sizeof(wchar_t)}; + } + + /** + * Binary compare the contents of two views. + * + * @returns true if the views are of the same size and if all the bytes in the two views are equal. + */ auto operator==(View other) const noexcept -> bool { if (size_ != other.size_) { return false; @@ -80,13 +176,26 @@ public: return std::memcmp(data_, other.data_, size_) == 0; } + /** + * Binary compare the contents of two views. + * + * @returns true if the views are of different size or if the bytes in the two views are not equal. + */ auto operator!=(View other) const noexcept -> bool { return !(*this == other); } + /** + * Access a single byte in the view. + * + * @param index The index of the byte that will be returned. + * @returns the byte at the specified index. + * + * @throws bytes::IndexOutOfBounds when the index is out of bounds. + */ auto operator[](std::size_t index) const -> const uint8_t& { if (index >= size_ ) { - throw std::runtime_error("index out of bounds"); + throw IndexOutOfBounds{}; } return data_[index]; diff --git a/cpp/src/vereign/crypto/aes.cc b/cpp/src/vereign/crypto/aes.cc index 59875fb3dd19c268bf8d86f591f008c3109de9ae..f9bcefab4365b17f31e960ffb802d662692695d2 100644 --- a/cpp/src/vereign/crypto/aes.cc +++ b/cpp/src/vereign/crypto/aes.cc @@ -19,11 +19,11 @@ void GCM256Encrypt( bytes::View src, bytes::View key, bytes::Buffer& iv, - bytes::Buffer& encrypted, - bytes::Buffer& tag + bytes::Buffer& tag, + bytes::Buffer& encrypted ) { iv.Reserve(gcmIVSizeBytes); - crypto::Rand(iv); + crypto::Rand(iv, gcmIVSizeBytes); encrypted.Reserve(src.Size() + aes256BlockSizeBytes); @@ -61,7 +61,7 @@ void GCM256Encrypt( encrypted.IncSize(bytes_written); tag.Reserve(gcmTagSizeBytes); - r = EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG, tag.FreeCap(), tag.end()); + r = EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG, gcmTagSizeBytes, tag.end()); if (r != 1) { throw OpenSSLError("getting GCM tag failed"); } diff --git a/cpp/src/vereign/crypto/aes.hh b/cpp/src/vereign/crypto/aes.hh index 861c749b8757cb7188824de79aa430ed246d6d41..09be668b964bddd8f31aecc93e874ae35834eb3b 100644 --- a/cpp/src/vereign/crypto/aes.hh +++ b/cpp/src/vereign/crypto/aes.hh @@ -1,19 +1,72 @@ #ifndef __VEREIGN_CRYPTO_AES_HH #define __VEREIGN_CRYPTO_AES_HH -#include "vereign/bytes/view.hh" #include <vereign/bytes/buffer.hh> +/** + * Provides utilities for AES encryption/decryption. + */ namespace vereign::crypto::aes { +/** + * Encrypt given bytes with AES256-GCM. + * + * Example: + * @code + * const std::string input{"foo bar"}; + * auto key = crypto::Rand(32); // 256 bits + * + * bytes::Buffer iv; + * bytes::Buffer tag; + * bytes::Buffer encrypted; + * + * crypto::aes::GCM256Encrypt(bytes::View(input), key.View(), iv, tag, encrypted); + * @endcode + * + * @param src The bytes that will be encrypted. + * @param key The AES 256 bit key. + * @param iv The initialization vector that was used during the encryption. + * @param tag The authentication tag that was produced during the encryption. + * @param encrypted The result of the encryption. + * + * @throws crypto::OpenSSLError on failure. + */ void GCM256Encrypt( bytes::View src, bytes::View key, bytes::Buffer& iv, - bytes::Buffer& encrypted, - bytes::Buffer& tag + bytes::Buffer& tag, + bytes::Buffer& encrypted ); +/** + * Decrypts given bytes with AES256-GCM. + * + * Example: + * @code + * const std::string input{"foo bar"}; + * auto key = crypto::Rand(32); // 256 bits + * + * bytes::Buffer iv; + * bytes::Buffer tag; + * bytes::Buffer encrypted; + * + * crypto::aes::GCM256Encrypt(bytes::View(input), key.View(), iv, tag, encrypted); + * + * bytes::Buffer decrypted; + * crypto::aes::GCM256Decrypt(encrypted.View(), key.View(), iv.View(), tag.View(), decrypted); + * + * assert(input == decrypted.View().String()); + * @endcode + * + * @param src The bytes that will be decrypted. + * @param key The AES 256 bit key. + * @param iv The initialization vector that was used during the encryption. + * @param tag The authentication tag that was produced during the encryption. + * @param encrypted The result of the decryption. + * + * @throws crypto::OpenSSLError on failure. + */ void GCM256Decrypt( bytes::View src, bytes::View key, diff --git a/cpp/src/vereign/crypto/bio.hh b/cpp/src/vereign/crypto/bio.hh index 8c1721430878d422d48de31e13d6f0931fa978b4..2bd5cedacee0b6c94c81a0f2af50e448982b6d3f 100644 --- a/cpp/src/vereign/crypto/bio.hh +++ b/cpp/src/vereign/crypto/bio.hh @@ -6,6 +6,12 @@ namespace vereign::crypto::bio { +/** + * Creates a bytes::View for given OpenSSL BIO. + * + * @param bio The input BIO. + * @returns a bytes view of OpenSSL BIO. + */ auto View(const BIO* bio) -> bytes::View; } // vereign::crypto::bio diff --git a/cpp/src/vereign/crypto/digest.hh b/cpp/src/vereign/crypto/digest.hh index 53f15d5b73791c6c3453665b541effe1e4f01968..365d45e9af3fa5fd3be617d539aaa823ac210af0 100644 --- a/cpp/src/vereign/crypto/digest.hh +++ b/cpp/src/vereign/crypto/digest.hh @@ -5,6 +5,22 @@ namespace vereign::crypto::digest { +/** + * Creates a SHA1 hash of given bytes. + * + * Example: + * @code + * const std::string input{"foo bar"}; + * + * bytes::Buffer hash; + * crypto::digest::sha1(bytes::View(data), hash); + * @endcode + * + * @param src The input bytes. + * @param result The SHA1 hash of the input. + * + * @throws crypto::OpenSSLError on failure. + */ void sha1(bytes::View src, bytes::Buffer& result); } // vereign::crypto::digest diff --git a/cpp/src/vereign/crypto/errors.hh b/cpp/src/vereign/crypto/errors.hh index c5282ade4ae3a4ab91ecc0b60fbb479e8b88a47c..949eb248cd8a0caf3014701a8e962c47f866f1ad 100644 --- a/cpp/src/vereign/crypto/errors.hh +++ b/cpp/src/vereign/crypto/errors.hh @@ -6,6 +6,9 @@ namespace vereign::crypto { +/** + * The base error type for the namespace vereign::crypto. + */ class Error : public std::runtime_error { public: Error(const std::string& what) @@ -14,6 +17,9 @@ public: } }; +/** + * An error thrown by the crypto functions. + */ class OpenSSLError : public Error { public: OpenSSLError(const std::string& what) diff --git a/cpp/src/vereign/crypto/rand.cc b/cpp/src/vereign/crypto/rand.cc new file mode 100644 index 0000000000000000000000000000000000000000..2acc168ed6d9df6905c3ad25e7053c041e29a715 --- /dev/null +++ b/cpp/src/vereign/crypto/rand.cc @@ -0,0 +1,26 @@ +#include <vereign/crypto/rand.hh> + +#include <openssl/rand.h> +#include <vereign/crypto/errors.hh> + +namespace vereign::crypto { + +void Rand(bytes::Buffer& buf, std::size_t size) { + buf.Reserve(size); + int result = RAND_bytes(buf.end(), size); + if (result == 0) { + ERR_clear_error(); + throw Error("crypto rand failed"); + } + + buf.IncSize(size); +} + +auto Rand(std::size_t size) -> bytes::Buffer { + bytes::Buffer buf{size}; + Rand(buf, size); + + return buf; +} + +} // vereign::crypto diff --git a/cpp/src/vereign/crypto/rand.hh b/cpp/src/vereign/crypto/rand.hh index 4067b039adf2b80688b3f7af93340730b05081eb..4c3979900f1464d2e54c9957f689f503af3e7e2f 100644 --- a/cpp/src/vereign/crypto/rand.hh +++ b/cpp/src/vereign/crypto/rand.hh @@ -2,21 +2,46 @@ #define __VEREIGN_CRYPTO_RAND_HH #include <vereign/bytes/buffer.hh> -#include <vereign/crypto/errors.hh> - -#include <openssl/rand.h> namespace vereign::crypto { -inline void Rand(bytes::Buffer& buf) { - int result = RAND_bytes(buf.end(), buf.FreeCap()); - if (result == 0) { - ERR_clear_error(); - throw Error("crypto rand failed"); - } +/** + * Appends a random bytes to a buffer. + * + * After the operation is finished, the passed `buf` will be with incremented size of the newly + * added random bytes. + * + * Example: + * @code + * bytes::Bytes buf; + * crypto::Rand(buf, 16); + * + * assert(buf.Size() == 16); + * @endcode + * + * @param buf The buffer that will be filled with random bytes. + * @param size The number of the random bytes. + * + * @throws crypto::Error on failure. + */ +void Rand(bytes::Buffer& buf, std::size_t size); - buf.IncSize(buf.FreeCap()); -} +/** + * Generate random bytes. + * + * Example: + * @code + * auto buf = crypto::Rand(16); + * + * assert(buf.Size() == 16); + * @endcode + * + * @param size The number of the random bytes. + * @returns buffer with the generated random bytes. + * + * @throws crypto::Error on failure. + */ +auto Rand(std::size_t size) -> bytes::Buffer; } // vereign::crypto diff --git a/cpp/src/vereign/crypto/rsa.hh b/cpp/src/vereign/crypto/rsa.hh index ab41d09e702ca3b6a9106a77ff5cbb2d1bd93fc8..4d8e18fc38701d7a921dc2ea42a2781030ee32a6 100644 --- a/cpp/src/vereign/crypto/rsa.hh +++ b/cpp/src/vereign/crypto/rsa.hh @@ -9,15 +9,137 @@ namespace vereign::crypto::rsa { +/** + * Generates new RSA key. + * + * Example: + * @code + * auto key = crypto::rsa::GenerateKey(2048); + * @endcode + * + * @param bits The length of the key. + * @returns the newly generated key. + * + * @throws crypto::OpenSSLError on failure. + */ auto GenerateKey(int bits) -> bssl::UniquePtr<EVP_PKEY>; +/** + * Encrypts given bytes with RSA public key. + * + * Example: + * @code + * const std::string input{"foo bar"}; + * auto key = crypto::rsa::GenerateKey(2048); + * bytes::Buffer encrypted; + * + * crypto::rsa::PublicKeyEncrypt(key.get(), bytes::View(input), encrypted); + * + * bytes::Buffer decrypted; + * crypto::rsa::PrivateKeyDecrypt(key.get(), encrypted.View(), decrypted); + * + * assert(decrypted.View() == bytes.View(input)); + * @endcode + * + * @param key The RSA key. + * @param src The bytes that will be encrypted. + * @param encrypted The result of the encryption. + * + * @throws crypto::OpenSSLError on failure. + */ void PublicKeyEncrypt(EVP_PKEY* key, bytes::View src, bytes::Buffer& encrypted); + +/** + * Decrypts given bytes with RSA private key. + * + * Example: + * @code + * const std::string input{"foo bar"}; + * auto key = crypto::rsa::GenerateKey(2048); + * bytes::Buffer encrypted; + * + * crypto::rsa::PublicKeyEncrypt(key.get(), bytes::View(input), encrypted); + * + * bytes::Buffer decrypted; + * crypto::rsa::PrivateKeyDecrypt(key.get(), encrypted.View(), decrypted); + * + * assert(decrypted.View() == bytes.View(input)); + * @endcode + * + * @param key The RSA key. + * @param src The bytes that will be decrypted. + * @param decrypted The result of the decryption. + * + * @throws crypto::OpenSSLError on failure. + */ void PrivateKeyDecrypt(EVP_PKEY* key, bytes::View src, bytes::Buffer& decrypted); +/** + * Exports a public key part to PEM format. + * + * @code + * auto key = crypto::rsa::GenerateKey(2048); + * + * auto bio = crypto::rsa::ExportPublicKeyToPEM(key.get()); + * std::cout << crypto::bio::View(bio.get()).String() << std::endl; + * @endcode + * + * @param key The key to export. + * @returns a memory BIO with the exported key. + * + * @throws crypto::OpenSSLError on failure. + */ auto ExportPublicKeyToPEM(EVP_PKEY* key) -> bssl::UniquePtr<BIO>; + +/** + * Import public key from PEM format. + * + * @code + * auto key = crypto::rsa::GenerateKey(2048); + * + * auto bio = crypto::rsa::ExportPublicKeyToPEM(key.get()); + * auto imported_key = crypto::rsa::ImportPublicKeyFromPEM(crypto::bio::View(bio.get())); + * @endcode + * + * @param pem PEM encoded key. + * @returns imported key. + * + * @throws crypto::OpenSSLError on failure. + */ auto ImportPublicKeyFromPEM(bytes::View pem) -> bssl::UniquePtr<EVP_PKEY>; +/** + * Export private key from PEM format. + * + * @code + * auto key = crypto::rsa::GenerateKey(2048); + * + * auto bio = crypto::rsa::ExportPrivateKeyToPEM(key.get()); + * std::cout << crypto::bio::View(bio.get()).String() << std::endl; + * @endcode + * + * @param key The key to export. + * @returns a memory BIO with the exported key. + * + * @throws crypto::OpenSSLError on failure. + */ auto ExportPrivateKeyToPEM(EVP_PKEY* key) -> bssl::UniquePtr<BIO>; + +/** + * Import private key from PEM format. + * + * @code + * auto key = crypto::rsa::GenerateKey(2048); + * + * auto bio = crypto::rsa::ExportPrivateKeyToPEM(key.get()); + * auto imported_key = crypto::rsa::ImportPrivateKeyFromPEM(crypto::bio::View(bio.get())); + * @endcode + * + * @param pem PEM encoded key. + * @returns imported key. + * + * @throws crypto::OpenSSLError on failure. + */ auto ImportPrivateKeyFromPEM(bytes::View pem) -> bssl::UniquePtr<EVP_PKEY>; } // vereign::crypto::rsa diff --git a/cpp/src/vereign/encoding/base64.hh b/cpp/src/vereign/encoding/base64.hh index 2bfaab5f159151b2a10330bf176ef7e0394ae167..a69a559403f9126045cdb69e89bbf084a519fd08 100644 --- a/cpp/src/vereign/encoding/base64.hh +++ b/cpp/src/vereign/encoding/base64.hh @@ -6,7 +6,40 @@ namespace vereign::encoding::base64 { +/** + * Encodes source bytes into base64 encoding. + * + * No new lines are inserted. + * + * Example: + * @code + * std::string s{"foob"}; + * bytes::Buffer encoded; + * encoding::base64::Encode(bytes::View(s), encoded); + * + * assert(encoded.View().String() == "Zm9vYg==") + * @endcode + * + * @param src The source bytes that will be encoded. + * @param encoded The encoded bytes. + */ void Encode(bytes::View src, bytes::Buffer& encoded); + +/** + * Decodes base64 encoded bytes. + * + * Example: + * @code + * std::string s{"Zm9vYg=="}; + * bytes::Buffer decoded; + * encoding::base64::Decode(bytes::View(s), decoded); + * + * assert(decoded.View().String() == "foob"); + * @endcode + * + * @param src The base64 encoded bytes that will be decoded. + * @param decoded The decoded bytes. + */ void Decode(bytes::View src, bytes::Buffer& decoded); } // vereign::encoding::base64 diff --git a/cpp/src/vereign/encoding/binary.cc b/cpp/src/vereign/encoding/binary.cc index 0358fc236762f3d09cbf6ddd8a66f970e5f9a570..3c25446ccf77e123d6c19ec71578a38ab895c34e 100644 --- a/cpp/src/vereign/encoding/binary.cc +++ b/cpp/src/vereign/encoding/binary.cc @@ -1,5 +1,7 @@ #include <vereign/encoding/binary.hh> +#include <vereign/encoding/errors.hh> + namespace vereign::encoding::binary { void EncodeUint8(bytes::Buffer& out, uint8_t v) { @@ -10,7 +12,7 @@ void EncodeUint8(bytes::Buffer& out, uint8_t v) { auto DecodeUint8(bytes::View& b) -> uint8_t { if (b.Size() < 1) { - throw std::runtime_error("decoding failed the input size is less than 1 bytes"); + throw encoding::Error("decoding failed the input size is less than 1 bytes"); } return b.Data()[0]; @@ -46,8 +48,7 @@ auto DecodeUint64(const uint8_t* b) -> uint64_t { auto DecodeUint64(bytes::View b) -> uint64_t { if (b.Size() < 8) { - // FIXME: add encoding::Error exception - throw std::runtime_error("decoding failed the input size is less than 8 bytes"); + throw encoding::Error("decoding failed the input size is less than 8 bytes"); } return DecodeUint64(b.Data()); @@ -61,7 +62,7 @@ void EncodeBytes(bytes::Buffer& out, bytes::View bytes) { auto DecodeBytes(bytes::View b, bytes::View& out) -> std::size_t { auto size = DecodeUint64(b); if (size < 0 || size + 8 > b.Size()) { - throw std::runtime_error("decode bytes failed: invalid size"); + throw encoding::Error("decode bytes failed: invalid size"); } out = b.Slice(8, size + 8); diff --git a/cpp/src/vereign/encoding/binary.hh b/cpp/src/vereign/encoding/binary.hh index 8d59c655446028fedec33532674166111b752930..8be71c31f3a76f1c4c36c7e631f8731253edd64d 100644 --- a/cpp/src/vereign/encoding/binary.hh +++ b/cpp/src/vereign/encoding/binary.hh @@ -4,19 +4,96 @@ #include <stdexcept> #include <vereign/bytes/buffer.hh> -// FIXME: add docs +/** + * Binary encoding of integers, bytes. + * + * The integers are encoded in little endian. + */ namespace vereign::encoding::binary { +/** + * Encodes a byte. + * + * On success the out buffer size will be incremented with 1. + * + * @param out buffer where the byte will be written. + * @param v the byte that will be encoded. + */ void EncodeUint8(bytes::Buffer& out, uint8_t v); + +/** + * Decodes a byte. + * + * @param b the source bytes from where the single byte will be decoded. + */ auto DecodeUint8(bytes::View& b) -> uint8_t; +/** + * Encodes a uint64. + * + * The number is encoded in little endian. + * + * @param out where the value will be written. + * @param v the number that will be encoded. + */ void EncodeUint64(uint8_t* out, uint64_t v); + +/** + * Encodes a uint64. + * + * On success the out buffer size will be incremented with 8. + * The number is encoded in little endian. + * + * @param out where the value will be written. + * @param v the number that will be encoded. + */ void EncodeUint64(bytes::Buffer& out, uint64_t v); +/** + * Decodes a uint64. + * + * The number is expected to be encoded in little endian. + * + * @param b the source number. + * @returns the decoded number. + */ auto DecodeUint64(const uint8_t* b) -> uint64_t; + +/** + * Decodes a uint64. + * + * The number is expected to be encoded in little endian. + * + * @param b the source bytes where the encoded number will be read from. + * @returns the decoded number. + * + * @throws encoding::Error when the source bytes size is less than 8. + */ auto DecodeUint64(bytes::View b) -> uint64_t; +/** + * Encodes bytes. + * + * The result is 8 bytes for the size, followed by the bytes them self. + * The out buffer size is incremented with out.Size() + 8. + * + * @param out the where the encoded value will be written. + * @param bytes the source bytes that will be encoded. + */ void EncodeBytes(bytes::Buffer& out, bytes::View bytes); + +/** + * Decode bytes. + * + * Note that the decoded bytes reference to the source buffer. + * In other words it is only a view, there is no copying involved. + * + * @param b the source with the encoded bytes. + * @param out the decoded bytes. + * @returns the bytes read during the decoding (8 + out.Size()). + * + * @throws encoding::Error when the decoding fails. + */ auto DecodeBytes(bytes::View b, bytes::View& out) -> std::size_t; } // namespace vereign::encoding::binary diff --git a/cpp/src/vereign/encoding/errors.hh b/cpp/src/vereign/encoding/errors.hh new file mode 100644 index 0000000000000000000000000000000000000000..d9d012210165fb645f600dc60a989dfb5ffcb392 --- /dev/null +++ b/cpp/src/vereign/encoding/errors.hh @@ -0,0 +1,21 @@ +#ifndef __VEREIGN_ENCODING_ERRORS_HH +#define __VEREIGN_ENCODING_ERRORS_HH + +#include <stdexcept> + +namespace vereign::encoding { + +/** + * Base error for namespace vereign::encoding. + */ +class Error : public std::runtime_error { +public: + Error(const std::string& what) + : std::runtime_error(what) + { + } +}; + +} // namespace vereign::encoding::binary + +#endif // __VEREIGN_ENCODING_ERRORS_HH diff --git a/cpp/src/vereign/encoding/hex.cc b/cpp/src/vereign/encoding/hex.cc index e513512bfddecebab9f9fdd51423bbf5ad811f36..46ed21564dbd5e8cc363e43e04123a9c6a5bddaf 100644 --- a/cpp/src/vereign/encoding/hex.cc +++ b/cpp/src/vereign/encoding/hex.cc @@ -39,16 +39,16 @@ void Encode(bytes::View src, bytes::Buffer& encoded) { encoded.IncSize(src.Size() * 2); } -void Decode(std::string_view src, bytes::Buffer& decoded) { - if (src.size() == 0) { +void Decode(bytes::View src, bytes::Buffer& decoded) { + if (src.Size() == 0) { return ; } - decoded.Reserve(src.size() / 2); - for (int i = 0, len = (int) src.size() - 1; i < len; i += 2) { + decoded.Reserve(src.Size() / 2); + for (int i = 0, len = (int) src.Size() - 1; i < len; i += 2) { decoded[i/2] = detail::charToInt(src[i]) * 16 + detail::charToInt(src[i + 1]); } - decoded.IncSize(src.size() / 2); + decoded.IncSize(src.Size() / 2); } void EncodeReverse(bytes::View src, bytes::Buffer& encoded) { diff --git a/cpp/src/vereign/encoding/hex.hh b/cpp/src/vereign/encoding/hex.hh index f2e406453bda12dbead8086bcf70099b72d2b176..6dec0f13270bae1d163d7f9114e56276f082515d 100644 --- a/cpp/src/vereign/encoding/hex.hh +++ b/cpp/src/vereign/encoding/hex.hh @@ -6,9 +6,52 @@ namespace vereign::encoding::hex { +/** + * Encodes bytes in hexadecimal. + * + * Example: + * @code + * bytes::Buffer encoded; + * encoding::hex::Decode(bytes::View("foobar"), encoded); + * + * assert(encoded.View().String() == "666f6f626172"); + * @endcode + * + * @param src The bytes that will be encoded. + * @param encoded The buffer where the encoded bytes will be written. + */ void Encode(bytes::View src, bytes::Buffer& encoded); + +/** + * Decodes hexadecimal encoded bytes. + * + * Example: + * @code + * bytes::Buffer decoded; + * encoding::hex::Decode(bytes::View("666f6f626172"), decoded); + * + * assert(decoded.View().String() == "foobar"); + * @endcode + * + * @param src The bytes that will be decoded. + * @param encoded The buffer where the decoded bytes will be written. + */ void Decode(bytes::View src, bytes::Buffer& decoded); +/** + * Encodes bytes in hexadecimal in reverse order. + * + * Example: + * @code + * bytes::Buffer encoded; + * encoding::hex::Decode(bytes::View("foobar"), encoded); + * + * assert(encoded.View().String() == "7261626f6f66"); + * @endcode + * + * @param src The bytes that will be encoded. + * @param encoded The buffer where the encoded bytes will be written. + */ void EncodeReverse(bytes::View src, bytes::Buffer& encoded); } // vereign::encoding::hex diff --git a/cpp/src/vereign/fs/operations.cc b/cpp/src/vereign/fs/operations.cc index 4ebcad13b8fc38807f5754dae166408add50ea27..9f33ed2d31d3322c2748ee32ba91d4b9c477f53f 100644 --- a/cpp/src/vereign/fs/operations.cc +++ b/cpp/src/vereign/fs/operations.cc @@ -18,11 +18,11 @@ auto Exists(const std::string& path) -> bool { return f.good(); } -auto IsDirectory(const std::string& path) -> bool { +auto IsDir(const std::string& path) -> bool { return boost::filesystem::is_directory(fs::detail::StringToPath(path)); } -auto CreateDirectory(const std::string& path) -> bool { +auto CreateDir(const std::string& path) -> bool { return boost::filesystem::create_directory(fs::detail::StringToPath(path)); } diff --git a/cpp/src/vereign/fs/operations.hh b/cpp/src/vereign/fs/operations.hh index 969289657a91ad03d4338b01f502d79d5bc79b49..e5f60c773a1eb9de85d76b32100e0c11d033c5bf 100644 --- a/cpp/src/vereign/fs/operations.hh +++ b/cpp/src/vereign/fs/operations.hh @@ -15,14 +15,14 @@ auto Exists(const std::string& path) -> bool; * * @param path Path to check. */ -auto IsDirectory(const std::string& path) -> bool; +auto IsDir(const std::string& path) -> bool; /** * Creates the last directory of the provided path. * * @param path Path of the directory that will be created. */ -auto CreateDirectory(const std::string& path) -> bool; +auto CreateDir(const std::string& path) -> bool; } // namespace vereign::fs diff --git a/cpp/src/vereign/fs/util.cc b/cpp/src/vereign/fs/util.cc index c59d0c2b1b97236b6c2abef8eaf6c867eadc7d6b..2e39a022f9ecd8d38388467b85beaedb955856d6 100644 --- a/cpp/src/vereign/fs/util.cc +++ b/cpp/src/vereign/fs/util.cc @@ -61,9 +61,9 @@ auto TempFilePath(std::string_view prefix) -> std::string { } auto TempDir(std::string_view prefix) -> std::string { - auto dir = TempFilePath(detail::PathToString(boost::filesystem::temp_directory_path()), prefix); + auto dir = TempFilePath(prefix); - CreateDirectory(dir); + CreateDir(dir); return dir; } diff --git a/cpp/src/vereign/grpc/error_code.hh b/cpp/src/vereign/grpc/error_code.hh new file mode 100644 index 0000000000000000000000000000000000000000..a64de2f872558876262883a92dc1bcda635189f4 --- /dev/null +++ b/cpp/src/vereign/grpc/error_code.hh @@ -0,0 +1,25 @@ +#ifndef __VEREIGN_GRPC_ERROR_CODES_HH +#define __VEREIGN_GRPC_ERROR_CODES_HH + +#include <cstdint> +#include <string> + +namespace vereign::grpc { + +static constexpr const char* ClientErrorStatus = "Vereign Client Library Error"; + +enum class ErrorCode : uint64_t { + ClientError = 1000, + UnexpectedError = 1001, + DeviceNotRegistered = 1002, + InvalidPinCode = 1003, + InvalidIdentity = 1004 +}; + +inline auto ErrorCodeAsString(ErrorCode ec) -> std::string { + return std::to_string(uint64_t(ec)); +} + +} + +#endif // __VEREIGN_GRPC_ERROR_CODES_HH diff --git a/cpp/src/vereign/grpc/identity_api.hh b/cpp/src/vereign/grpc/identity_api.hh index 375ba8c6a4fda0f1d2a79073230510506cdde56e..5e40c265d64d4f2ba1146a98cd274361af2bd82d 100644 --- a/cpp/src/vereign/grpc/identity_api.hh +++ b/cpp/src/vereign/grpc/identity_api.hh @@ -1,9 +1,12 @@ #ifndef __VEREIGN_GRPC_IDENTITY_API_HH #define __VEREIGN_GRPC_IDENTITY_API_HH -#include "vereign/client_library/common_types.pb.h" -#include "vereign/client_library/identity_types.pb.h" #include <vereign/grpc/gen/identity_api.hh> + +#include <vereign/grpc/error_code.hh> +#include <vereign/kvstore/errors.hh> +#include <vereign/client_library/common_types.pb.h> +#include <vereign/client_library/identity_types.pb.h> #include <boost/core/ignore_unused.hpp> namespace vereign::grpc { @@ -32,14 +35,21 @@ public: try { this->service_->LoginWithNewDevice(req, resp); + + } catch (const kvstore::InvalidPinCodeError& e) { + resp->set_code(ErrorCodeAsString(ErrorCode::InvalidPinCode)); + resp->set_status(ClientErrorStatus); + resp->set_error(e.what()); + } catch (const std::exception& e) { - resp->set_code("500"); - resp->set_status("Internal Service Error"); + resp->set_code(ErrorCodeAsString(ErrorCode::ClientError)); + resp->set_status(ClientErrorStatus); resp->set_error(e.what()); + } catch (...) { - resp->set_code("500"); - resp->set_status("Internal Service Error"); - resp->set_error("Internal Service Error"); + resp->set_code(ErrorCodeAsString(ErrorCode::UnexpectedError)); + resp->set_status(ClientErrorStatus); + resp->set_error(ClientErrorStatus); } return ::grpc::Status::OK; @@ -54,14 +64,31 @@ public: try { this->service_->LoginWithPreviouslyAddedDevice(req, resp); + + } catch (const kvstore::StorageNotInitializedError& e) { + resp->set_code(ErrorCodeAsString(ErrorCode::DeviceNotRegistered)); + resp->set_status(ClientErrorStatus); + resp->set_error(e.what()); + + } catch (const kvstore::InvalidPinCodeError& e) { + resp->set_code(ErrorCodeAsString(ErrorCode::InvalidPinCode)); + resp->set_status(ClientErrorStatus); + resp->set_error(e.what()); + + } catch (const kvstore::InvalidIdentityError& e) { + resp->set_code(ErrorCodeAsString(ErrorCode::InvalidIdentity)); + resp->set_status(ClientErrorStatus); + resp->set_error(e.what()); + } catch (const std::exception& e) { - resp->set_code("500"); - resp->set_status("Internal Service Error"); + resp->set_code(ErrorCodeAsString(ErrorCode::ClientError)); + resp->set_status(ClientErrorStatus); resp->set_error(e.what()); + } catch (...) { - resp->set_code("500"); - resp->set_status("Internal Service Error"); - resp->set_error("Internal Service Error"); + resp->set_code(ErrorCodeAsString(ErrorCode::UnexpectedError)); + resp->set_status(ClientErrorStatus); + resp->set_error(ClientErrorStatus); } return ::grpc::Status::OK; diff --git a/cpp/src/vereign/grpc/server.cc b/cpp/src/vereign/grpc/server.cc index 8a4b21ad2224b13c7509163c9669e5b52604fc12..f0fae25a176de979bd9274693687a1d1ef6b272f 100644 --- a/cpp/src/vereign/grpc/server.cc +++ b/cpp/src/vereign/grpc/server.cc @@ -53,7 +53,7 @@ public: { if (storage_path == "") { storage_path = fs::path::Join(fs::HomePath(), "vereign"); - fs::CreateDirectory(storage_path); + fs::CreateDir(storage_path); } storage_path = fs::path::Join(storage_path, "db"); diff --git a/cpp/src/vereign/kvstore/README.md b/cpp/src/vereign/kvstore/README.md new file mode 100644 index 0000000000000000000000000000000000000000..62a67dc5f3d413eb11c04a66e68c26bb62cca1ce --- /dev/null +++ b/cpp/src/vereign/kvstore/README.md @@ -0,0 +1,71 @@ +# Vereign C++ Client Library Storage Spec {#vcl_storage_spec} + +## Overview + +The Vereign C++ Library Storage is used for securely store locally user's identity and profile +certificates. + +The storage is a key/value storage where the values are encrypted with `master key` using +`AES256-GCM` cypher. + +The current backend of the storage is sqlite3, where the database file is normally located at +`${HOME}/vereign/db`. + +The `master key` key is provided in different way depending on the operating system. + +## windows master key + +Under windows [CNG](https://docs.microsoft.com/en-us/windows/win32/seccng/about-cng) is used for +generating a persistent RSA key pair 2048 bits long. The name of the key in the windows storage by +default is `vereign_key`. This key is used to encrypt a randomly generated AES256 key, and the +encrypted key is saved in the storage under a special key `__master_key`. + +## linux master key + +Under linux the user must provide a password (pin code). +This password along with randomly generated salt is used to derive a master key using +`PKCS5_PBKDF2_HMAC` with `SHA256` where the salt is 16 bytes and the iterations are 2^18. +The salt and the iterations are stored in the storage under special keys `__master_key_salt` and +`master_key_iterations`. Note that these are stored in plain form without encryption. + +## locking and storage tag + +Every time the storage is going to be modified the whole storage must be locked, which with the +sqlite3 backend is achieved with so called exclusive transaction. +This is needed in order to have consistency when more then a single application use the same storage. + +But locking is not enough to achieve consistency. If an application A already had opened the storage, +and application B reset the identity (changing the master key), and after some time A writes to the +storage it will overwrite values written by B leaving the storage in a state where different values +are encrypted with different keys. + +In order to prevent this, when the master key is created for the first time a 16 byte long random +tag is generated and stored encrypted with the master key into a special key `__tag`. +Then whenever a value must be written after the storage is locked additionally the `__tag` is read +and decrypted. If the tag retrieval or decryption fails, and error is triggered and the write is +cancelled. + +## encrypted values encoding + +As already mentioned the values are encrypted with `AES256-GCM`. + +The encrypted values are encoded according to the BNF grammars described below. + +The primitives used are: +* `uint8` - a single byte. +* `uint64` - unsigned 64 bit integer in Little Endian. +* `bytes` - a sequence of bytes. + +### encoding version 1 + +``` +Bytes = Size Data + Size = uint64 + Data = bytes + +EncryptedValue = Version IV Tag EncryptedData + Version = uint8 + IV = bytes + Tag = bytes + EncryptedData = bytes +``` diff --git a/cpp/src/vereign/kvstore/crypto_storage.hh b/cpp/src/vereign/kvstore/crypto_storage.hh index a258e0ab49081a6b14b087cb24e318567c5fe6e8..671e9abe3d82ba51723c03401a94db82c95d66a7 100644 --- a/cpp/src/vereign/kvstore/crypto_storage.hh +++ b/cpp/src/vereign/kvstore/crypto_storage.hh @@ -12,15 +12,95 @@ class CryptoStorageImpl; } +/** + * Crypto storage is used for securely store sensitive user's data. + * + * This includes user's device private/public key, profile certificates etc. + * + * The CryptoStorage has different implementations per operating system. + * Check detail/linux_crypto_storage.cc and detail/win_crypto_storage.cc. + * + * For more information about the crypto storage design check the [storage spec](@ref vcl_storage_spec) + * in the README.md. + * + */ class CryptoStorage { public: + /** + * Creates CryptoStorage. + * + * @param storage The underlying key/value storage. + * @param disable_key_protection Used only under Windows. If false when the master RSA key + * is created and loaded, the user will be asked for his consent by showing a dialog window + * with text information specified in the UI policy. + */ CryptoStorage(Storage& storage, bool disable_key_protection = false); + + /** + * Default destructor - does nothing. + */ ~CryptoStorage(); + // disable copying + CryptoStorage(const CryptoStorage&) = delete; + auto operator=(const CryptoStorage&) -> CryptoStorage& = delete; + + /** + * Reset the storage, by effectively initializing a new empty storage. + * + * Although the resulting storage is empty it will include some internal data needed for the + * master key management. + * + * @param pin Used only under Linux. A pin code that will be used for derivation of the master key. + * Under windows ncrypt is used for generating a RSA key which is used for encryption of the + * master key, and thus the pin is not used. + * + * @throws Error on failure. + */ void Reset(const std::string& pin); + + /** + * Open the storage. + * + * @param pin Used only under Linux. A pin code that will be used for derivation of the master key. + * Under windows ncrypt is used for generating a RSA key which is used for encryption of the + * master key, and thus the pin is not used. + * + * @throws StorageNotInitializedError when storage is not initialized yet. One must use + * CryptoStorage::Reset to initialize the storage prior calling CryptoStorage::Open. + * + * @throws InvalidPinCodeError (only under Linux) when the pin code is empty or does not match + * the pin code used with CryptoStorage::Reset. + * + * @throws Error when the key derivation under Linux fails. + * + * @throws InvalidIdentityError when the master key could not be decrypted under Windows. + * This possibly means that the RSA key was changed. + */ void Open(const std::string& pin); + /** + * Encrypt and store bytes value into the storage. + * + * @param key The key under which the value will be stored. + * @param value The bytes that will be encrypted and stored. + * + * @throws StorageNotInitializedError when the storage is not initialized. + * @throws InvalidIdentityError when another application had reset the storage. + */ void PutBytes(const std::string& key, bytes::View value); + + /** + * Retrieve and decrypt bytes value from the storage. + * + * @param key The key of the value that will be retrieved. + * @param value Buffer where the value will be returned. + * + * @throws StorageNotInitializedError when the storage is not initialized. + * @throws ValueNotFoundError when there is no value under the provided key. + * @throws encoding::Error when the encrypted value cannot be decoded. + * @throws crypto::OpenSSLError when the value cannot be decrypted. + */ void GetBytes(const std::string& key, bytes::Buffer& value); private: diff --git a/cpp/src/vereign/kvstore/detail/base_crypto_storage.cc b/cpp/src/vereign/kvstore/detail/base_crypto_storage.cc index 940ccb5016ff1e780872451331847df63a8acab2..83bb4c7b1930b76829af0192b6357b674a42f7ae 100644 --- a/cpp/src/vereign/kvstore/detail/base_crypto_storage.cc +++ b/cpp/src/vereign/kvstore/detail/base_crypto_storage.cc @@ -31,14 +31,14 @@ BaseCryptoStorageImpl::BaseCryptoStorageImpl(kvstore::Storage& storage) void BaseCryptoStorageImpl::encryptBytes(const std::string& key, bytes::View value) { if (key_.Size() == 0) { - throw Error("crypto storage is not initialized"); + throw StorageNotInitializedError{"key not initialized"}; } bytes::Buffer iv; bytes::Buffer tag; bytes::Buffer encrypted; - crypto::aes::GCM256Encrypt(value, key_.View(), iv, encrypted, tag); + crypto::aes::GCM256Encrypt(value, key_.View(), iv, tag, encrypted); bytes::Buffer encoded_value; EncodeEncryptedValue(encoded_value, iv.View(), tag.View(), encrypted.View()); @@ -49,7 +49,9 @@ void BaseCryptoStorageImpl::encryptBytes(const std::string& key, bytes::View val void BaseCryptoStorageImpl::PutBytes(const std::string& key, bytes::View value) { kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep}; - validateTag(); + if (!isTagValid()) { + throw InvalidIdentityError{}; + } encryptBytes(key, value); } @@ -58,7 +60,7 @@ void BaseCryptoStorageImpl::GetBytes(const std::string& key, bytes::Buffer& valu kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep}; if (key_.Size() == 0) { - throw Error("crypto storage is not initialized"); + throw StorageNotInitializedError{"key not initialized"}; } bytes::Buffer encoded; @@ -76,18 +78,19 @@ void BaseCryptoStorageImpl::initKey(bytes::Buffer&& key) { key_ = std::move(key); } -void BaseCryptoStorageImpl::validateTag() const { +auto BaseCryptoStorageImpl::isTagValid() const -> bool { bytes::Buffer tag; try { GetBytes("__tag", tag); - } catch (const crypto::Error& err) { - throw IdentityChanged{}; + + return true; + } catch (const crypto::Error&) { + return false; } } void BaseCryptoStorageImpl::updateTag() { - bytes::Buffer tag{tagSizeBytes}; - crypto::Rand(tag); + auto tag = crypto::Rand(tagSizeBytes); encryptBytes("__tag", tag.View()); } diff --git a/cpp/src/vereign/kvstore/detail/base_crypto_storage.hh b/cpp/src/vereign/kvstore/detail/base_crypto_storage.hh index 2351c822b6f0782027b12f3f2ff53f38ea9cb50a..49278c3f6847d4555dfd3ba58fc52a38a7d05e07 100644 --- a/cpp/src/vereign/kvstore/detail/base_crypto_storage.hh +++ b/cpp/src/vereign/kvstore/detail/base_crypto_storage.hh @@ -18,7 +18,7 @@ public: protected: void initKey(bytes::Buffer&& key); - void validateTag() const; + auto isTagValid() const -> bool; void updateTag(); private: diff --git a/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc b/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc index 03a6c26c3b8dba1e79492dec86c937aa8bc5901d..6a19e57500e6b570ae2ce181891618351ec335b4 100644 --- a/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc +++ b/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc @@ -33,14 +33,25 @@ CryptoStorageImpl::CryptoStorageImpl(kvstore::Storage& storage, bool disable_key } void CryptoStorageImpl::Open(const std::string& pin) { + if (pin.empty()) { + throw InvalidPinCodeError{}; + } + kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep}; bytes::Buffer salt; - auto iterations = storage_.GetInt64("__master_key_iterations"); + + int64_t iterations = 0; + try { + iterations = storage_.GetInt64("__master_key_iterations"); + storage_.GetBytes("__master_key_salt", salt); + } catch (const std::exception& e) { + throw StorageNotInitializedError{e.what()}; + } + if (iterations == 0) { - throw StorageNotInitializedError{}; + throw StorageNotInitializedError{"iterations cannot be zero"}; } - storage_.GetBytes("__master_key_salt", salt); bytes::Buffer key{aesKeySizeBytes}; @@ -51,7 +62,7 @@ void CryptoStorageImpl::Open(const std::string& pin) { salt.View().Size(), iterations, EVP_sha256(), - key.FreeCap(), + aesKeySizeBytes, key.end() ); if (result == 0) { @@ -61,13 +72,17 @@ void CryptoStorageImpl::Open(const std::string& pin) { key.IncSize(aesKeySizeBytes); initKey(std::move(key)); - // FIXME: write tests for tampering and regular identity change - validateTag(); + if (!isTagValid()) { + throw InvalidPinCodeError{}; + } } void CryptoStorageImpl::Reset(const std::string& pin) { - bytes::Buffer salt{saltSizeBytes}; - crypto::Rand(salt); + if (pin.empty()) { + throw InvalidPinCodeError{}; + } + + auto salt = crypto::Rand(saltSizeBytes); bytes::Buffer key{aesKeySizeBytes}; @@ -78,7 +93,7 @@ void CryptoStorageImpl::Reset(const std::string& pin) { salt.View().Size(), iterations, EVP_sha256(), - key.FreeCap(), + aesKeySizeBytes, key.end() ); if (result == 0) { diff --git a/cpp/src/vereign/kvstore/detail/value_encoder.cc b/cpp/src/vereign/kvstore/detail/value_encoder.cc new file mode 100644 index 0000000000000000000000000000000000000000..6144bebbdffdee83f9797cf063d9729b34d96c35 --- /dev/null +++ b/cpp/src/vereign/kvstore/detail/value_encoder.cc @@ -0,0 +1,42 @@ +#include <vereign/kvstore/detail/value_encoder.hh> + +#include <vereign/kvstore/errors.hh> +#include <vereign/encoding/binary.hh> + +namespace vereign::kvstore::detail { + +void EncodeEncryptedValue( + bytes::Buffer& out, + bytes::View iv, + bytes::View tag, + bytes::View encrypted +) { + // 25 = buffers size numbers (3 int64) plus 1 byte for the version + out.Reserve(iv.Size() + tag.Size() + encrypted.Size() + 25); + // encode version + encoding::binary::EncodeUint8(out, 1); + + // encode data + encoding::binary::EncodeBytes(out, iv); + encoding::binary::EncodeBytes(out, tag); + encoding::binary::EncodeBytes(out, encrypted); +} + +void DecodeEncryptedValue( + bytes::View encoded, + bytes::View& iv, + bytes::View& tag, + bytes::View& encrypted +) { + auto version = encoding::binary::DecodeUint8(encoded); + if (version != 1) { + throw Error("decoding encrypted value failed: invalid version"); + } + + auto offset = 1; + offset += encoding::binary::DecodeBytes(encoded.Slice(offset), iv); + offset += encoding::binary::DecodeBytes(encoded.Slice(offset), tag); + offset += encoding::binary::DecodeBytes(encoded.Slice(offset), encrypted); +} + +} // namespace vereign::kvstore::detail diff --git a/cpp/src/vereign/kvstore/detail/value_encoder.hh b/cpp/src/vereign/kvstore/detail/value_encoder.hh index 101b77fa08f04c73ecf940239625c6c2de8c5c71..3c9e532853a853febaadf44b39c7941c1f0f28e1 100644 --- a/cpp/src/vereign/kvstore/detail/value_encoder.hh +++ b/cpp/src/vereign/kvstore/detail/value_encoder.hh @@ -3,46 +3,43 @@ #include <vereign/bytes/view.hh> #include <vereign/bytes/buffer.hh> -#include <vereign/encoding/binary.hh> - -#include <vereign/kvstore/errors.hh> namespace vereign::kvstore::detail { -// FIXME: move to implementation file -inline void EncodeEncryptedValue( +/** + * Encodes value encrypted with AES in GCM mode. + * + * @param out The encoded result. + * @param iv The IV of the encrypted value. + * @param tag The tag of the encrypted value. + * @param encrypted The encrypted bytes. + */ +void EncodeEncryptedValue( bytes::Buffer& out, bytes::View iv, bytes::View tag, bytes::View encrypted -) { - // 25 = buffers size numbers (3 int64) plus 1 byte for the version - out.Reserve(iv.Size() + tag.Size() + encrypted.Size() + 25); - // encode version - encoding::binary::EncodeUint8(out, 1); - - // encode data - encoding::binary::EncodeBytes(out, iv); - encoding::binary::EncodeBytes(out, tag); - encoding::binary::EncodeBytes(out, encrypted); -} - -inline void DecodeEncryptedValue( +); + +/** + * Decode value encrypted with AES in GCM mode. + * + * Note that iv, tag and encrypted are decoded in-place, meaning that they point to the same memory + * of the `encoded` input. + * + * @param out The encoded value. + * @param iv The decoded IV of the encrypted value. + * @param tag The decoded tag of the encrypted value. + * @param encrypted The decoded encrypted bytes. + * + * @throws kvstore::Error on failure. + */ +void DecodeEncryptedValue( bytes::View encoded, bytes::View& iv, bytes::View& tag, bytes::View& encrypted -) { - auto version = encoding::binary::DecodeUint8(encoded); - if (version != 1) { - throw Error("decoding encrypted value failed: invalid version"); - } - - auto offset = 1; - offset += encoding::binary::DecodeBytes(encoded.Slice(offset), iv); - offset += encoding::binary::DecodeBytes(encoded.Slice(offset), tag); - offset += encoding::binary::DecodeBytes(encoded.Slice(offset), encrypted); -} +); } // namespace vereign::kvstore::detail diff --git a/cpp/src/vereign/kvstore/detail/win_crypto_storage.cc b/cpp/src/vereign/kvstore/detail/win_crypto_storage.cc index 271acf4bdb6f12db5af2fb80a86e9cb6ddd258ae..f3090c6687a4b40ae5f57fde6981bbdc4243cbf6 100644 --- a/cpp/src/vereign/kvstore/detail/win_crypto_storage.cc +++ b/cpp/src/vereign/kvstore/detail/win_crypto_storage.cc @@ -13,6 +13,7 @@ #include <boost/core/ignore_unused.hpp> #include <vereign/ncrypt/rsa.hh> +#include <vereign/ncrypt/errors.hh> #include <chrono> @@ -25,7 +26,6 @@ namespace { constexpr auto lockRetrySleep = std::chrono::milliseconds{1000}; // FIXME: ask business for these values - constexpr const auto vereignKey = std::string_view{"vereign_key"}; constexpr const auto vereignKeyCreationTitle = std::string_view{"Vereign Client"}; constexpr const auto vereignKeyDescription = std::string_view{ "Vereign Client will use this key to authenticate with the Vereign Services" @@ -45,20 +45,26 @@ void CryptoStorageImpl::Open(const std::string& pin) { kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep}; auto provider = ncrypt::rsa::OpenStorageProvider(); - auto rsa_key = ncrypt::rsa::LoadKey(provider.Get(), std::string(vereignKey)); + auto rsa_key = ncrypt::rsa::LoadKey(provider.Get(), std::string(VereignKeyName)); if (!rsa_key) { - throw IdentityChanged{}; + throw StorageNotInitializedError{"key not initialized"}; } - bytes::Buffer encrypted_key; - storage_.GetBytes("__master_key", encrypted_key); - bytes::Buffer key; - ncrypt::rsa::PrivateKeyDecrypt(rsa_key.Get(), encrypted_key.View(), key); + try { + bytes::Buffer encrypted_key; + storage_.GetBytes("__master_key", encrypted_key); + + ncrypt::rsa::PrivateKeyDecrypt(rsa_key.Get(), encrypted_key.View(), key); + } catch(const std::exception&) { + throw InvalidIdentityError{}; + } initKey(std::move(key)); // FIXME: write tests for tampering and regular identity change - validateTag(); + if (!isTagValid()) { + throw InvalidIdentityError{}; + } } void CryptoStorageImpl::Reset(const std::string& pin) { @@ -66,7 +72,7 @@ void CryptoStorageImpl::Reset(const std::string& pin) { kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep}; auto provider = ncrypt::rsa::OpenStorageProvider(); - auto old_key = ncrypt::rsa::LoadKey(provider.Get(), std::string(vereignKey)); + auto old_key = ncrypt::rsa::LoadKey(provider.Get(), std::string(VereignKeyName)); if (old_key) { ncrypt::rsa::DeleteKey(old_key.Get()); } @@ -83,7 +89,7 @@ void CryptoStorageImpl::Reset(const std::string& pin) { auto rsa_key = ncrypt::rsa::CreateKey( provider.Get(), keySizeBits, - std::string(vereignKey), + std::string(VereignKeyName), ui_policy ); diff --git a/cpp/src/vereign/kvstore/errors.hh b/cpp/src/vereign/kvstore/errors.hh index 371f273b1257ba5a464f2d03737a8bb86536707e..c119c510273cce58d67071e56da2ad16f43983da 100644 --- a/cpp/src/vereign/kvstore/errors.hh +++ b/cpp/src/vereign/kvstore/errors.hh @@ -13,10 +13,18 @@ public: } }; +class ValueNotFoundError : public Error { +public: + ValueNotFoundError() + : Error{"value not found"} + { + } +}; + class StorageNotInitializedError : public Error { public: - StorageNotInitializedError() - : Error{"storage is not initialized"} + StorageNotInitializedError(const std::string& reason) + : Error{"storage is not initialized: " + reason} { } }; @@ -29,10 +37,18 @@ public: } }; -class IdentityChanged : public Error { +class InvalidPinCodeError : public Error { +public: + InvalidPinCodeError() + : Error{"invalid pin code"} + { + } +}; + +class InvalidIdentityError : public Error { public: - IdentityChanged() - : Error{"identity has been changed"} + InvalidIdentityError() + : Error{"invalid identity"} { } }; diff --git a/cpp/src/vereign/kvstore/lock.cc b/cpp/src/vereign/kvstore/lock.cc index 65935c0c3583dfcba3cb52bd11b2c204d4f48056..5a0863e967ea6de6243652a7bbb1752c8a286480 100644 --- a/cpp/src/vereign/kvstore/lock.cc +++ b/cpp/src/vereign/kvstore/lock.cc @@ -21,7 +21,7 @@ Lock::Lock(Storage& storage, int retry_count, std::chrono::milliseconds sleep_in storage.Lock(); return; - } catch (const LockError& err) { + } catch (const LockError&) { std::this_thread::sleep_for(sleep_interval); continue; diff --git a/cpp/src/vereign/kvstore/sqlite_storage.cc b/cpp/src/vereign/kvstore/sqlite_storage.cc index 3e45d0f5f6bd74806b7ad2a1a5f49d9c9e765a5e..4789346fef43cde99a8701c620d2d93eea7f0fb7 100644 --- a/cpp/src/vereign/kvstore/sqlite_storage.cc +++ b/cpp/src/vereign/kvstore/sqlite_storage.cc @@ -90,9 +90,11 @@ void SqliteStorage::GetBytes(const std::string& key, bytes::Buffer& value) { stmt.BindText(1, key); auto end = stmt.Step(); - if (!end) { - value.Write(stmt.GetColumnBlob(0)); + if (end) { + throw ValueNotFoundError{}; } + + value.Write(stmt.GetColumnBlob(0)); } void SqliteStorage::PutInt64(const std::string& key, int64_t value) { @@ -115,7 +117,7 @@ auto SqliteStorage::GetInt64(const std::string& key) -> int64_t { auto end = stmt.Step(); if (end) { - return 0; + throw ValueNotFoundError{}; } auto buf = stmt.GetColumnBlob(0); diff --git a/cpp/src/vereign/kvstore/storage.hh b/cpp/src/vereign/kvstore/storage.hh index aa21c54684d0aef25313459f9b0f0194847beff1..a190bd54fc573ff0512c80e10f7283cbd88e9542 100644 --- a/cpp/src/vereign/kvstore/storage.hh +++ b/cpp/src/vereign/kvstore/storage.hh @@ -5,6 +5,8 @@ namespace vereign::kvstore { +constexpr const auto VereignKeyName = std::string_view{"vereign_key"}; + class Storage { public: virtual void Lock() = 0; diff --git a/cpp/src/vereign/ncrypt/errors.hh b/cpp/src/vereign/ncrypt/errors.hh index a37056c02947ccdcd0aa49841aaeb4593dd6c06e..fe6920132b3e3cc1f1596c68af9671c3cfa2764f 100644 --- a/cpp/src/vereign/ncrypt/errors.hh +++ b/cpp/src/vereign/ncrypt/errors.hh @@ -7,14 +7,37 @@ namespace vereign::ncrypt { +// windows returns this error when there is unexpected NULL input constexpr SECURITY_STATUS NTE_NULL_REFERENCE_POINTER = 0x800706f4; +/** + * Returns a string representation of the SECURITY_STATUS errors. + * + * @param status The SECURITY_STATUS error. + * @returns a string representation of the SECURITY_STATUS error. + */ auto SecurityStatusToString(SECURITY_STATUS status) -> std::string; +/** + * The base error type for the namespace vereign::ncrypto. + */ class Error : public std::runtime_error { public: + /** + * Creates Error with status and additional message. + * + * Example: + * @code + * auto err = Error{NTE_FAIL, "operation failed"}; + * assert(err.what() == "operation failed: NTE_FAIL"); + * assert(err.SecurityStatus() == NTE_FAIL); + * @endcode + */ Error(SECURITY_STATUS status, const std::string& msg); + /** + * Returns the SECURITY_STATUS associated with this error. + */ auto SecurityStatus() const -> SECURITY_STATUS; private: diff --git a/cpp/src/vereign/ncrypt/rsa.hh b/cpp/src/vereign/ncrypt/rsa.hh index 6cb4048d401b997f6f4f8ba79b38d5c9eab8581c..06d3f05d2cb1892b6a43956c6e6940f6d6b3d0a0 100644 --- a/cpp/src/vereign/ncrypt/rsa.hh +++ b/cpp/src/vereign/ncrypt/rsa.hh @@ -11,23 +11,138 @@ namespace vereign::ncrypt::rsa { +/** + * KeyUIPolicy is used to specify some UI captions shown to the user in a dialog when a key is + * created or opened for the first time in a process. + */ struct KeyUIPolicy { std::string_view CreationTitle; std::string_view Description; std::string_view FriendlyName; }; +/** + * Opens the default ncrypt storage provider. + * + * @returns the storage provider handle. + * + * @throws ncrypt::Error on failure. + */ auto OpenStorageProvider() -> UniquePtr; + +/** + * Loads a key from a ncrypt storage provider. + * + * Example: + * @code + * auto provider = ncrypt::rsa::OpenStorageProvider(); + * auto key = ncrypt::rsa::LoadKey(provider.Get(), "test_key"); + * assert(key.Get() != 0); + * @endcode + * + * @param provider The ncrypt storage provider. + * @param key_name The name of the key that will be loaded. + * @returns the loaded key if the key exists, or null ncrypt::UniquePtr if the key does not exists. + * + * @throws ncrypt::Error on failure. + */ auto LoadKey(NCRYPT_PROV_HANDLE provider, const std::string& key_name) -> UniquePtr; + +/** + * Creates persistent RSA key into a given storage provider. + * + * Example: + * @code + * auto provider = ncrypt::rsa::OpenStorageProvider(); + * // create key without UI policy + * auto key = ncrypt::rsa::CreateKey(provider.Get(), 2048, "test_key", {}); + * assert(key.Get() != 0); + * @endcode + * + * @param provider The ncrypt storage provider. + * @param bits The length of the key in bits. + * @param key_name The new key name. + * @param ui_policy The UI policy. If the UI policy is not provided, the key will not have any + * protection. If the UI policy is provided when the key is created and loaded the user will be + * asked for his consent by showing a dialog window with text information specified in the UI policy. + * + * @returns the created key on success. + * + * @throws ncrypt::Error on failure. + */ auto CreateKey( NCRYPT_PROV_HANDLE provider, int bits, const std::string& key_name, std::optional<KeyUIPolicy> ui_policy ) -> UniquePtr; + +/** + * Deletes a key. + * + * Example: + * @code + * auto provider = ncrypt::rsa::OpenStorageProvider(); + * auto key = ncrypt::rsa::LoadKey(provider.Get(), "test_key"); + * if (key) { + * ncryp::rsa::DeleteKey(key.Get()); + * } + * @endcode + * + * @param key The key to delete. + * + * @throws ncrypt::Error on failure. + */ void DeleteKey(NCRYPT_KEY_HANDLE key); +/** + * Encrypts given bytes with RSA public key. + * + * Example: + * @code + * const std::string input{"foo bar"}; + * auto provider = ncrypt::rsa::OpenStorageProvider(); + * auto key = ncrypt::rsa::CreateKey(provider.Get(), 2048, "test_key", {}); + * + * bytes::Buffer encrypted; + * ncrypt::rsa::PublicKeyEncrypt(key.Get(), bytes::View(input), encrypted); + * + * bytes::Buffer decrypted; + * ncrypt::rsa::PrivateKeyDecrypt(key.Get(), encrypted.View(), decrypted); + * assert(decrypted.View() == encrypted.View()); + * @endcode + * + * @param key The RSA key. + * @param src The bytes that will be encrypted. + * @param encrypted The result of the encryption. + * + * @throws ncrypt::Error on failure. + */ void PublicKeyEncrypt(NCRYPT_KEY_HANDLE key, bytes::View src, bytes::Buffer& encrypted); + +/** + * Decrypts given bytes with RSA private key. + * + * Example: + * @code + * const std::string input{"foo bar"}; + * auto provider = ncrypt::rsa::OpenStorageProvider(); + * auto key = ncrypt::rsa::CreateKey(provider.Get(), 2048, "test_key", {}); + * + * bytes::Buffer encrypted; + * ncrypt::rsa::PublicKeyEncrypt(key.Get(), bytes::View(input), encrypted); + * + * bytes::Buffer decrypted; + * ncrypt::rsa::PrivateKeyDecrypt(key.Get(), encrypted.View(), decrypted); + * assert(decrypted.View() == encrypted.View()); + * @endcode + * + * @param key The RSA key. + * @param src The bytes that will be decrypted. + * @param decrypted The result of the decryption. + * + * @throws ncrypt::Error on failure. + */ void PrivateKeyDecrypt(NCRYPT_KEY_HANDLE key, bytes::View src, bytes::Buffer& decrypted); } // vereign::ncrypt::rsa diff --git a/cpp/src/vereign/ncrypt/unique_ptr.cc b/cpp/src/vereign/ncrypt/unique_ptr.cc index 06e7d2c9239e9b68b4877b7b78ff8621ef897837..a177839f4ec6c2a12780a5e204db45e92965ecd1 100644 --- a/cpp/src/vereign/ncrypt/unique_ptr.cc +++ b/cpp/src/vereign/ncrypt/unique_ptr.cc @@ -11,8 +11,8 @@ UniquePtr::UniquePtr() noexcept { } -UniquePtr::UniquePtr(ULONG_PTR ptr) noexcept - : ptr_{ptr} +UniquePtr::UniquePtr(ULONG_PTR handle) noexcept + : ptr_{handle} { } diff --git a/cpp/src/vereign/ncrypt/unique_ptr.hh b/cpp/src/vereign/ncrypt/unique_ptr.hh index 8341e3b08bbd08848a06c0931067ac23848e03e1..ae4475300c7399f136810082988e4c19d3495666 100644 --- a/cpp/src/vereign/ncrypt/unique_ptr.hh +++ b/cpp/src/vereign/ncrypt/unique_ptr.hh @@ -5,23 +5,88 @@ namespace vereign::ncrypt { +/** + * Holds ownership of ncrypt handles like NCRYPT_PROV_HANDLE, NCRYPT_PROV_HANDLE. + * + * The UniquePtr should be used any time a ncrypt API returns a handle in order to ensure exception + * safety and resource cleanup. + * + * The owned handler is typically cleaned up in the UniquePtr::~UniquePtr() by using + * NCryptFreeObject function. + */ class UniquePtr { public: + /** + * Constructs a default empty object. + */ UniquePtr() noexcept; - explicit UniquePtr(ULONG_PTR ptr) noexcept; + /** + * Constructs a UniquePtr that becomes an owner of the passed handle. + * + * @param handle The handle that becomes owned by the crated UniquePtr. + */ + explicit UniquePtr(ULONG_PTR handle) noexcept; + + /** + * Move constructor. + */ UniquePtr(UniquePtr&& other) noexcept; + + /** + * Move assignment operator. + */ auto operator=(UniquePtr&& other) noexcept -> UniquePtr&; + // Disable copying UniquePtr(const UniquePtr&) = delete; auto operator=(const UniquePtr&) -> UniquePtr& = delete; - ~UniquePtr() noexcept ; + /** + * Upon destruction the owned handle if any is freed with NCryptFreeObject function. + */ + ~UniquePtr() noexcept; + /** + * Retrieve the owned handle. + */ auto Get() const noexcept -> ULONG_PTR; + + /** + * Retrieve a pointer to the owned handle. + * + * Example: + * @code + * UniquePtr key{}; + * NCryptOpenKey(provider, key.Ref(), L"test_key", 0, 0); + * assert(bool(key) == true); + * @endcode + * + * This makes it possible for a ncrypt API to inject its output handle directly into the + * UniquePtr object. + */ auto Ref() noexcept -> ULONG_PTR*; + + /** + * Free the owned handler. + * + * If the owned handler is not null, it frees the handle by calling NCryptFreeObject, and sets + * the owned handler to null. + */ void Reset(); + + /** + * Releases the ownership of the owned handler. + * + * @returns the owned handler, before releasing the ownership. + */ auto Release() -> ULONG_PTR; + + /** + * Checks if *this owns a non-null handler. + * + * @returns true if *this owns a handler, false otherwise. + */ operator bool() const noexcept; private: diff --git a/cpp/tests/integration/init_integration_test_storage.cc b/cpp/tests/integration/init_integration_test_storage.cc index 32a3fe0063b8a1dd357579d5452ac8cfa1917138..ff234034cd50f576d43b70263a48f724013b4292 100644 --- a/cpp/tests/integration/init_integration_test_storage.cc +++ b/cpp/tests/integration/init_integration_test_storage.cc @@ -28,7 +28,7 @@ auto main(int argc, char** argv) -> int { storage_path = fs::path::Join(fs::HomePath(), "vereign_integration_test"); } - fs::CreateDirectory(storage_path); + fs::CreateDir(storage_path); test::PrepareNewDevice(host, port, public_key, pin, storage_path); } diff --git a/cpp/tests/vereign/CMakeLists.txt b/cpp/tests/vereign/CMakeLists.txt index 3994b8bad658abf6c3d90ca345bf4f88ed7a9964..0bcfbc6e919333d7cae2525229b4c53b0cf21247 100644 --- a/cpp/tests/vereign/CMakeLists.txt +++ b/cpp/tests/vereign/CMakeLists.txt @@ -20,6 +20,10 @@ list(APPEND TESTS_SRC sync/channel_test.cc encoding/base64_test.cc + encoding/hex_test.cc + + bytes/buffer_test.cc + bytes/view_test.cc crypto/aes_test.cc crypto/rsa_test.cc @@ -39,6 +43,7 @@ list(APPEND TESTS_SRC grpc/server_test.cc grpc/json/encoder_test.cc + grpc/identity_api_test.cc ) if (WIN32) diff --git a/cpp/tests/vereign/bytes/buffer_test.cc b/cpp/tests/vereign/bytes/buffer_test.cc new file mode 100644 index 0000000000000000000000000000000000000000..710c3c184d2111aae21dc02c9b29a9939850842f --- /dev/null +++ b/cpp/tests/vereign/bytes/buffer_test.cc @@ -0,0 +1,491 @@ +#include <vereign/bytes/buffer.hh> + +#include <vereign/bytes/errors.hh> + +#include <catch2/catch.hpp> + +using namespace vereign; + +TEST_CASE("bytes::Buffer::Buffer", "[vereign/bytes]") { + SECTION("empty buffer") { + auto buf = bytes::Buffer{}; + + CHECK(buf.Size() == 0); + CHECK(buf.Cap() == 0); + CHECK(buf.FreeCap() == 0); + } + + SECTION("buffer with predefined capacity") { + auto buf = bytes::Buffer{16}; + + CHECK(buf.Size() == 0); + CHECK(buf.Cap() == 16); + CHECK(buf.FreeCap() == 16); + } + + SECTION("buffer from a view") { + auto buf = bytes::Buffer{bytes::View{"foo bar"}}; + + CHECK(buf.Size() == 7); + CHECK(buf.Cap() == 7); + CHECK(buf.FreeCap() == 0); + CHECK(buf.View().String() == "foo bar"); + } + + SECTION("move construct") { + auto old_buf = bytes::Buffer{bytes::View{"foo bar"}}; + auto buf = std::move(old_buf); + + CHECK(old_buf.Size() == 0); + CHECK(old_buf.Cap() == 0); + CHECK(old_buf.FreeCap() == 0); + + CHECK(buf.Size() == 7); + CHECK(buf.Cap() == 7); + CHECK(buf.FreeCap() == 0); + CHECK(buf.View().String() == "foo bar"); + + auto empty = bytes::Buffer{}; + auto new_empty = std::move(empty); + + CHECK(empty.Size() == 0); + CHECK(empty.Cap() == 0); + CHECK(empty.FreeCap() == 0); + + CHECK(new_empty.Size() == 0); + CHECK(new_empty.Cap() == 0); + CHECK(new_empty.FreeCap() == 0); + } + + SECTION("move assign") { + auto foo = bytes::Buffer{bytes::View{"foo"}}; + auto bar = bytes::Buffer{bytes::View{"bar"}}; + + foo = std::move(bar); + + CHECK(foo.Size() == 3); + CHECK(foo.Cap() == 3); + CHECK(foo.FreeCap() == 0); + CHECK(foo.View().String() == "bar"); + + CHECK(bar.Size() == 3); + CHECK(bar.Cap() == 3); + CHECK(bar.FreeCap() == 0); + CHECK(bar.View().String() == "foo"); + + auto empty = bytes::Buffer{}; + foo = std::move(empty); + + CHECK(foo.Size() == 0); + CHECK(foo.Cap() == 0); + CHECK(foo.FreeCap() == 0); + CHECK(foo.View().String() == ""); + + CHECK(empty.Size() == 3); + CHECK(empty.Cap() == 3); + CHECK(empty.FreeCap() == 0); + CHECK(empty.View().String() == "bar"); + + empty = std::move(foo); + + CHECK(empty.Size() == 0); + CHECK(empty.Cap() == 0); + CHECK(empty.FreeCap() == 0); + CHECK(empty.View().String() == ""); + + CHECK(foo.Size() == 3); + CHECK(foo.Cap() == 3); + CHECK(foo.FreeCap() == 0); + CHECK(foo.View().String() == "bar"); + } +} + +TEST_CASE("bytes::Buffer::begin", "[vereign/bytes]") { + SECTION("empty buffer") { + auto buf = bytes::Buffer{}; + + CHECK(buf.begin() == nullptr); + } + + SECTION("non empty buffer") { + auto buf = bytes::Buffer{bytes::View("foo bar")}; + + CHECK(buf.begin() != nullptr); + + std::string s; + for (auto& it : buf) { + s += it; + } + + CHECK(s == "foo bar"); + + for (auto& it : buf) { + it = 'x'; + } + + CHECK(buf.View().String() == "xxxxxxx"); + } +} + +TEST_CASE("bytes::Buffer::end", "[vereign/bytes]") { + SECTION("empty buffer") { + auto buf = bytes::Buffer{}; + + CHECK(buf.end() == nullptr); + } + + SECTION("non empty buffer") { + auto buf = bytes::Buffer{bytes::View("foo bar")}; + + CHECK(buf.end() != nullptr); + + std::string s; + for (const auto& it : buf) { + s += it; + } + + CHECK(s == "foo bar"); + + for (auto& it : buf) { + it = 'x'; + } + + CHECK(buf.View().String() == "xxxxxxx"); + } + + SECTION("append to the end") { + auto buf = bytes::Buffer{bytes::View("foo bar")}; + + buf.Reserve(4); + std::strncpy((char*)buf.end(), " baz", 4); + buf.IncSize(4); + + CHECK(buf.Size() == 11); + CHECK(buf.View().String() == "foo bar baz"); + } +} + +TEST_CASE("bytes::Buffer::operator[]", "[vereign/bytes]") { + SECTION("empty buffer") { + auto buf = bytes::Buffer{}; + + CHECK_THROWS_AS( + buf[0], + bytes::IndexOutOfBounds + ); + } + + SECTION("non empty buffer") { + auto buf = bytes::Buffer{bytes::View("foo")}; + buf.Reserve(16); + + CHECK(buf.Cap() == 19); + CHECK(buf[0] == 'f'); + CHECK(buf[1] == 'o'); + CHECK(buf[2] == 'o'); + + CHECK_THROWS_AS( + buf[19], + bytes::IndexOutOfBounds + ); + } + + SECTION("modify buffer") { + auto buf = bytes::Buffer{bytes::View("foo")}; + + buf[0] = 'b'; + buf[1] = 'a'; + buf[2] = 'r'; + + CHECK_THROWS_AS( + buf[3] = 'x', + bytes::IndexOutOfBounds + ); + + CHECK(buf.View().String() == "bar"); + } +} + +TEST_CASE("bytes::Buffer::Size", "[vereign/bytes]") { + SECTION("empty buffer") { + auto buf = bytes::Buffer{}; + + CHECK(buf.Size() == 0); + } + + SECTION("non empty buffer") { + auto buf = bytes::Buffer{bytes::View("foo")}; + + CHECK(buf.Size() == 3); + } + + SECTION("append to buffer") { + auto buf = bytes::Buffer{bytes::View("foo")}; + buf.Write(bytes::View(" bar")); + + CHECK(buf.Size() == 7); + } +} + +TEST_CASE("bytes::Buffer::Cap", "[vereign/bytes]") { + SECTION("empty buffer") { + auto buf = bytes::Buffer{}; + + CHECK(buf.Cap() == 0); + } + + SECTION("non empty buffer") { + auto buf = bytes::Buffer{bytes::View("foo")}; + + CHECK(buf.Cap() == 3); + } + + SECTION("append to buffer") { + auto buf = bytes::Buffer{bytes::View("foo")}; + + // capacity is doubled + buf.Write(bytes::View("b")); + + CHECK(buf.Cap() == 6); + } +} + +TEST_CASE("bytes::Buffer::FreeCap", "[vereign/bytes]") { + SECTION("empty buffer") { + auto buf = bytes::Buffer{}; + + CHECK(buf.FreeCap() == 0); + } + + SECTION("non empty buffer") { + auto buf = bytes::Buffer{bytes::View("foo")}; + + CHECK(buf.Cap() == 3); + CHECK(buf.FreeCap() == 0); + + buf.Reserve(1); + + CHECK(buf.Cap() - buf.Size() == buf.FreeCap()); + } +} + +TEST_CASE("bytes::Buffer::Reserve", "[vereign/bytes]") { + SECTION("empty buffer") { + auto buf = bytes::Buffer{}; + + buf.Reserve(8); + CHECK(buf.Cap() == 8); + CHECK(buf.Size() == 0); + } + + SECTION("when the required reservation is less than the old capacity*2, it reserves 2*capacity") { + { + auto buf = bytes::Buffer{bytes::View("foo")}; + + CHECK(buf.Cap() == 3); + CHECK(buf.Size() == 3); + + // capacity is doubled + buf.Reserve(1); + + CHECK(buf.Cap() == 6); + CHECK(buf.FreeCap() == 3); + CHECK(buf.Size() == 3); + } + + { + auto buf = bytes::Buffer{bytes::View("foo")}; + + CHECK(buf.Cap() == 3); + CHECK(buf.Size() == 3); + + // capacity is doubled + buf.Reserve(2); + + CHECK(buf.Cap() == 6); + CHECK(buf.FreeCap() == 3); + CHECK(buf.Size() == 3); + } + + { + auto buf = bytes::Buffer{bytes::View("foo")}; + + CHECK(buf.Cap() == 3); + CHECK(buf.Size() == 3); + + // capacity is doubled + buf.Reserve(3); + + CHECK(buf.Cap() == 6); + CHECK(buf.FreeCap() == 3); + CHECK(buf.Size() == 3); + } + } + + SECTION("when the required reservation is greater than the old capacity*2, " + "it reserves capacity+required size") { + auto buf = bytes::Buffer{bytes::View("foo")}; + + CHECK(buf.Cap() == 3); + CHECK(buf.Size() == 3); + + buf.Reserve(4); + + CHECK(buf.Cap() == 7); + CHECK(buf.FreeCap() == 4); + CHECK(buf.Size() == 3); + } +} + +TEST_CASE("bytes::Buffer::IncSize", "[vereign/bytes]") { + SECTION("when IncSize is called on empty buffer, it throws bytes::IncrementOutOfBounds") { + auto buf = bytes::Buffer{}; + + CHECK_THROWS_AS( + buf.IncSize(10), + bytes::IncrementOutOfBounds + ); + } + + SECTION("when IncSize is called with value bigger than the free cap, " + "it throws bytes::IncrementOutOfBounds") { + + auto buf = bytes::Buffer{3}; + + std::strncpy((char*)buf.end(), "bar", 3); + + CHECK_THROWS_AS( + buf.IncSize(4), + bytes::IncrementOutOfBounds + ); + } + + SECTION("when IncSize is called with zero, it does nothing") { + bytes::Buffer buf; + buf.IncSize(0); + + CHECK(buf.Size() == 0); + } + + SECTION("success") { + auto buf = bytes::Buffer{3}; + + std::strncpy((char*)buf.end(), "bar", 3); + + buf.IncSize(3); + + CHECK(buf.Size() == 3); + CHECK(buf.View().String() == "bar"); + } +} + +TEST_CASE("bytes::Buffer::WriteWithinCap", "[vereign/bytes]") { + SECTION("when WriteWithinCap is called on empty buffer, it does nothing") { + auto buf = bytes::Buffer{}; + buf.WriteWithinCap(bytes::View("foo")); + + CHECK(buf.Size() == 0); + CHECK(buf.Cap() == 0); + } + + SECTION("when WriteWithinCap is called with empty source, it does nothing") { + auto buf = bytes::Buffer{3}; + buf.WriteWithinCap(bytes::View()); + + CHECK(buf.Size() == 0); + CHECK(buf.Cap() == 3); + } + + SECTION("when WriteWithinCap is called with source bigger than the free cap, " + "it copies only free cap amount of bytes") { + + auto buf = bytes::Buffer{3}; + buf.WriteWithinCap(bytes::View("foo bar")); + + CHECK(buf.Size() == 3); + CHECK(buf.View().String() == "foo"); + } + + SECTION("when WriteWithinCap is called with source smaller than the free cap, " + "it copies the full source of bytes") { + + auto buf = bytes::Buffer{7}; + buf.WriteWithinCap(bytes::View("foo bar")); + + CHECK(buf.Size() == 7); + CHECK(buf.View().String() == "foo bar"); + } +} + +TEST_CASE("bytes::Buffer::Write", "[vereign/bytes]") { + SECTION("when Write is called on empty buffer, " + "it expands the buffer and writes all the source bytes") { + auto buf = bytes::Buffer{}; + buf.Write(bytes::View("foo")); + + CHECK(buf.Size() == 3); + CHECK(buf.Cap() == 3); + CHECK(buf.View().String() == "foo"); + } + + SECTION("when Write is called with empty source, it does nothing") { + auto buf = bytes::Buffer{3}; + buf.Write(bytes::View()); + + CHECK(buf.Size() == 0); + CHECK(buf.Cap() == 3); + } + + SECTION("when Write is called with source bigger than the free cap, " + "it expands the buffer and writes all the source bytes") { + + auto buf = bytes::Buffer{3}; + buf.Write(bytes::View("foo bar")); + + CHECK(buf.Size() == 7); + CHECK(buf.View().String() == "foo bar"); + } + + SECTION("when Write is called with source smaller than the free cap, " + "it copies the full source of bytes") { + + auto buf = bytes::Buffer{7}; + buf.Write(bytes::View("foo bar")); + + CHECK(buf.Size() == 7); + CHECK(buf.View().String() == "foo bar"); + } +} + +TEST_CASE("bytes::Buffer::View", "[vereign/bytes]") { + SECTION("when the buffer is empty, it returns empty view") { + auto buf = bytes::Buffer{}; + + auto v = buf.View(); + CHECK(v.Size() == 0); + } + + SECTION("when the buffer is not empty, it returns a view of the buffer") { + auto buf = bytes::Buffer{bytes::View("123")}; + + auto v = buf.View(); + CHECK(v.Size() == 3); + CHECK(v.String() == "123"); + + v = buf.View(1); + CHECK(v.Size() == 2); + CHECK(v.String() == "23"); + + v = buf.View(2); + CHECK(v.Size() == 1); + CHECK(v.String() == "3"); + + v = buf.View(3); + CHECK(v.Size() == 0); + CHECK(v.String() == ""); + + v = buf.View(4); + CHECK(v.Size() == 0); + CHECK(v.String() == ""); + } +} diff --git a/cpp/tests/vereign/bytes/view_test.cc b/cpp/tests/vereign/bytes/view_test.cc new file mode 100644 index 0000000000000000000000000000000000000000..1f29ab454d9d40225c45c89adddf1dce7f06036d --- /dev/null +++ b/cpp/tests/vereign/bytes/view_test.cc @@ -0,0 +1,236 @@ +#include <vereign/bytes/view.hh> + +#include <vereign/crypto/rand.hh> +#include <vereign/bytes/errors.hh> + +#include <catch2/catch.hpp> + +using namespace vereign; + +TEST_CASE("bytes::View::View", "[vereign/bytes]") { + SECTION("empty view") { + auto v = bytes::View{}; + + CHECK(v.Size() == 0); + } + + SECTION("view from a pointer and a size") { + const uint8_t* data = (uint8_t*)"foo"; + auto v = bytes::View(data, 3); + + CHECK(v.Size() == 3); + CHECK(v.String() == "foo"); + } + + SECTION("view from a void pointer and a size") { + const void* data = (void*)"foo"; + auto v = bytes::View(data, 3); + + CHECK(v.Size() == 3); + CHECK(v.String() == "foo"); + } + + SECTION("view from a string") { + std::string data = "foo"; + auto v = bytes::View(data); + + CHECK(v.Size() == 3); + CHECK(v.String() == "foo"); + } + + SECTION("view from a wide string") { + std::wstring data = L"foo"; + auto v = bytes::View(data); + + CHECK(v.Size() == sizeof(wchar_t)*3); + CHECK(v.WideString() == L"foo"); + } +} + +TEST_CASE("bytes::View::Slice", "[vereign/bytes]") { + SECTION("empty view") { + auto v = bytes::View{}; + + CHECK(v.Slice(1).Size() == 0); + } + + SECTION("non-empty view") { + std::string s = "123"; + auto v = bytes::View(s); + + CHECK(v.Slice(0).Size() == 3); + CHECK(v.Slice(0).String() == "123"); + + CHECK(v.Slice(1).Size() == 2); + CHECK(v.Slice(1).String() == "23"); + + CHECK(v.Slice(1, 2).Size() == 1); + CHECK(v.Slice(1, 2).String() == "2"); + + CHECK(v.Slice(1, 3).Size() == 2); + CHECK(v.Slice(1, 3).String() == "23"); + + CHECK(v.Slice(1, 4).Size() == 2); + CHECK(v.Slice(1, 4).String() == "23"); + } + + SECTION("start/end out of bounds") { + std::string s = "123"; + auto v = bytes::View(s); + + CHECK(v.Slice(0).Size() == 3); + CHECK(v.Slice(0).String() == "123"); + + CHECK(v.Slice(3).Size() == 0); + CHECK(v.Slice(3).String() == ""); + + CHECK(v.Slice(4).Size() == 0); + CHECK(v.Slice(4).String() == ""); + + CHECK(v.Slice(1, 4).Size() == 2); + CHECK(v.Slice(1, 4).String() == "23"); + } +} + +TEST_CASE("bytes::View::Data", "[vereign/bytes]") { + SECTION("empty view") { + auto v = bytes::View{}; + + CHECK(v.Data() == nullptr); + } + + SECTION("non-empty view") { + std::string s = "123"; + auto v = bytes::View(s); + + CHECK(v.Data()[0] == '1'); + CHECK(v.Data()[1] == '2'); + } +} + +TEST_CASE("bytes::View::CharData", "[vereign/bytes]") { + SECTION("empty view") { + auto v = bytes::View{}; + + CHECK(v.CharData() == nullptr); + } + + SECTION("non-empty view") { + std::string s = "123"; + auto v = bytes::View(s); + + const char* data = v.CharData(); + + CHECK(data[0] == '1'); + CHECK(data[1] == '2'); + } +} + +TEST_CASE("bytes::View::WideCharData", "[vereign/bytes]") { + SECTION("empty view") { + auto v = bytes::View{}; + + CHECK(v.WideCharData() == nullptr); + } + + SECTION("non-empty view") { + std::wstring s = L"123"; + auto v = bytes::View(s); + + const wchar_t* data = v.WideCharData(); + + CHECK(data[0] == wchar_t('1')); + CHECK(data[1] == wchar_t('2')); + } +} + +TEST_CASE("bytes::View::String", "[vereign/bytes]") { + SECTION("empty view") { + auto v = bytes::View{}; + + CHECK(v.String() == ""); + } + + SECTION("non-empty view") { + std::string s = "123"; + auto v = bytes::View(s); + + CHECK(v.String() == "123"); + } +} + +TEST_CASE("bytes::View::WideString", "[vereign/bytes]") { + SECTION("empty view") { + auto v = bytes::View{}; + + CHECK(v.WideString() == L""); + } + + SECTION("non-empty view") { + std::wstring s = L"123"; + auto v = bytes::View(s); + + CHECK(v.WideString() == L"123"); + } +} + +TEST_CASE("bytes::View::operator==", "[vereign/bytes]") { + auto s1 = std::string{"foo"}; + auto s2 = std::string{"foo"}; + + CHECK(bytes::View(s1) == bytes::View(s2)); + + s2 = "bar"; + CHECK((bytes::View(s1) == bytes::View(s2)) == false); + + s2 = ""; + CHECK((bytes::View(s1) == bytes::View(s2)) == false); + + auto b1 = crypto::Rand(16); + auto b2 = bytes::Buffer{b1.View()}; + + CHECK(bytes::View(b1.begin(), 16) == bytes::View(b2.begin(), 16)); +} + +TEST_CASE("bytes::View::operator!=", "[vereign/bytes]") { + auto s1 = std::string{"foo"}; + auto s2 = std::string{"foo"}; + + CHECK((bytes::View(s1) != bytes::View(s2)) == false); + + s2 = "bar"; + CHECK(bytes::View(s1) != bytes::View(s2)); + + s2 = ""; + CHECK(bytes::View(s1) != bytes::View(s2)); + + auto b1 = crypto::Rand(16); + auto b2 = bytes::Buffer{b1.View()}; + + CHECK((bytes::View(b1.begin(), 16) != bytes::View(b2.begin(), 16)) == false); +} + +TEST_CASE("bytes::View::operator[]", "[vereign/bytes]") { + SECTION("empty view") { + auto v = bytes::View{}; + + CHECK_THROWS_AS( + v[0], + bytes::IndexOutOfBounds + ); + } + + SECTION("non-empty view") { + auto s = std::string{"123"}; + auto v = bytes::View(s); + + CHECK(v[0] == '1'); + CHECK(v[1] == '2'); + CHECK(v[2] == '3'); + + CHECK_THROWS_AS( + v[3], + bytes::IndexOutOfBounds + ); + } +} diff --git a/cpp/tests/vereign/crypto/aes_test.cc b/cpp/tests/vereign/crypto/aes_test.cc index 4989c0675fcca437d527000e0924f3662b311e74..cfbb694c4e2eaddfd922670ad528a6984ebb0850 100644 --- a/cpp/tests/vereign/crypto/aes_test.cc +++ b/cpp/tests/vereign/crypto/aes_test.cc @@ -10,14 +10,13 @@ using namespace vereign; TEST_CASE("aes::GCM256Encrypt", "[vereign/crypto/aes][vereign/crypto]") { const std::string data{"foo bar"}; - bytes::Buffer key{32}; - crypto::Rand(key); + auto key = crypto::Rand(32); bytes::Buffer iv; bytes::Buffer tag; bytes::Buffer encrypted; - crypto::aes::GCM256Encrypt(bytes::View(data), key.View(), iv, encrypted, tag); + crypto::aes::GCM256Encrypt(bytes::View(data), key.View(), iv, tag, encrypted); bytes::Buffer decrypted; crypto::aes::GCM256Decrypt(encrypted.View(), key.View(), iv.View(), tag.View(), decrypted); diff --git a/cpp/tests/vereign/crypto/rsa_test.cc b/cpp/tests/vereign/crypto/rsa_test.cc index b484d6eca07021b0838f95ad6ef56f3ffc815aed..f9f27acb9b3a20cf0621dd886144a6a7f2b7306b 100644 --- a/cpp/tests/vereign/crypto/rsa_test.cc +++ b/cpp/tests/vereign/crypto/rsa_test.cc @@ -41,8 +41,7 @@ TEST_CASE("crypto::rsa PublicKeyEncrypt/PrivateKeyDecrypt", "[vereign/crypto/rsa SECTION("max size input") { auto key = crypto::rsa::GenerateKey(2048); - bytes::Buffer input{214}; - crypto::Rand(input); + auto input = crypto::Rand(214); bytes::Buffer encrypted; crypto::rsa::PublicKeyEncrypt(key.get(), input.View(), encrypted); @@ -56,8 +55,7 @@ TEST_CASE("crypto::rsa PublicKeyEncrypt/PrivateKeyDecrypt", "[vereign/crypto/rsa SECTION("invalid big input") { auto key = crypto::rsa::GenerateKey(2048); - bytes::Buffer input{215}; - crypto::Rand(input); + auto input = crypto::Rand(215); bytes::Buffer encrypted; CHECK_THROWS_WITH( diff --git a/cpp/tests/vereign/encoding/base64_test.cc b/cpp/tests/vereign/encoding/base64_test.cc index 2038a634f9a27e24b85b77aec670b7d432e2be52..0e6316b90d2de75e8f9cefcf9c4772259355982b 100644 --- a/cpp/tests/vereign/encoding/base64_test.cc +++ b/cpp/tests/vereign/encoding/base64_test.cc @@ -40,8 +40,7 @@ TEST_CASE("base64::Encode", "[vereign/encoding/base64][vereign/encoding]") { } SECTION("big input must not contain new lines") { - bytes::Buffer input{1024}; - crypto::Rand(input); + auto input = crypto::Rand(1024); bytes::Buffer encoded; encoding::base64::Encode(input.View(), encoded); diff --git a/cpp/tests/vereign/encoding/hex_test.cc b/cpp/tests/vereign/encoding/hex_test.cc new file mode 100644 index 0000000000000000000000000000000000000000..9b4fefea84b7de42c502de478613888a736578d9 --- /dev/null +++ b/cpp/tests/vereign/encoding/hex_test.cc @@ -0,0 +1,96 @@ +#include <vereign/encoding/hex.hh> + +#include <vereign/crypto/rand.hh> +#include <vereign/bytes/view_dump.hh> + +#include <catch2/catch.hpp> + +using namespace vereign; + +TEST_CASE("encoding::hex::Encode", "[vereign/encoding/hex][vereign/encoding]") { + struct Test { + std::string Input; + std::string Expected; + }; + + using namespace std::string_literals; + + std::vector<Test> tests{ + {"", ""}, + {"f", "66"}, + {"fo", "666f"}, + {"foo", "666f6f"}, + {"foob", "666f6f62"}, + {"fooba", "666f6f6261"}, + {"foobar", "666f6f626172"}, + {"\x00\x42"s "foobar", "0042666f6f626172"} + }; + + for (auto& test : tests) { + bytes::Buffer encoded; + encoding::hex::Encode(bytes::View(test.Input), encoded); + CHECK(test.Expected == encoded.View().String()); + } +} + +TEST_CASE("encoding::hex::Decode", "[vereign/encoding/hex][vereign/encoding]") { + struct Test { + std::string Expected; + std::string Input; + }; + + using namespace std::string_literals; + + std::vector<Test> tests{ + {"", ""}, + {"f", "66"}, + {"fo", "666f"}, + {"foo", "666f6f"}, + {"foob", "666f6f62"}, + {"fooba", "666f6f6261"}, + {"foobar", "666f6f626172"}, + {"\x00\x42"s "foobar", "0042666f6f626172"} + }; + + for (auto& test : tests) { + bytes::Buffer decoded; + encoding::hex::Decode(bytes::View(test.Input), decoded); + CHECK(test.Expected == decoded.View().String()); + } + + auto input = crypto::Rand(16); + + bytes::Buffer encoded; + encoding::hex::Encode(input.View(), encoded); + + bytes::Buffer decoded; + encoding::hex::Decode(encoded.View(), decoded); + + CHECK(input.View() == decoded.View()); +} + +TEST_CASE("encoding::hex::EncodeReverse", "[vereign/encoding/hex][vereign/encoding]") { + struct Test { + std::string Input; + std::string Expected; + }; + + using namespace std::string_literals; + + std::vector<Test> tests{ + {"", ""}, + {"f", "66"}, + {"fo", "6f66"}, + {"foo", "6f6f66"}, + {"foob", "626f6f66"}, + {"fooba", "61626f6f66"}, + {"foobar", "7261626f6f66"}, + {"\x00\x42"s "foobar", "7261626f6f664200"} + }; + + for (auto& test : tests) { + bytes::Buffer encoded; + encoding::hex::EncodeReverse(bytes::View(test.Input), encoded); + CHECK(test.Expected == encoded.View().String()); + } +} diff --git a/cpp/tests/vereign/grpc/identity_api_test.cc b/cpp/tests/vereign/grpc/identity_api_test.cc new file mode 100644 index 0000000000000000000000000000000000000000..516aa71338dbf10660e61d48b0451f55cb647fc6 --- /dev/null +++ b/cpp/tests/vereign/grpc/identity_api_test.cc @@ -0,0 +1,337 @@ +#include <vereign/grpc/server.hh> + +#include <vereign/grpc/error_code.hh> +#include <vereign/core/scope_guard.hh> +#include <vereign/client_library/passport_api.gen.grpc.pb.h> +#include <vereign/client_library/types.gen.pb.h> +#include <vereign/client_library/identity_api.gen.grpc.pb.h> +#include <vereign/service/identity_service.hh> +#include <vereign/fs/util.hh> +#include <vereign/fs/path.hh> +#include <vereign/test/device.hh> +#include <vereign/test/service_context.hh> + +#ifdef _WIN32 +# include <vereign/ncrypt/rsa.hh> +#endif + +#include <util/env.hh> +#include <util/protobuf.hh> +#include <grpcpp/create_channel.h> + +#include <catch2/catch.hpp> + +TEST_CASE("grpc::IdentityAPI::LoginWithPreviouslyAddedDevice", "[vereign/grpc][.integration]") { +#ifdef _WIN32 + SECTION("when the device is not registered yet, it must fail with DeviceNotRegistered error") { + auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST"); + auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https"); + + auto storage_path = vereign::fs::TempDir("test_db_"); + auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path); + + auto provider = vereign::ncrypt::rsa::OpenStorageProvider(); + auto key = vereign::ncrypt::rsa::LoadKey( + provider.Get(), + std::string(vereign::kvstore::VereignKeyName) + ); + if (key) { + vereign::ncrypt::rsa::DeleteKey(key.Get()); + } + + vereign::grpc::Server server{"localhost:", host, port, storage_path}; + auto on_exit = vereign::core::MakeScopeGuard([&server] { + server.Shutdown(); + }); + + auto channel = ::grpc::CreateChannel( + "localhost:" + std::to_string(server.SelectedPort()), + ::grpc::InsecureChannelCredentials() + ); + + auto identity_client = vereign::client_library::IdentityAPI::NewStub(channel); + auto login_req = vereign::client_library::LoginFormPreviousAddedDevice{}; + auto login_resp = vereign::client_library::EmptyResponse{}; + + ::grpc::ClientContext login_ctx; + identity_client->LoginWithPreviouslyAddedDevice(&login_ctx, login_req, &login_resp); + + CHECK(login_resp.error() != ""); + CHECK(login_resp.status() == vereign::grpc::ClientErrorStatus); + REQUIRE(login_resp.code() == vereign::grpc::ErrorCodeAsString( + vereign::grpc::ErrorCode::DeviceNotRegistered + )); + } + + SECTION("when the device key has been changed, it must fail with InvalidIdentity error") { + auto public_key = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY"); + auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST"); + auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https"); + + auto storage_path = vereign::fs::TempDir("test_db_"); + auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path); + vereign::test::PrepareNewDevice(host, port, public_key, "", storage_path); + + auto provider = vereign::ncrypt::rsa::OpenStorageProvider(); + auto key = vereign::ncrypt::rsa::LoadKey( + provider.Get(), + std::string(vereign::kvstore::VereignKeyName) + ); + if (key) { + vereign::ncrypt::rsa::DeleteKey(key.Get()); + } + + vereign::ncrypt::rsa::CreateKey( + provider.Get(), + 2048, + std::string(vereign::kvstore::VereignKeyName), + {} + ); + + vereign::grpc::Server server{"localhost:", host, port, storage_path}; + auto on_exit = vereign::core::MakeScopeGuard([&server] { + server.Shutdown(); + }); + + auto channel = ::grpc::CreateChannel( + "localhost:" + std::to_string(server.SelectedPort()), + ::grpc::InsecureChannelCredentials() + ); + + auto identity_client = vereign::client_library::IdentityAPI::NewStub(channel); + auto login_req = vereign::client_library::LoginFormPreviousAddedDevice{}; + auto login_resp = vereign::client_library::EmptyResponse{}; + + ::grpc::ClientContext login_ctx; + identity_client->LoginWithPreviouslyAddedDevice(&login_ctx, login_req, &login_resp); + + CHECK(login_resp.error() == "invalid identity"); + CHECK(login_resp.status() == vereign::grpc::ClientErrorStatus); + REQUIRE(login_resp.code() == vereign::grpc::ErrorCodeAsString( + vereign::grpc::ErrorCode::InvalidIdentity + )); + } +#else + SECTION("when the device is not registered yet, it must fail with DeviceNotRegistered error") { + auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST"); + auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https"); + + auto storage_path = vereign::fs::TempDir("test_db_"); + auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path); + + vereign::grpc::Server server{"localhost:", host, port, storage_path}; + auto on_exit = vereign::core::MakeScopeGuard([&server] { + server.Shutdown(); + }); + + auto channel = ::grpc::CreateChannel( + "localhost:" + std::to_string(server.SelectedPort()), + ::grpc::InsecureChannelCredentials() + ); + + auto identity_client = vereign::client_library::IdentityAPI::NewStub(channel); + auto login_req = vereign::client_library::LoginFormPreviousAddedDevice{}; + auto login_resp = vereign::client_library::EmptyResponse{}; + login_req.set_pin("foo"); + + ::grpc::ClientContext login_ctx; + identity_client->LoginWithPreviouslyAddedDevice(&login_ctx, login_req, &login_resp); + + CHECK(login_resp.error() != ""); + CHECK(login_resp.status() == vereign::grpc::ClientErrorStatus); + REQUIRE(login_resp.code() == vereign::grpc::ErrorCodeAsString( + vereign::grpc::ErrorCode::DeviceNotRegistered + )); + } + + SECTION("when the pin is wrong, it must fail with InvalidPinCode error") { + auto public_key = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY"); + auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST"); + auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https"); + + const std::string pin = "foo"; + auto storage_path = vereign::fs::TempDir("test_db_"); + auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path); + vereign::test::PrepareNewDevice(host, port, public_key, pin, storage_path); + + vereign::grpc::Server server{"localhost:", host, port, storage_path}; + auto on_exit = vereign::core::MakeScopeGuard([&server] { + server.Shutdown(); + }); + + auto channel = ::grpc::CreateChannel( + "localhost:" + std::to_string(server.SelectedPort()), + ::grpc::InsecureChannelCredentials() + ); + + auto identity_client = vereign::client_library::IdentityAPI::NewStub(channel); + auto login_req = vereign::client_library::LoginFormPreviousAddedDevice{}; + auto login_resp = vereign::client_library::EmptyResponse{}; + login_req.set_pin("invalid_pin"); + + ::grpc::ClientContext login_ctx; + identity_client->LoginWithPreviouslyAddedDevice(&login_ctx, login_req, &login_resp); + + CHECK(login_resp.error() == "invalid pin code"); + CHECK(login_resp.status() == vereign::grpc::ClientErrorStatus); + REQUIRE(login_resp.code() == vereign::grpc::ErrorCodeAsString( + vereign::grpc::ErrorCode::InvalidPinCode + )); + } + + SECTION("when the pin is empty, it must fail with InvalidPinCode error") { + auto public_key = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY"); + auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST"); + auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https"); + + const std::string pin = "foo"; + auto storage_path = vereign::fs::TempDir("test_db_"); + auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path); + vereign::test::PrepareNewDevice(host, port, public_key, pin, storage_path); + + vereign::grpc::Server server{"localhost:", host, port, storage_path}; + auto on_exit = vereign::core::MakeScopeGuard([&server] { + server.Shutdown(); + }); + + auto channel = ::grpc::CreateChannel( + "localhost:" + std::to_string(server.SelectedPort()), + ::grpc::InsecureChannelCredentials() + ); + + auto identity_client = vereign::client_library::IdentityAPI::NewStub(channel); + auto login_req = vereign::client_library::LoginFormPreviousAddedDevice{}; + auto login_resp = vereign::client_library::EmptyResponse{}; + login_req.set_pin(""); + + ::grpc::ClientContext login_ctx; + identity_client->LoginWithPreviouslyAddedDevice(&login_ctx, login_req, &login_resp); + + CHECK(login_resp.error() == "invalid pin code"); + CHECK(login_resp.status() == vereign::grpc::ClientErrorStatus); + REQUIRE(login_resp.code() == vereign::grpc::ErrorCodeAsString( + vereign::grpc::ErrorCode::InvalidPinCode + )); + } + +#endif + + SECTION("when the device is not authorized, it must fail with DeviceNotRegistered error") { + auto public_key = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY"); + auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST"); + auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https"); + + // the old device is used later for new device confirmation and authorization + auto old_storage_path = vereign::fs::TempFilePath("test_db_"); + auto rm_old_storage_path = vereign::fs::RemoveFileGuard{old_storage_path}; + auto old_device_ctx = vereign::test::ServiceContext{host, port, old_storage_path}; + auto old_device = vereign::test::Device{old_device_ctx}; + old_device.Login(public_key); + + auto storage_path = vereign::fs::TempDir("test_db_"); + auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path); + const std::string pin = "foo"; + + std::string qr_code; + std::string action_id; + + { + + vereign::grpc::Server server{"localhost:", host, port, storage_path}; + auto on_exit = vereign::core::MakeScopeGuard([&server] { + server.Shutdown(); + }); + + auto channel = ::grpc::CreateChannel( + "localhost:" + std::to_string(server.SelectedPort()), + ::grpc::InsecureChannelCredentials() + ); + + auto identity_client = vereign::client_library::IdentityAPI::NewStub(channel); + + auto register_req = vereign::client_library::LoginFormNewDevice{}; + auto register_resp = vereign::client_library::LoginFormNewDeviceResponse{}; + register_req.set_pin(pin); + + ::grpc::ClientContext register_ctx; + identity_client->LoginWithNewDevice(®ister_ctx, register_req, ®ister_resp); + + CHECK(register_resp.error() == ""); + CHECK(register_resp.status() == "OK"); + REQUIRE(register_resp.code() == "200"); + + qr_code = register_resp.data().qrcode(); + action_id = register_resp.data().actionid(); + } + + { + vereign::grpc::Server server{"localhost:", host, port, storage_path}; + auto on_exit = vereign::core::MakeScopeGuard([&server] { + server.Shutdown(); + }); + + auto channel = ::grpc::CreateChannel( + "localhost:" + std::to_string(server.SelectedPort()), + ::grpc::InsecureChannelCredentials() + ); + + auto identity_client = vereign::client_library::IdentityAPI::NewStub(channel); + auto login_req = vereign::client_library::LoginFormPreviousAddedDevice{}; + auto login_resp = vereign::client_library::EmptyResponse{}; + login_req.set_pin(pin); + + ::grpc::ClientContext login_ctx; + identity_client->LoginWithPreviouslyAddedDevice(&login_ctx, login_req, &login_resp); + + CHECK(login_resp.error() == "Error retrieving entity "); + CHECK(login_resp.status() == "Error while login"); + REQUIRE(login_resp.code() == "400"); + + old_device.ConfirmNewDevice(qr_code, action_id); + + ::grpc::ClientContext login_ctx_confirmed; + identity_client->LoginWithPreviouslyAddedDevice(&login_ctx_confirmed, login_req, &login_resp); + + CHECK(login_resp.error() == "Device unauthorized"); + CHECK(login_resp.status() == "Error while login"); + REQUIRE(login_resp.code() == "400"); + } + } +} + +TEST_CASE("grpc::IdentityAPI::LoginWithNewDevice", "[vereign/grpc][.integration]") { +#ifdef _WIN32 +#else + SECTION("when the pin is empty, it must fail with InvalidPinCode error") { + auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST"); + auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https"); + + auto storage_path = vereign::fs::TempDir("test_db_"); + auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path); + + vereign::grpc::Server server{"localhost:", host, port, storage_path}; + auto on_exit = vereign::core::MakeScopeGuard([&server] { + server.Shutdown(); + }); + + auto channel = ::grpc::CreateChannel( + "localhost:" + std::to_string(server.SelectedPort()), + ::grpc::InsecureChannelCredentials() + ); + + auto identity_client = vereign::client_library::IdentityAPI::NewStub(channel); + auto login_req = vereign::client_library::LoginFormNewDevice{}; + auto login_resp = vereign::client_library::LoginFormNewDeviceResponse{}; + login_req.set_pin(""); + + ::grpc::ClientContext login_ctx; + identity_client->LoginWithNewDevice(&login_ctx, login_req, &login_resp); + + CHECK(login_resp.error() == "invalid pin code"); + CHECK(login_resp.status() == vereign::grpc::ClientErrorStatus); + REQUIRE(login_resp.code() == vereign::grpc::ErrorCodeAsString( + vereign::grpc::ErrorCode::InvalidPinCode + )); + } +#endif // _WIN32 +} diff --git a/cpp/tests/vereign/grpc/server_test.cc b/cpp/tests/vereign/grpc/server_test.cc index f6b5c38678cc233c2a24b1d9345c9f1afbb67d6e..0f3a18589adfb98ebe6d39bb064c91257439b7f5 100644 --- a/cpp/tests/vereign/grpc/server_test.cc +++ b/cpp/tests/vereign/grpc/server_test.cc @@ -16,14 +16,15 @@ #include <catch2/catch.hpp> -using namespace vereign; - TEST_CASE("grpc::Server", "[vereign/grpc/server][.integration]") { - // NOTE: use the init_integration_storage tool to create a new test storage - auto storage_path = vereign::test::RequireEnv("TEST_VEREIGN_INTEGRATION_STORAGE"); - auto host = test::RequireEnv("TEST_VEREIGN_API_HOST"); - auto port = test::GetEnv("TEST_VEREIGN_API_PORT", "https"); - auto pin = vereign::test::GetEnv("TEST_VEREIGN_PIN", "foo"); + auto public_key = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY"); + auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST"); + auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https"); + + const std::string pin = "foo"; + auto storage_path = vereign::fs::TempDir("test_db_"); + auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path); + vereign::test::PrepareNewDevice(host, port, public_key, pin, storage_path); vereign::grpc::Server server{"localhost:", host, port, storage_path}; auto on_exit = vereign::core::MakeScopeGuard([&server] { diff --git a/cpp/tests/vereign/kvstore/crypto_storage_test.cc b/cpp/tests/vereign/kvstore/crypto_storage_test.cc index 3182030d9fcc211b0eede503ce313c95cc02720c..57e3e2945f9cecb97e84656379190f641256e486 100644 --- a/cpp/tests/vereign/kvstore/crypto_storage_test.cc +++ b/cpp/tests/vereign/kvstore/crypto_storage_test.cc @@ -1,6 +1,8 @@ #include <vereign/kvstore/crypto_storage.hh> +#include <vereign/encoding/errors.hh> #include <vereign/kvstore/sqlite_storage.hh> +#include <vereign/kvstore/errors.hh> #include <vereign/crypto/rand.hh> #include <vereign/bytes/view_dump.hh> #include <vereign/fs/util.hh> @@ -12,7 +14,7 @@ using namespace vereign; -TEST_CASE("CryptoStorage::Reset", "[vereign/kvstore]") { +TEST_CASE("kvstore::CryptoStorage::Reset", "[vereign/kvstore]") { auto storage_path = fs::TempFilePath("test_db_"); fs::RemoveFileGuard rm{storage_path}; @@ -39,12 +41,11 @@ TEST_CASE("CryptoStorage::Reset", "[vereign/kvstore]") { } } -TEST_CASE("CryptoStorage::PutBytes", "[vereign/kvstore]") { +TEST_CASE("kvstore::CryptoStorage::PutBytes", "[vereign/kvstore]") { auto storage_path = fs::TempFilePath("test_db_"); fs::RemoveFileGuard rm{storage_path}; - bytes::Buffer big_value{100000}; - crypto::Rand(big_value); + auto big_value = crypto::Rand(100000); auto kvstorage = kvstore::SqliteStorage(storage_path); kvstore::CryptoStorage storage{kvstorage, true}; @@ -59,3 +60,40 @@ TEST_CASE("CryptoStorage::PutBytes", "[vereign/kvstore]") { auto cmp = std::memcmp(v.View().Data(), big_value.View().Data(), v.Size()); CHECK(cmp == 0); } + +TEST_CASE("kvstore::CryptoStorage::GetBytes", "[vereign/kvstore]") { + auto storage_path = fs::TempFilePath("test_db_"); + fs::RemoveFileGuard rm{storage_path}; + + auto kvstorage = kvstore::SqliteStorage(storage_path); + kvstore::CryptoStorage storage{kvstorage, true}; + + storage.Reset("foo"); + + SECTION("when the value does not exists, it must throw ValueNotFoundError") { + bytes::Buffer v; + + CHECK_THROWS_AS(storage.GetBytes("does_not_exists", v), kvstore::ValueNotFoundError); + + CHECK(v.Size() == 0); + } + + SECTION("when the value cannot be decoded, it must throw encoding::Error") { + kvstorage.PutInt64("foo", 1); + + bytes::Buffer v; + + CHECK_THROWS_AS(storage.GetBytes("foo", v), encoding::Error); + + CHECK(v.Size() == 0); + } + + SECTION("when the value exists, it must retrieve the value") { + storage.PutBytes("foo", bytes::View("bar")); + + bytes::Buffer v; + storage.GetBytes("foo", v); + + CHECK(v.View() == bytes::View("bar")); + } +} diff --git a/cpp/tests/vereign/kvstore/sqlite_storage_test.cc b/cpp/tests/vereign/kvstore/sqlite_storage_test.cc index a815908cbfdb607287bc4ae976466fb4c2cdf8b8..f747ef2e4225b2b4c38b68551ab5cec2fcb538ae 100644 --- a/cpp/tests/vereign/kvstore/sqlite_storage_test.cc +++ b/cpp/tests/vereign/kvstore/sqlite_storage_test.cc @@ -17,6 +17,48 @@ using namespace vereign; +TEST_CASE("kvstore::SqliteStorage::GetBytes", "[vereign/kvstore]") { + auto storage_path = fs::TempFilePath("test_db_"); + fs::RemoveFileGuard rm{storage_path}; + + auto kvstorage = kvstore::SqliteStorage(storage_path); + + SECTION("when the value does not exists, it must throw ValueNotFoundError") { + bytes::Buffer v; + + CHECK_THROWS_AS(kvstorage.GetBytes("does_not_exists", v), kvstore::ValueNotFoundError); + + CHECK(v.Size() == 0); + } + + SECTION("when the value exists, it must retrieve the value") { + kvstorage.PutBytes("foo", bytes::View("bar")); + + bytes::Buffer v; + kvstorage.GetBytes("foo", v); + + CHECK(v.View() == bytes::View("bar")); + } +} + +TEST_CASE("kvstore::SqliteStorage::GetInt64", "[vereign/kvstore]") { + auto storage_path = fs::TempFilePath("test_db_"); + fs::RemoveFileGuard rm{storage_path}; + + auto kvstorage = kvstore::SqliteStorage(storage_path); + + SECTION("when the value does not exists, it must throw ValueNotFoundError") { + CHECK_THROWS_AS(kvstorage.GetInt64("does_not_exists"), kvstore::ValueNotFoundError); + } + + SECTION("when the value exists, it must retrieve the value") { + kvstorage.PutInt64("foo", 42); + + auto v = kvstorage.GetInt64("foo"); + + CHECK(v == 42); + } +} TEST_CASE("kvstore::SqliteStorage::DeleteAll", "[vereign/kvstore]") { auto storage_path = fs::TempFilePath("test_db_"); @@ -32,8 +74,8 @@ TEST_CASE("kvstore::SqliteStorage::DeleteAll", "[vereign/kvstore]") { kvstorage.DeleteAll(); - CHECK(kvstorage.GetInt64("foo") == 0); - CHECK(kvstorage.GetInt64("bar") == 0); + CHECK_THROWS_AS(kvstorage.GetInt64("foo"), kvstore::ValueNotFoundError); + CHECK_THROWS_AS(kvstorage.GetInt64("bar"), kvstore::ValueNotFoundError); } TEST_CASE("kvstore::SqliteStorage::Lock", "[vereign/kvstore]") { diff --git a/cpp/tests/vereign/test/device.hh b/cpp/tests/vereign/test/device.hh index be09ce192dacc6879d81697b5488e24daa40f48c..9ad85bd8a42142f35f7c9a589244c2a8b25fd294 100644 --- a/cpp/tests/vereign/test/device.hh +++ b/cpp/tests/vereign/test/device.hh @@ -2,6 +2,7 @@ #define __TESTS_VEREIGN_TEST_DEVICE_HH #include <memory> +#include <string> namespace vereign::service { class IdentityService;