aboutsummaryrefslogtreecommitdiff
path: root/src/cryptography
diff options
context:
space:
mode:
Diffstat (limited to 'src/cryptography')
-rw-r--r--src/cryptography/noscrypt_cipher.cpp109
-rw-r--r--src/cryptography/noscrypt_cipher.hpp193
-rw-r--r--src/cryptography/nostr_secure_rng.cpp46
-rw-r--r--src/cryptography/nostr_secure_rng.hpp51
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