From 267d6823579b960d2993a380825831b6787a3680 Mon Sep 17 00:00:00 2001 From: Vaughn Nugent Date: Tue, 10 Sep 2024 15:48:04 +0000 Subject: refactor: update libs, quick error fixes, rng (#14) Co-authored-by: Michael J <37635304+buttercat1791@users.noreply.github.com> --- CMakeLists.txt | 4 +- include/cryptography/noscrypt_cipher.hpp | 173 -------------------- include/signer/noscrypt_signer.hpp | 16 -- src/cryptography/noscrypt_cipher.cpp | 109 ++++-------- src/cryptography/noscrypt_cipher.hpp | 193 ++++++++++++++++++++++ src/cryptography/nostr_secure_rng.cpp | 46 ++++++ src/cryptography/nostr_secure_rng.hpp | 51 ++++++ src/internal/noscrypt_logger.hpp | 43 +++++ src/signer/noscrypt_signer.cpp | 273 ++++++++++++------------------- 9 files changed, 474 insertions(+), 434 deletions(-) delete mode 100644 include/cryptography/noscrypt_cipher.hpp create mode 100644 src/cryptography/noscrypt_cipher.hpp create mode 100644 src/cryptography/nostr_secure_rng.cpp create mode 100644 src/cryptography/nostr_secure_rng.hpp create mode 100644 src/internal/noscrypt_logger.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b875512..889f19d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,7 +59,7 @@ include_directories(${uuid_v4_SOURCE_DIR}) FetchContent_Declare( libnoscrypt GIT_REPOSITORY git@github.com:VnUgE/noscrypt.git - GIT_TAG fb3608b9455b3e0956e401e6254da13cebd71558 + GIT_TAG v0.1.5 ) FetchContent_MakeAvailable(libnoscrypt) @@ -86,7 +86,6 @@ set(HEADERS ${INCLUDE_DIR}/nostr.hpp ${CLIENT_INCLUDE_DIR}/web_socket_client.hpp ${CLIENT_INCLUDE_DIR}/websocketpp_client.hpp - ${CRYPTOGRAPHY_INCLUDE_DIR}/noscrypt_cipher.hpp ${DATA_INCLUDE_DIR}/data.hpp ${SERVICE_INCLUDE_DIR}/nostr_service_base.hpp ${SIGNER_INCLUDE_DIR}/signer.hpp @@ -102,6 +101,7 @@ set(SIGNER_SOURCE_DIR ./src/signer) set(SOURCES ${CLIENT_SOURCE_DIR}/websocketpp_client.cpp ${CRYPTOGRAPHY_SOURCE_DIR}/noscrypt_cipher.cpp + ${CRYPTOGRAPHY_SOURCE_DIR}/nostr_secure_rng.cpp ${DATA_SOURCE_DIR}/event.cpp ${DATA_SOURCE_DIR}/filters.cpp ${SERVICE_SOURCE_DIR}/nostr_service_base.cpp diff --git a/include/cryptography/noscrypt_cipher.hpp b/include/cryptography/noscrypt_cipher.hpp deleted file mode 100644 index 3337240..0000000 --- a/include/cryptography/noscrypt_cipher.hpp +++ /dev/null @@ -1,173 +0,0 @@ -#pragma once - -#include - -#include -#include - -namespace nostr -{ -namespace cryptography -{ -class NoscryptCipherContext -{ -private: - NCUtilCipherContext* _cipher; - -public: - - NoscryptCipherContext(uint32_t version, uint32_t mode) - { - /* - * Create a new cipher context with the specified - * version and mode that will live for the duration of the - * instance. - * - * The user is expected to use the noscryptutil mode for - * setting encryption/decryption modes. - * - * The cipher will zero out the memory when it is freed. - * - * For decryption, by default the mac is verified before - * decryption occurs. - * - * NOTE: The ciper is set to reusable mode, so encrypt/decrypt - * can be called multiple times although it's not recommended, - * its just the more predictable way for users to handle it. - */ - - _cipher = NCUtilCipherAlloc( - version, - mode | NC_UTIL_CIPHER_ZERO_ON_FREE | NC_UTIL_CIPHER_REUSEABLE - ); - - //TODO, may fail to allocate memory. - } - - ~NoscryptCipherContext() - { - //Free the cipher context (will also zero any data/pointers) - NCUtilCipherFree(_cipher); - } - - NCResult update( - const std::shared_ptr libContext, - const std::shared_ptr localKey, - const std::shared_ptr remoteKey - ) const - { - return NCUtilCipherUpdate(_cipher, libContext.get(), localKey.get(), remoteKey.get()); - } - - NCResult setIV(std::vector& iv) const - { - return NCUtilCipherSetProperty(_cipher, NC_ENC_SET_IV, iv.data(), (uint32_t)iv.size()); - } - - size_t ivSize() const - { - NCResult size = NCUtilCipherGetIvSize(_cipher); - - if (size <= 0) - { - //TODO Implement error handling - return 0; - } - - return size; - } - - NCResult outputSize() const - { - return NCUtilCipherGetOutputSize(_cipher); - } - - uint32_t flags() const - { - NCResult result = NCUtilCipherGetFlags(_cipher); - - if (result <= 0) - { - //TODO Implement error handling - return 0; - } - - return (uint32_t)result; - } - - NCResult readOutput(std::vector& output) const - { - return NCUtilCipherReadOutput(_cipher, output.data(), (uint32_t)output.size()); - } - - NCResult setInput(const std::vector& input) const - { - /* - * Assign and validate input string. Init can be only called multiple times - * without side effects when the reusable flag is set. (currently set) - */ - - return NCUtilCipherInit(_cipher, input.data(), input.size()); - } -}; - -class NoscryptCipher -{ - -private: - const NoscryptCipherContext _cipher; - /* - * Stores the initialziation vector (aka nonce for nip44) for the cipher. - * Noscrypt needs a memory buffer to store the iv, as it only holds pointers. - * - * This buffer must always point to valid memory after the cipher is created. - */ - std::vector _ivBuffer; - -public: - NoscryptCipher(uint32_t version, uint32_t mode); - - /* - * @brief Performs the cipher operation on the input data. Depending on the mode - * the cipher was initialized as, this will either encrypt or decrypt the data. - * @param libContext The noscrypt library context. - * @param localKey The local secret key used to encrypt/decrypt the data. - * @param remoteKey The remote public key used to encrypt/decrypt the data. - * @param input The data to encrypt/decrypt. - * @returns The opposite of the input data. - * @remark This cipher function follows the nostr nips format and will use do it's - * best to - */ - std::string update( - const std::shared_ptr libContext, - const std::shared_ptr localKey, - const std::shared_ptr remoteKey, - const std::string& input - ); - - /** - * @brief Computes the length of a base64 encoded string. - * @param n The length of the string to be encoded. - * @return The length of the resulting base64 encoded string. - */ - inline static size_t base64EncodedSize(const size_t n) - { - return (((n + 2) / 3) << 2) + 1; - }; - - /** - * @brief Computes the length of a string decoded from base64. - * @param n The length of the base64 encoded string. - * @return The length of the decoded string. - */ - inline static size_t base64DecodedSize(const size_t n) - { - return (n * 3) >> 2; - }; - - static std::string naiveEncodeBase64(const std::string& str); - - static std::string naiveDecodeBase64(const std::string& str); -}; -} // namespace cryptography -} // namespace nostr diff --git a/include/signer/noscrypt_signer.hpp b/include/signer/noscrypt_signer.hpp index 85188fa..0a7d0fd 100644 --- a/include/signer/noscrypt_signer.hpp +++ b/include/signer/noscrypt_signer.hpp @@ -145,12 +145,6 @@ private: #pragma region Cryptography - /** - * @brief Reseeds OpenSSL's pseudo-random number generator, using `/dev/random` as the seed, if - * possible. - */ - void _reseedRandomNumberGenerator(uint32_t bufferSize = 32); - /** * @brief Encrypts a string according to the standard specified in NIP-04. * @param input The string to be encrypted. @@ -182,16 +176,6 @@ private: std::string _decryptNip44(const std::string input); #pragma endregion - - #pragma region Logging - - inline void _logNoscryptInitResult(NCResult initResult) const; - - inline void _logNoscryptSecretValidationResult(NCResult secretValidationResult) const; - - inline void _logNoscryptPubkeyGenerationResult(NCResult pubkeyGenerationResult) const; - - #pragma endregion }; } // namespace signer } // namespace nostr diff --git a/src/cryptography/noscrypt_cipher.cpp b/src/cryptography/noscrypt_cipher.cpp index ae9aaf0..2d5d671 100644 --- a/src/cryptography/noscrypt_cipher.cpp +++ b/src/cryptography/noscrypt_cipher.cpp @@ -2,49 +2,16 @@ #include #include -#include -#include "cryptography/noscrypt_cipher.hpp" +#include "nostr_secure_rng.hpp" +#include "noscrypt_cipher.hpp" +#include "../internal/noscrypt_logger.hpp" -using namespace nostr::cryptography; using namespace std; +using namespace nostr::cryptography; -static void _printNoscryptError(NCResult result, const std::string funcName, int lineNum) -{ - uint8_t argPosition; - - switch (NCParseErrorCode(result, &argPosition)) - { - case E_NULL_PTR: - PLOG_ERROR << "noscrypt - error: A null pointer was passed in " << funcName << "(" << argPosition << ") at line " << lineNum; - break; - - case E_INVALID_ARG: - PLOG_ERROR << "noscrypt - error: An invalid argument was passed in " << funcName << "(" << argPosition << ") at line " << lineNum; - break; - - case E_INVALID_CONTEXT: - PLOG_ERROR << "noscrypt - error: An invalid context was passed in " << funcName << "(" << argPosition << ") on line " << lineNum; - break; - - case E_ARGUMENT_OUT_OF_RANGE: - PLOG_ERROR << "noscrypt - error: An argument was out of range in " << funcName << "(" << argPosition << ") at line " << lineNum; - break; - - case E_OPERATION_FAILED: - PLOG_ERROR << "noscrypt - error: An operation failed in " << funcName << "(" << argPosition << ") at line " << lineNum; - break; - - default: - PLOG_ERROR << "noscrypt - error: An unknown error " << result << " occurred in " << funcName << "(" << argPosition << ") at line " << lineNum; - break; - } - -} - -#define LOG_NC_ERROR(result) _printNoscryptError(result, __func__, __LINE__) -NoscryptCipher::NoscryptCipher(uint32_t version, uint32_t mode) : +NoscryptCipher::NoscryptCipher(NoscryptCipherVersion version, NoscryptCipherMode mode) : _cipher(version, mode) { /* @@ -54,7 +21,7 @@ NoscryptCipher::NoscryptCipher(uint32_t version, uint32_t mode) : * operation. */ - if ((mode & NC_UTIL_CIPHER_MODE) == NC_UTIL_CIPHER_MODE_ENCRYPT) + if (mode == NoscryptCipherMode::CIPHER_MODE_ENCRYPT) { //Resize the vector to the size of the current cipher this->_ivBuffer.resize(this->_cipher.ivSize()); @@ -82,10 +49,10 @@ std::string NoscryptCipher::update( //Safely convert the string to a vector of bytes (allocates and copies, so maybe speed up later) const vector inputBuffer(input.begin(), input.end()); -result = this->_cipher.setInput(inputBuffer); + result = this->_cipher.setInput(inputBuffer); if (result != NC_SUCCESS) { - LOG_NC_ERROR(result); + NC_LOG_ERROR(result); return string(); } @@ -97,25 +64,17 @@ result = this->_cipher.setInput(inputBuffer); * Keep in mind, this will automatically work for nip44 and nip04, either the * AES iv or the ChaCha nonce. */ - if ((this->_cipher.flags() & NC_UTIL_CIPHER_MODE) == NC_UTIL_CIPHER_MODE_ENCRYPT) + if (this->_cipher.mode() == NoscryptCipherMode::CIPHER_MODE_ENCRYPT) { - int code = RAND_bytes( - this->_ivBuffer.data(), - this->_ivBuffer.size() //Size set in constructor - ); - - if (code <= 0) - { - PLOG_ERROR << "Failed to generate a nonce or IV for encryption."; - return string(); - } + //A secure random initialization vector is needed for encryption operations + NostrSecureRng::fill(this->_ivBuffer); } //Performs the operation (either encryption or decryption) result = this->_cipher.update(libContext, localKey, remoteKey); if (result != NC_SUCCESS) { - LOG_NC_ERROR(result); + NC_LOG_ERROR(result); return string(); } @@ -127,7 +86,7 @@ result = this->_cipher.setInput(inputBuffer); NCResult outputSize = this->_cipher.outputSize(); if (outputSize <= 0) { - LOG_NC_ERROR(outputSize); + NC_LOG_ERROR(outputSize); return string(); } @@ -137,7 +96,7 @@ result = this->_cipher.setInput(inputBuffer); result = this->_cipher.readOutput(output); if (result != outputSize) { - LOG_NC_ERROR(result); + NC_LOG_ERROR(result); return string(); } @@ -148,36 +107,40 @@ string NoscryptCipher::naiveEncodeBase64(const std::string& str) { // Compute base64 size and allocate a string buffer of that size. const size_t encodedSize = NoscryptCipher::base64EncodedSize(str.size()); - unsigned char* encodedData = new unsigned char[encodedSize]; + + auto encodedData = make_unique(encodedSize); // Encode the input string to base64. - EVP_EncodeBlock(encodedData, (const unsigned char*)str.data(), str.size()); + EVP_EncodeBlock( + encodedData.get(), + reinterpret_cast(str.c_str()), + str.size() + ); // Construct the encoded string from the buffer. - string encodedStr((char*)encodedData); - - // Zero out the buffer and delete the pointer. - memset(encodedData, 0, encodedSize); - delete [] encodedData; - - return encodedStr; + return string( + reinterpret_cast(encodedData.get()), + encodedSize + ); } string NoscryptCipher::naiveDecodeBase64(const string& str) { // Compute the size of the decoded string and allocate a buffer of that size. const size_t decodedSize = NoscryptCipher::base64DecodedSize(str.size()); - unsigned char* decodedData = new unsigned char[decodedSize]; + + auto decodedData = make_unique(decodedSize); // Decode the input string from base64. - EVP_DecodeBlock(decodedData, (const unsigned char*)str.data(), str.size()); + EVP_DecodeBlock( + decodedData.get(), + reinterpret_cast(str.c_str()), + str.size() + ); // Construct the decoded string from the buffer. - string decodedStr((char*)decodedData); - - // Zero out the buffer and delete the pointer. - memset(decodedData, 0, decodedSize); - delete [] decodedData; - - return decodedStr; + return string( + reinterpret_cast(decodedData.get()), + decodedSize + ); }; diff --git a/src/cryptography/noscrypt_cipher.hpp b/src/cryptography/noscrypt_cipher.hpp new file mode 100644 index 0000000..04bdbf2 --- /dev/null +++ b/src/cryptography/noscrypt_cipher.hpp @@ -0,0 +1,193 @@ +#pragma once + +#include + +#include +#include +#include "../internal/noscrypt_logger.hpp" + +namespace nostr +{ +namespace cryptography +{ + +enum NoscryptCipherMode : uint32_t +{ + CIPHER_MODE_ENCRYPT = NC_UTIL_CIPHER_MODE_ENCRYPT, + CIPHER_MODE_DECRYPT = NC_UTIL_CIPHER_MODE_DECRYPT, +}; + +enum NoscryptCipherVersion : uint32_t +{ + NIP04 = NC_ENC_VERSION_NIP04, + NIP44 = NC_ENC_VERSION_NIP44, +}; + +class NoscryptCipherContext +{ +private: + NCUtilCipherContext* _cipher; + +public: + + NoscryptCipherContext(NoscryptCipherVersion version, NoscryptCipherMode mode) + { + /* + * Create a new cipher context with the specified + * version and mode that will live for the duration of the + * instance. + * + * The user is expected to use the noscryptutil mode for + * setting encryption/decryption modes. + * + * The cipher will zero out the memory when it is freed. + * + * For decryption, by default the mac is verified before + * decryption occurs. + * + * NOTE: The ciper is set to reusable mode, so encrypt/decrypt + * can be called multiple times although it's not recommended, + * its just the more predictable way for users to handle it. + */ + + _cipher = NCUtilCipherAlloc( + (uint32_t)version, + ((uint32_t)mode) | NC_UTIL_CIPHER_ZERO_ON_FREE | NC_UTIL_CIPHER_REUSEABLE + ); + + //TODO, may fail to allocate memory. + } + + ~NoscryptCipherContext() + { + //Free the cipher context (will also zero any data/pointers) + NCUtilCipherFree(_cipher); + } + + NCResult update( + const std::shared_ptr libContext, + const std::shared_ptr localKey, + const std::shared_ptr remoteKey + ) const + { + return NCUtilCipherUpdate(_cipher, libContext.get(), localKey.get(), remoteKey.get()); + } + + NCResult setIV(std::vector& iv) const + { + return NCUtilCipherSetProperty(_cipher, NC_ENC_SET_IV, iv.data(), (uint32_t)iv.size()); + } + + size_t ivSize() const + { + NCResult size = NCUtilCipherGetIvSize(_cipher); + + if (size <= 0) + { + //TODO Implement error handling + return 0; + } + + return size; + } + + NCResult outputSize() const + { + return NCUtilCipherGetOutputSize(_cipher); + } + + uint32_t flags() const + { + NCResult result = NCUtilCipherGetFlags(_cipher); + + if (result <= 0) + { + //TODO Implement error handling + return 0; + } + + return (uint32_t)result; + } + + NoscryptCipherMode mode() const + { + //Mode bit is lsb so just mask off the rest of the flags and convert back to enum + return (NoscryptCipherMode)(flags() & NC_UTIL_CIPHER_MODE); + } + + NCResult readOutput(std::vector& output) const + { + return NCUtilCipherReadOutput(_cipher, output.data(), (uint32_t)output.size()); + } + + NCResult setInput(const std::vector& input) const + { + /* + * Assign and validate input string. Init can be only called multiple times + * without side effects when the reusable flag is set. (currently set) + */ + + return NCUtilCipherInit(_cipher, input.data(), input.size()); + } +}; + +class NoscryptCipher +{ + +private: + const NoscryptCipherContext _cipher; + /* + * Stores the initialziation vector (aka nonce for nip44) for the cipher. + * Noscrypt needs a memory buffer to store the iv, as it only holds pointers. + * + * This buffer must always point to valid memory after the cipher is created. + */ + std::vector _ivBuffer; + +public: + NoscryptCipher(NoscryptCipherVersion version, NoscryptCipherMode mode); + + /* + * @brief Performs the cipher operation on the input data. Depending on the mode + * the cipher was initialized as, this will either encrypt or decrypt the data. + * @param libContext The noscrypt library context. + * @param localKey The local secret key used to encrypt/decrypt the data. + * @param remoteKey The remote public key used to encrypt/decrypt the data. + * @param input The data to encrypt/decrypt. + * @returns The opposite of the input data. + * @remark This cipher function follows the nostr nips format and will use do it's + * best to + */ + std::string update( + const std::shared_ptr libContext, + const std::shared_ptr localKey, + const std::shared_ptr remoteKey, + const std::string& input + ); + + /** + * @brief Computes the length of a base64 encoded string. + * @param n The length of the string to be encoded. + * @return The length of the resulting base64 encoded string. + */ + inline static size_t base64EncodedSize(const size_t n) + { + return (((n + 2) / 3) << 2) + 1; + }; + + /** + * @brief Computes the length of a string decoded from base64. + * @param n The length of the base64 encoded string. + * @return The length of the decoded string. + */ + inline static size_t base64DecodedSize(const size_t n) + { + return (n * 3) >> 2; + }; + + static std::string naiveEncodeBase64(const std::string& str); + + static std::string naiveDecodeBase64(const std::string& str); +}; +} // namespace cryptography +} // namespace nostr diff --git a/src/cryptography/nostr_secure_rng.cpp b/src/cryptography/nostr_secure_rng.cpp new file mode 100644 index 0000000..9d92514 --- /dev/null +++ b/src/cryptography/nostr_secure_rng.cpp @@ -0,0 +1,46 @@ +#include +#include + +#include +#include +#include + +#include "nostr_secure_rng.hpp" + +using namespace std; +using namespace nostr::cryptography; + +void NostrSecureRng::fill(void* buffer, size_t length) +{ + if (RAND_bytes((uint8_t*)buffer, length) != 1) + { + //TODO throw runtime exception + PLOG_ERROR << "Failed to generate random bytes"; + } +} + +inline void NostrSecureRng::fill(vector& buffer) +{ + fill(buffer.data(), buffer.size()); +} + +void NostrSecureRng::reseed(uint32_t bufferSize) +{ + int rc = RAND_load_file("/dev/random", bufferSize); + + if (rc != bufferSize) + { + PLOG_WARNING << "Failed to reseed the RNG with /dev/random, falling back to /dev/urandom."; + RAND_poll(); + } +} + +void NostrSecureRng::zero(void* buffer, size_t length) +{ + OPENSSL_cleanse(buffer, length); +} + +inline void NostrSecureRng::zero(vector& buffer) +{ + zero(buffer.data(), buffer.size()); +} \ No newline at end of file diff --git a/src/cryptography/nostr_secure_rng.hpp b/src/cryptography/nostr_secure_rng.hpp new file mode 100644 index 0000000..7fa340e --- /dev/null +++ b/src/cryptography/nostr_secure_rng.hpp @@ -0,0 +1,51 @@ +#pragma once + +#include + +namespace nostr +{ +namespace cryptography +{ + +class NostrSecureRng +{ +private: + +public: + + /** + * @brief Fills the given buffer with secure random bytes. + * @param buffer The buffer to fill with random bytes. + * @param length The number of bytes to fill. + */ + static void fill(void* buffer, size_t length); + + /* + * @brief Fills the given vector with secure random bytes. + * @param buffer The vector to fill with random bytes. + */ + static inline void fill(std::vector& buffer); + + /* + * @brief Reseeds the RNG with random bytes from /dev/random. + * @param bufferSize The number of bytes to read from /dev/random. + * @remark Falls back to /dev/urandom if /dev/random is not available. + */ + static void reseed(uint32_t bufferSize = 32); + + /* + * @brief Securley zeroes out the given buffer. + * @param buffer A pointer to the buffer to zero out. + * @param length The number of bytes to zero out. + */ + static void zero(void* buffer, size_t length); + + /* + * @brief Securley zeroes out the given vector. + * @param buffer The vector to zero out. + */ + static inline void zero(std::vector& buffer); +}; + +} // namespace cryptography +} // namespace nostr diff --git a/src/internal/noscrypt_logger.hpp b/src/internal/noscrypt_logger.hpp new file mode 100644 index 0000000..b767b81 --- /dev/null +++ b/src/internal/noscrypt_logger.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +/* +* @brief Logs an error message with the function name and line number where the +* error occurred. This is useful for debugging and logging errors in the Noscrypt +* library. +*/ +#define NC_LOG_ERROR(result) _printNoscryptError(result, __func__, __LINE__) + +static inline void _printNoscryptError(NCResult result, const std::string funcName, int lineNum) +{ + uint8_t argPosition; + + switch (NCParseErrorCode(result, &argPosition)) + { + case E_NULL_PTR: + PLOG_ERROR << "noscrypt - error: A null pointer was passed in " << funcName << "(" << argPosition << ") at line " << lineNum; + break; + + case E_INVALID_ARG: + PLOG_ERROR << "noscrypt - error: An invalid argument was passed in " << funcName << "(" << argPosition << ") at line " << lineNum; + break; + + case E_INVALID_CONTEXT: + PLOG_ERROR << "noscrypt - error: An invalid context was passed in " << funcName << "(" << argPosition << ") on line " << lineNum; + break; + + case E_ARGUMENT_OUT_OF_RANGE: + PLOG_ERROR << "noscrypt - error: An argument was out of range in " << funcName << "(" << argPosition << ") at line " << lineNum; + break; + + case E_OPERATION_FAILED: + PLOG_ERROR << "noscrypt - error: An operation failed in " << funcName << "(" << argPosition << ") at line " << lineNum; + break; + + default: + PLOG_ERROR << "noscrypt - error: An unknown error " << result << " occurred in " << funcName << "(" << argPosition << ") at line " << lineNum; + break; + } +} diff --git a/src/signer/noscrypt_signer.cpp b/src/signer/noscrypt_signer.cpp index 2b87703..d12bd44 100644 --- a/src/signer/noscrypt_signer.cpp +++ b/src/signer/noscrypt_signer.cpp @@ -2,23 +2,87 @@ #include #include #include -#include #include #include #include -#include -#include #include #include "signer/noscrypt_signer.hpp" -#include "noscrypt_cipher.hpp" +#include "../cryptography/nostr_secure_rng.hpp" +#include "../cryptography/noscrypt_cipher.hpp" +#include "../internal/noscrypt_logger.hpp" +using namespace std; using namespace nostr::data; using namespace nostr::service; using namespace nostr::signer; using namespace nostr::cryptography; -using namespace std; + +#pragma region Local Statics + +static shared_ptr ncAllocContext() +{ + /* + * Use the utilties library to allocate a new Noscrypt context. + * Shared pointer will call free when smart pointer is destroyed + */ + + return shared_ptr( + NCUtilContextAlloc(), + &NCUtilContextFree + ); +} + +static shared_ptr initNoscryptContext() +{ + //Use helper to allocate a dynamic sized shared pointer + auto ctx = ncAllocContext(); + + auto randomEntropy = make_unique(NC_CONTEXT_ENTROPY_SIZE); + NostrSecureRng::fill(randomEntropy.get(), NC_CONTEXT_ENTROPY_SIZE); + + NCResult initResult = NCInitContext(ctx.get(), randomEntropy.get()); + + NC_LOG_ERROR(initResult); + + return ctx; +}; + +/** + * @brief Generates a private/public key pair for local use. + * @returns The generated keypair of the form `[privateKey, publicKey]`, or a pair of empty + * strings if the function failed. + * @remarks This keypair is intended for temporary use, and should not be saved or used outside + * of this class. + */ +static void createLocalKeypair( + const shared_ptr ctx, + shared_ptr secret, + shared_ptr pubkey +) +{ + // Loop attempts to generate a secret key until a valid key is produced. + // Limit the number of attempts to prevent resource exhaustion in the event of a failure. + NCResult secretValidationResult; + int loopCount = 0; + do + { + NostrSecureRng::fill(secret.get(), sizeof(NCSecretKey)); + + secretValidationResult = NCValidateSecretKey(ctx.get(), secret.get()); + + } while (secretValidationResult != NC_SUCCESS && ++loopCount < 64); + + NC_LOG_ERROR(secretValidationResult); + + // Use noscrypt to derive the public key from its private counterpart. + NCResult pubkeyGenerationResult = NCGetPublicKey(ctx.get(), secret.get(), pubkey.get()); + + NC_LOG_ERROR(pubkeyGenerationResult); +}; + +#pragma endregion #pragma region Constructors and Destructors @@ -28,9 +92,13 @@ NoscryptSigner::NoscryptSigner( { plog::init(plog::debug, appender.get()); - this->_reseedRandomNumberGenerator(); - this->_initNoscryptContext(); - this->_createLocalKeypair(); + this->_noscryptContext = initNoscryptContext(); + + createLocalKeypair( + this->_noscryptContext, + this->_localPrivateKey, + this->_localPublicKey + ); this->_nostrService = nostrService; }; @@ -258,67 +326,6 @@ inline void NoscryptSigner::_setRemotePublicKey(const string value) #pragma region Setup -void NoscryptSigner::_initNoscryptContext() -{ - shared_ptr context; - auto contextStructSize = NCGetContextStructSize(); - auto randomEntropy = make_unique(contextStructSize); - - random_device rd; - mt19937 gen(rd()); - uniform_int_distribution<> dist(0, contextStructSize); - generate_n(randomEntropy.get(), contextStructSize, [&]() { return dist(gen); }); - - NCResult initResult = NCInitContext(context.get(), randomEntropy.get()); - this->_logNoscryptInitResult(initResult); - - this->_noscryptContext = move(context); -}; - -/** - * @brief Generates a private/public key pair for local use. - * @returns The generated keypair of the form `[privateKey, publicKey]`, or a pair of empty - * strings if the function failed. - * @remarks This keypair is intended for temporary use, and should not be saved or used outside - * of this class. - */ -void NoscryptSigner::_createLocalKeypair() -{ - string privateKey; - string publicKey; - - // To generate a private key, all we need is a random 32-bit buffer. - auto secret = make_unique(); - - // Loop attempts to generate a secret key until a valid key is produced. - // Limit the number of attempts to prevent resource exhaustion in the event of a failure. - NCResult secretValidationResult; - int loopCount = 0; - do - { - int rc = RAND_bytes(secret->key, sizeof(NCSecretKey)); - if (rc != 1) - { - unsigned long err = ERR_get_error(); - PLOG_ERROR << "OpenSSL error " << err << " occurred while generating a secret key."; - } - - secretValidationResult = NCValidateSecretKey(this->_noscryptContext.get(), secret.get()); - } while (secretValidationResult != NC_SUCCESS && ++loopCount < 64); - - this->_logNoscryptSecretValidationResult(secretValidationResult); - this->_localPrivateKey = move(secret); - - // Use noscrypt to derive the public key from its private counterpart. - auto pubkey = make_unique(); - NCResult pubkeyGenerationResult = NCGetPublicKey( - this->_noscryptContext.get(), - secret.get(), - pubkey.get()); - this->_logNoscryptPubkeyGenerationResult(pubkeyGenerationResult); - this->_localPublicKey = move(pubkey); -}; - int NoscryptSigner::_parseRemotePublicKey(string connectionToken) { int queryStart = connectionToken.find('?'); @@ -403,35 +410,39 @@ shared_ptr NoscryptSigner::_wrapSignerMessage(nlohmann::json jrpc) wrapperEvent->tags.push_back({ "p", this->_getRemotePublicKey() }); wrapperEvent->content = encryptedContent; - // Generate a random seed for the signer. - auto random32 = make_shared(32); - int code = RAND_bytes(random32.get(), 32); - if (code <= 0) - { - PLOG_ERROR << "Failed to generate a random 32-byte seed buffer for the signer."; - return nullptr; - } + uint8_t schnorrSig[64]; + uint8_t random32[32]; + + //Secure random signing entropy is required + NostrSecureRng::fill(random32, sizeof(random32)); // Sign the wrapper message with the local secret key. string serializedEvent = wrapperEvent->serialize(); - uint32_t dataSize = serializedEvent.length(); - auto signature = make_unique(64); + NCResult signatureResult = NCSignData( this->_noscryptContext.get(), this->_localPrivateKey.get(), - random32.get(), - reinterpret_cast(serializedEvent.data()), - dataSize, - signature.get()); + random32, + reinterpret_cast(serializedEvent.c_str()), + serializedEvent.length(), + schnorrSig + ); + + //Random buffer could leak sensitive signing information + NostrSecureRng::zero(random32, sizeof(random32)); // TODO: Handle result codes. if (signatureResult != NC_SUCCESS) { + NC_LOG_ERROR(signatureResult); return nullptr; } // Add the signature to the event. - wrapperEvent->sig = string((char*)signature.get(), 64); + wrapperEvent->sig = string( + reinterpret_cast(schnorrSig), + sizeof(schnorrSig) + ); return wrapperEvent; }; @@ -512,16 +523,6 @@ promise NoscryptSigner::_pingSigner() #pragma region Cryptography -void NoscryptSigner::_reseedRandomNumberGenerator(uint32_t bufferSize) -{ - int rc = RAND_load_file("/dev/random", bufferSize); - if (rc != bufferSize) - { - PLOG_WARNING << "Failed to reseed the RNG with /dev/random, falling back to /dev/urandom."; - RAND_poll(); - } -}; - string NoscryptSigner::_encryptNip04(std::string input) { throw runtime_error("NIP-04 encryption is not yet implemented."); @@ -534,7 +535,10 @@ string NoscryptSigner::_decryptNip04(string input) string NoscryptSigner::_encryptNip44(string input) { - NoscryptCipher cipher = NoscryptCipher(NC_ENC_VERSION_NIP44, NC_UTIL_CIPHER_MODE_ENCRYPT); + NoscryptCipher cipher = NoscryptCipher( + NoscryptCipherVersion::NIP44, + NoscryptCipherMode::CIPHER_MODE_ENCRYPT + ); auto output = cipher.update( this->_noscryptContext, @@ -552,7 +556,10 @@ string NoscryptSigner::_decryptNip44(string input) { //TODO handle input validation as per nip44 spec - NoscryptCipher cipher = NoscryptCipher(NC_ENC_VERSION_NIP44, NC_UTIL_CIPHER_MODE_DECRYPT); + NoscryptCipher cipher = NoscryptCipher( + NoscryptCipherVersion::NIP44, + NoscryptCipherMode::CIPHER_MODE_DECRYPT + ); return cipher.update( this->_noscryptContext, @@ -564,77 +571,3 @@ string NoscryptSigner::_decryptNip44(string input) #pragma endregion -#pragma region Logging - -inline void NoscryptSigner::_logNoscryptInitResult(NCResult initResult) const -{ - switch (NCParseErrorCode(initResult, NULL)) { - case NC_SUCCESS: - PLOG_INFO << "noscrypt - success"; - break; - - case E_NULL_PTR: - PLOG_ERROR << "noscrypt - error: A null pointer was passed to the initializer."; - break; - - case E_INVALID_ARG: - PLOG_ERROR << "noscrypt - error: An invalid argument was passed to the initializer."; - break; - - case E_INVALID_CONTEXT: - PLOG_ERROR << "noscrypt - error: The NCContext struct is in an invalid state."; - break; - - case E_ARGUMENT_OUT_OF_RANGE: - PLOG_ERROR << "noscrypt - error: An initializer argument was outside the range of acceptable values."; - break; - - case E_OPERATION_FAILED: - PLOG_ERROR << "noscrypt - error"; - break; - } -}; - -inline void NoscryptSigner::_logNoscryptSecretValidationResult(NCResult secretValidationResult) const -{ - if (NCParseErrorCode(secretValidationResult, NULL) == NC_SUCCESS) - { - PLOG_INFO << "noscrypt_signer - success: Generated a valid secret key."; - } - else - { - PLOG_ERROR << "noscrypt_signer - error: Failed to generate a valid secret key."; - } -}; - -inline void NoscryptSigner::_logNoscryptPubkeyGenerationResult(NCResult pubkeyGenerationResult) const -{ - - switch (NCParseErrorCode(pubkeyGenerationResult, NULL)) { - case NC_SUCCESS: - PLOG_INFO << "noscrypt - success: Generated a valid public key."; - break; - - case E_NULL_PTR: - PLOG_ERROR << "noscrypt - error: A null pointer was passed to the public key generation function."; - break; - - case E_INVALID_ARG: - PLOG_ERROR << "noscrypt - error: An invalid argument was passed to the public key generation function."; - break; - - case E_INVALID_CONTEXT: - PLOG_ERROR << "noscrypt - error: The NCContext struct is in an invalid state."; - break; - - case E_ARGUMENT_OUT_OF_RANGE: - PLOG_ERROR << "noscrypt - error: An argument was outside the range of acceptable values."; - break; - - case E_OPERATION_FAILED: - PLOG_ERROR << "noscrypt - error: Failed to generate the public key from the secret key."; - break; - } -}; - -#pragma endregion -- cgit