diff options
author | Vaughn Nugent <public@vaughnnugent.com> | 2024-09-10 15:48:04 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-09-10 10:48:04 -0500 |
commit | 267d6823579b960d2993a380825831b6787a3680 (patch) | |
tree | 6f9336624fc28888828a0e5902c3743638530e12 /src/cryptography | |
parent | f26162c2eaa5e60ce76c67d8c7510b266fed0bba (diff) |
refactor: update libs, quick error fixes, rng (#14)note-signing
Co-authored-by: Michael J <37635304+buttercat1791@users.noreply.github.com>
Diffstat (limited to 'src/cryptography')
-rw-r--r-- | src/cryptography/noscrypt_cipher.cpp | 109 | ||||
-rw-r--r-- | src/cryptography/noscrypt_cipher.hpp | 193 | ||||
-rw-r--r-- | src/cryptography/nostr_secure_rng.cpp | 46 | ||||
-rw-r--r-- | src/cryptography/nostr_secure_rng.hpp | 51 |
4 files changed, 326 insertions, 73 deletions
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 <plog/Log.h> #include <openssl/evp.h> -#include <openssl/rand.h> -#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<uint8_t> 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<uint8_t>(encodedSize); // Encode the input string to base64. - EVP_EncodeBlock(encodedData, (const unsigned char*)str.data(), str.size()); + EVP_EncodeBlock( + encodedData.get(), + reinterpret_cast<const uint8_t*>(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<char*>(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<uint8_t>(decodedSize); // Decode the input string from base64. - EVP_DecodeBlock(decodedData, (const unsigned char*)str.data(), str.size()); + EVP_DecodeBlock( + decodedData.get(), + reinterpret_cast<const uint8_t*>(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<char*>(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 <memory> + +#include <noscrypt.h> +#include <noscryptutil.h> +#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<const NCContext> libContext, + const std::shared_ptr<const NCSecretKey> localKey, + const std::shared_ptr<const NCPublicKey> remoteKey + ) const + { + return NCUtilCipherUpdate(_cipher, libContext.get(), localKey.get(), remoteKey.get()); + } + + NCResult setIV(std::vector<uint8_t>& 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<uint8_t>& output) const + { + return NCUtilCipherReadOutput(_cipher, output.data(), (uint32_t)output.size()); + } + + NCResult setInput(const std::vector<uint8_t>& 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<uint8_t> _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<const NCContext> libContext, + const std::shared_ptr<const NCSecretKey> localKey, + const std::shared_ptr<const NCPublicKey> 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 <plog/Init.h> +#include <plog/Log.h> + +#include <openssl/evp.h> +#include <openssl/rand.h> +#include <openssl/crypto.h> + +#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<uint8_t>& 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<uint8_t>& 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 <memory> + +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<uint8_t>& 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<uint8_t>& buffer); +}; + +} // namespace cryptography +} // namespace nostr |