/* {{copyright}} */ /* {{version}} */ /* {{license}} */ #pragma once #include "data.h" #include #include #include #include #include #include #include #include #include namespace sdi_toolBox::crypto { //-------------------------------------------------------------- class DigitalSign { // Custom deleter for EVP_PKEY to ensure it's automatically freed struct EVP_PKEY_Deleter { void operator()(EVP_PKEY *p) const { if (p) { EVP_PKEY_free(p); } } }; using pEVP_PKEY = std::unique_ptr; struct BIO_Deleter { void operator()(BIO *b) const { if (b) { BIO_free(b); } } }; using pBIO = std::unique_ptr; struct EVP_MD_CTX_Deleter { void operator()(EVP_MD_CTX *ctx) const { if (ctx) { EVP_MD_CTX_free(ctx); } } }; using pEVP_MD_CTX = std::unique_ptr; public: DigitalSign() = default; // Default constructor virtual ~DigitalSign() = default; // Default destructor DigitalSign(const DigitalSign &obj) = delete; // Copy constructor DigitalSign(DigitalSign &&obj) noexcept = delete; // Move constructor DigitalSign &operator=(const DigitalSign &obj) = delete; // Copy assignment operator DigitalSign &operator=(DigitalSign &&obj) noexcept = delete; // Move assignment operator void loadPEMKeyFromFile(const std::filesystem::path &filepath, bool is_private); // Load key from PEM file void loadPEMKeyFromMemory(const std::string &key, bool is_private); // Load key from memory std::vector sign(const Data8 &data, const EVP_MD *digest_type = EVP_sha256()) const; // Sign data with private key std::string signHex(const Data8 &data, const EVP_MD *digest_type = EVP_sha256()) const; // Sign data with private key bool verify(const Data8 &data, const std::span &signature, const EVP_MD *digest_type = EVP_sha256()) const; // Verify signature with public key bool verify(const Data8 &data, const std::string &signature, const EVP_MD *digest_type = EVP_sha256()) const; // Verify signature with public key protected: pEVP_PKEY m_privateKey; pEVP_PKEY m_publicKey; private: static std::vector hexToBytes(const std::string &hexString); // Converts a hexadecimal string into a vector of bytes static std::string bytesToHex(const std::span &bytes); // Converts a span of bytes into a hexadecimal string representation }; //-------------------------------------------------------------- /* Load key from PEM file */ inline void DigitalSign::loadPEMKeyFromFile(const std::filesystem::path &filepath, const bool is_private) { std::ifstream file(filepath, std::ifstream::binary); if (!file.is_open()) throw std::runtime_error("Error: Could not open file " + filepath.string()); const std::string key((std::istreambuf_iterator(file)), std::istreambuf_iterator()); loadPEMKeyFromMemory(key, is_private); } //-------------------------------------------------------------- /* Load key from memory */ inline void DigitalSign::loadPEMKeyFromMemory(const std::string &key, const bool is_private) { if (key.empty()) throw std::runtime_error("Error: Input stream is empty or could not be read"); // Create a memory BIO (Basic Input/Output) for reading the key const auto bio = static_cast(BIO_new_mem_buf(key.data(), static_cast(key.size()))); if (!bio) throw std::runtime_error("Error: Failed to create memory BIO"); // Use the BIO to read the key if (is_private) { m_privateKey = static_cast(PEM_read_bio_PrivateKey(bio.get(), nullptr, nullptr, nullptr)); if (!m_privateKey) throw std::runtime_error("Error: Failed to read private key from memory buffer"); } else { m_publicKey = static_cast(PEM_read_bio_PUBKEY(bio.get(), nullptr, nullptr, nullptr)); if (!m_publicKey) throw std::runtime_error("Error: Failed to read private key from memory buffer"); } } //-------------------------------------------------------------- /* Sign data with private key */ inline std::vector DigitalSign::sign(const Data8 &data, const EVP_MD *digest_type) const { if (!m_privateKey) throw std::runtime_error("Error: Private key is not loaded for signing"); // Initialize the context for signing const auto md_ctx = static_cast(EVP_MD_CTX_new()); if (!md_ctx) throw std::runtime_error("Error: Failed to create EVP_MD_CTX"); // Set the context for signing with the private key and digest if (EVP_DigestSignInit(md_ctx.get(), nullptr, digest_type, nullptr, m_privateKey.get()) != 1) throw std::runtime_error("Error: EVP_DigestSignInit failed"); // Provide the data to be signed if (EVP_DigestSignUpdate(md_ctx.get(), data.getBytes().data(), data.getSize()) != 1) throw std::runtime_error("Error: EVP_DigestSignUpdate failed"); // Determine the required signature size size_t sigLen = 0; if (EVP_DigestSignFinal(md_ctx.get(), nullptr, &sigLen) != 1) throw std::runtime_error("Error: EVP_DigestSignFinal (size) failed"); // Allocate space and generate the signature std::vector signature(sigLen); if (EVP_DigestSignFinal(md_ctx.get(), signature.data(), &sigLen) != 1) throw std::runtime_error("Error: EVP_DigestSignFinal (signature) failed"); // Resize signature to the actual length used signature.resize(sigLen); return signature; } //-------------------------------------------------------------- /* Sign data */ inline std::string DigitalSign::signHex(const Data8 &data, const EVP_MD *digest_type) const { return bytesToHex(sign(data, digest_type)); } //-------------------------------------------------------------- /* Verify signature with public key */ inline bool DigitalSign::verify(const Data8 &data, const std::span &signature, const EVP_MD *digest_type) const { if (!m_publicKey) throw std::runtime_error("Error: Public key is not loaded for verifying"); // Initialize the context for verifying const auto md_ctx = static_cast(EVP_MD_CTX_new()); if (!md_ctx) throw std::runtime_error("Error: Failed to create EVP_MD_CTX"); // Set the context for verifying with the public key and digest if (EVP_DigestVerifyInit(md_ctx.get(), nullptr, digest_type, nullptr, m_publicKey.get()) != 1) throw std::runtime_error("Error: EVP_DigestVerifyInit failed"); // Provide the original data if (EVP_DigestVerifyUpdate(md_ctx.get(), data.getBytes().data(), data.getSize()) != 1) throw std::runtime_error("Error: EVP_DigestVerifyUpdate failed"); // Verify the signature // Returns 1 for a good signature, 0 for a bad signature, and -1 on error const auto result = EVP_DigestVerifyFinal(md_ctx.get(), signature.data(), signature.size()); if (result == 1) return true; // Signature is valid if (result == 0) return false; // Signature is NOT valid throw std::runtime_error("Error: EVP_DigestVerifyFinal failed during execution"); } //-------------------------------------------------------------- /* Verify signature with public key */ inline bool DigitalSign::verify(const Data8 &data, const std::string &signature, const EVP_MD *digest_type) const { return verify(data, hexToBytes(signature), digest_type); } //-------------------------------------------------------------- /* Converts a hexadecimal string into a vector of bytes */ inline std::vector DigitalSign::hexToBytes(const std::string &hexString) { // A valid hex string must have an even length if (hexString.length() % 2 != 0) throw std::invalid_argument("Hex string must have an even length"); std::vector bytes; bytes.reserve(hexString.length() / 2); for (size_t i = 0; i < hexString.length(); i += 2) { std::string byteString = hexString.substr(i, 2); unsigned long byteVal; try { byteVal = std::stoul(byteString, nullptr, 16); } catch (const std::exception & /*e*/) { throw std::invalid_argument("Invalid hexadecimal character sequence: " + byteString); // Non-hex characters ? } // Check if the value fits in a uint8_t (should always be true for 2 chars) if (byteVal > 0xFF) throw std::invalid_argument("Internal error: Converted value exceeds 0xFF"); bytes.push_back(static_cast(byteVal)); } return bytes; } //-------------------------------------------------------------- /* Converts a span of bytes into a hexadecimal string representation */ inline std::string DigitalSign::bytesToHex(const std::span &bytes) { std::stringstream ss; ss << std::hex << std::uppercase << std::setfill('0'); for (const uint8_t byte : bytes) ss << std::format("{0:02X}", byte); return ss.str(); } //-------------------------------------------------------------- } // namespace sdi_toolBox::crypto