From bc80ca660f96424ba8b2bd9ea60ad52239d7552a Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Sun, 11 Aug 2024 19:16:06 -0500 Subject: Handle base64 encoding and decoding --- CMakeLists.txt | 6 +- include/cryptography/noscrypt_cipher.hpp | 173 +++++++++++++++++++++++++++++ src/cryptography/noscrypt_cipher.cpp | 183 +++++++++++++++++++++++++++++++ src/signer/noscrypt_cipher.cpp | 145 ------------------------ src/signer/noscrypt_cipher.hpp | 160 --------------------------- src/signer/noscrypt_signer.cpp | 1 + 6 files changed, 362 insertions(+), 306 deletions(-) create mode 100644 include/cryptography/noscrypt_cipher.hpp create mode 100644 src/cryptography/noscrypt_cipher.cpp delete mode 100644 src/signer/noscrypt_cipher.cpp delete mode 100644 src/signer/noscrypt_cipher.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 8140805..b875512 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -72,11 +72,13 @@ set_target_properties(noscrypt PROPERTIES NC_ENABLE_UTILS ON) #utilities libra #======== Build the project ========# set(INCLUDE_DIR ./include) set(CLIENT_INCLUDE_DIR ./include/client) +set(CRYPTOGRAPHY_INCLUDE_DIR ./include/cryptography) set(DATA_INCLUDE_DIR ./include/data) set(SERVICE_INCLUDE_DIR ./include/service) set(SIGNER_INCLUDE_DIR ./include/signer) include_directories(${INCLUDE_DIR}) include_directories(${CLIENT_INCLUDE_DIR}) +include_directories(${CRYPTOGRAPHY_INCLUDE_DIR}) include_directories(${DATA_INCLUDE_DIR}) include_directories(${SIGNER_INCLUDE_DIR}) @@ -84,6 +86,7 @@ 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 @@ -92,16 +95,17 @@ set(HEADERS set(SOURCE_DIR ./src) set(CLIENT_SOURCE_DIR ./src/client) +set(CRYPTOGRAPHY_SOURCE_DIR ./src/cryptography) set(DATA_SOURCE_DIR ./src/data) set(SERVICE_SOURCE_DIR ./src/service) set(SIGNER_SOURCE_DIR ./src/signer) set(SOURCES ${CLIENT_SOURCE_DIR}/websocketpp_client.cpp + ${CRYPTOGRAPHY_SOURCE_DIR}/noscrypt_cipher.cpp ${DATA_SOURCE_DIR}/event.cpp ${DATA_SOURCE_DIR}/filters.cpp ${SERVICE_SOURCE_DIR}/nostr_service_base.cpp ${SIGNER_SOURCE_DIR}/noscrypt_signer.cpp - ${SIGNER_SOURCE_DIR}/noscrypt_cipher.cpp ) add_library(aedile ${SOURCES}) diff --git a/include/cryptography/noscrypt_cipher.hpp b/include/cryptography/noscrypt_cipher.hpp new file mode 100644 index 0000000..3337240 --- /dev/null +++ b/include/cryptography/noscrypt_cipher.hpp @@ -0,0 +1,173 @@ +#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/src/cryptography/noscrypt_cipher.cpp b/src/cryptography/noscrypt_cipher.cpp new file mode 100644 index 0000000..ae9aaf0 --- /dev/null +++ b/src/cryptography/noscrypt_cipher.cpp @@ -0,0 +1,183 @@ +#include +#include + +#include +#include + +#include "cryptography/noscrypt_cipher.hpp" + +using namespace nostr::cryptography; +using namespace std; + +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) : + _cipher(version, mode) +{ + /* + * We can know what iv size we need for the cipher now and allocate + * a buffer just to save some allocations and code during the + * encryption phase. This buffer is only needed during an encryption + * operation. + */ + + if ((mode & NC_UTIL_CIPHER_MODE) == NC_UTIL_CIPHER_MODE_ENCRYPT) + { + //Resize the vector to the size of the current cipher + this->_ivBuffer.resize(this->_cipher.ivSize()); + + //Safe to assign the iv to the context now and it will maintain a pointer to the buffer + this->_cipher.setIV(this->_ivBuffer); + } +} + +std::string NoscryptCipher::update( + const std::shared_ptr libContext, + const std::shared_ptr localKey, + const std::shared_ptr remoteKey, + const std::string& input +) +{ + NCResult result; + + //Argument exception if the input is empty + if (input.empty()) + { + return string(); + } + + //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); + if (result != NC_SUCCESS) + { + LOG_NC_ERROR(result); + return string(); + } + + /* + * If were in encryption mode a random nonce (iv) must be generated. The size was determined + * when the cipher was created and already assigned to the context, so we just need to assign + * the random data. + * + * 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) + { + 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(); + } + } + + //Performs the operation (either encryption or decryption) + result = this->_cipher.update(libContext, localKey, remoteKey); + if (result != NC_SUCCESS) + { + LOG_NC_ERROR(result); + return string(); + } + + /* + * Time to read the ciper output by getting the size of the output, then creating + * a string to store it to + */ + + NCResult outputSize = this->_cipher.outputSize(); + if (outputSize <= 0) + { + LOG_NC_ERROR(outputSize); + return string(); + } + + //Alloc vector for reading input data (maybe only alloc once) + vector output(outputSize); + + result = this->_cipher.readOutput(output); + if (result != outputSize) + { + LOG_NC_ERROR(result); + return string(); + } + + return string(output.begin(), output.end()); +} + +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]; + + // Encode the input string to base64. + EVP_EncodeBlock(encodedData, (const unsigned char*)str.data(), 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; +} + +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]; + + // Decode the input string from base64. + EVP_DecodeBlock(decodedData, (const unsigned char*)str.data(), 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; +}; diff --git a/src/signer/noscrypt_cipher.cpp b/src/signer/noscrypt_cipher.cpp deleted file mode 100644 index d751261..0000000 --- a/src/signer/noscrypt_cipher.cpp +++ /dev/null @@ -1,145 +0,0 @@ -#include -#include -#include - -#include - -#include "noscrypt_cipher.hpp" - -using namespace nostr::signer; -using namespace std; - -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) : - _cipher(version, mode) -{ - /* - * We can know what iv size we need for the cipher now and allocate - * a buffer just to save some allocations and code during the - * encryption phase. This buffer is only needed during an encryption - * operation. - */ - - if ((mode & NC_UTIL_CIPHER_MODE) == NC_UTIL_CIPHER_MODE_ENCRYPT) - { - //Resize the vector to the size of the current cipher - this->_ivBuffer.resize(this->_cipher.ivSize()); - - //Safe to assign the iv to the context now and it will maintain a pointer to the buffer - this->_cipher.setIV(this->_ivBuffer); - } -} - -std::string NoscryptCipher::update( - const std::shared_ptr libContext, - const std::shared_ptr localKey, - const std::shared_ptr remoteKey, - const std::string& input -) -{ - NCResult result; - - //Argument exception if the input is empty - if (input.empty()) - { - return string(); - } - - //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); - if (result != NC_SUCCESS) - { - LOG_NC_ERROR(result); - return string(); - } - - /* - * If were in encryption mode a random nonce (iv) must be generated. The size was determined - * when the cipher was created and already assigned to the context, so we just need to assign - * the random data. - * - * 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) - { - 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(); - } - } - - //Performs the operation (either encryption or decryption) - result = this->_cipher.update(libContext, localKey, remoteKey); - if (result != NC_SUCCESS) - { - LOG_NC_ERROR(result); - return string(); - } - - /* - * Time to read the ciper output by getting the size of the output, then creating - * a string to store it to - */ - - NCResult outputSize = this->_cipher.outputSize(); - if (outputSize <= 0) - { - LOG_NC_ERROR(outputSize); - return string(); - } - - //Alloc vector for reading input data (maybe only alloc once) - vector output(outputSize); - - result = this->_cipher.readOutput(output); - if (result != outputSize) - { - LOG_NC_ERROR(result); - return string(); - } - - return string(output.begin(), output.end()); -} diff --git a/src/signer/noscrypt_cipher.hpp b/src/signer/noscrypt_cipher.hpp deleted file mode 100644 index ffe4bec..0000000 --- a/src/signer/noscrypt_cipher.hpp +++ /dev/null @@ -1,160 +0,0 @@ - -#include - -#include -#include - -namespace nostr -{ -namespace signer -{ -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 - ); - - static std::string naiveEncodeBase64(const std::string& str) - { - //TODO Implement base64 encoding - return str; - } - - static std::string naiveDecodeBase64(const std::string& str) - { - //TODO Implement base64 decoding - return str; - } -}; -} // namespace signer -} // namespace nostr diff --git a/src/signer/noscrypt_signer.cpp b/src/signer/noscrypt_signer.cpp index 3cf4b6f..2b87703 100644 --- a/src/signer/noscrypt_signer.cpp +++ b/src/signer/noscrypt_signer.cpp @@ -17,6 +17,7 @@ using namespace nostr::data; using namespace nostr::service; using namespace nostr::signer; +using namespace nostr::cryptography; using namespace std; #pragma region Constructors and Destructors -- cgit