Logger System 1.0.0
High-performance C++20 thread-safe logging system with asynchronous capabilities
Loading...
Searching...
No Matches
integrity_policy.h
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2025, kcenon
3// See the LICENSE file in the project root for full license information.
4
23#pragma once
24
26
27#include <iomanip>
28#include <memory>
29#include <sstream>
30#include <string>
31#include <utility>
32
33// OpenSSL 3.x+ for HMAC via EVP_MAC API (optional, fallback to portable hash)
34#if __has_include(<openssl/hmac.h>)
35#include <openssl/core_names.h>
36#include <openssl/evp.h>
37#include <openssl/hmac.h>
38#include <openssl/params.h>
39#ifndef LOGGER_INTEGRITY_HAS_OPENSSL
40#define LOGGER_INTEGRITY_HAS_OPENSSL 1
41#endif
42#endif
43
45
54public:
55 virtual ~integrity_policy() = default;
56
62 virtual std::string sign(const std::string& record) const = 0;
63
70 virtual bool verify(const std::string& record,
71 const std::string& signature) const = 0;
72
78 virtual std::string name() const = 0;
79};
80
94public:
103 : key_(std::make_shared<secure_key>(std::move(key))) {
104 }
105
106 std::string sign(const std::string& record) const override {
107 return compute_hmac(record, *key_);
108 }
109
110 bool verify(const std::string& record,
111 const std::string& signature) const override {
112 if (signature.empty()) {
113 return false;
114 }
115 const std::string expected = compute_hmac(record, *key_);
116 if (expected.empty() || expected.size() != signature.size()) {
117 return false;
118 }
119 // Constant-time compare to avoid signature-timing leaks.
120 unsigned char diff = 0;
121 for (size_t i = 0; i < expected.size(); ++i) {
122 diff |= static_cast<unsigned char>(expected[i])
123 ^ static_cast<unsigned char>(signature[i]);
124 }
125 return diff == 0;
126 }
127
128 std::string name() const override {
129 return "HMAC-SHA256";
130 }
131
132private:
133 static std::string compute_hmac(const std::string& message,
134 const secure_key& key) {
135#ifdef LOGGER_INTEGRITY_HAS_OPENSSL
136 unsigned char digest[EVP_MAX_MD_SIZE];
137 size_t digest_len = 0;
138
139 EVP_MAC* mac = EVP_MAC_fetch(nullptr, "HMAC", nullptr);
140 if (!mac) {
141 return std::string();
142 }
143
144 EVP_MAC_CTX* ctx = EVP_MAC_CTX_new(mac);
145 if (!ctx) {
146 EVP_MAC_free(mac);
147 return std::string();
148 }
149
150 OSSL_PARAM params[] = {
151 OSSL_PARAM_construct_utf8_string(
152 OSSL_MAC_PARAM_DIGEST,
153 const_cast<char*>("SHA256"),
154 0),
155 OSSL_PARAM_END
156 };
157
158 if (EVP_MAC_init(ctx, key.data().data(), key.size(), params) != 1) {
159 EVP_MAC_CTX_free(ctx);
160 EVP_MAC_free(mac);
161 return std::string();
162 }
163
164 if (EVP_MAC_update(
165 ctx,
166 reinterpret_cast<const unsigned char*>(message.data()),
167 message.size()) != 1) {
168 EVP_MAC_CTX_free(ctx);
169 EVP_MAC_free(mac);
170 return std::string();
171 }
172
173 if (EVP_MAC_final(ctx, digest, &digest_len, sizeof(digest)) != 1) {
174 EVP_MAC_CTX_free(ctx);
175 EVP_MAC_free(mac);
176 return std::string();
177 }
178
179 EVP_MAC_CTX_free(ctx);
180 EVP_MAC_free(mac);
181
182 std::ostringstream hex;
183 hex << std::hex << std::setfill('0');
184 for (size_t i = 0; i < digest_len; ++i) {
185 hex << std::setw(2) << static_cast<int>(digest[i]);
186 }
187 return hex.str();
188#else
189 // Portable fallback. NOT cryptographically secure; intended only
190 // to keep sign/verify round-trip working on builds without OpenSSL.
191 std::size_t hash = 0xcbf29ce484222325ULL;
192 for (size_t i = 0; i < message.size(); ++i) {
193 hash ^= static_cast<unsigned char>(message[i]);
194 hash *= 0x100000001b3ULL;
195 if (i < key.size()) {
196 hash ^= key.data()[i];
197 }
198 }
199 std::ostringstream oss;
200 oss << std::hex << std::setfill('0') << std::setw(16) << hash;
201 return oss.str();
202#endif
203 }
204
205 // shared_ptr so the policy can be copied cheaply across decorator chains
206 // while still ensuring the key is zeroized when the last owner releases.
207 std::shared_ptr<secure_key> key_;
208};
209
218inline std::string format_signature_suffix(const integrity_policy& policy,
219 const std::string& record) {
220 std::string sig = policy.sign(record);
221 if (sig.empty()) {
222 return std::string();
223 }
224 std::string out;
225 out.reserve(14 + policy.name().size() + sig.size());
226 out.append(" SIGNATURE[");
227 out.append(policy.name());
228 out.append("]:");
229 out.append(sig);
230 return out;
231}
232
233} // namespace kcenon::logger::security
HMAC-SHA256 integrity policy (ISO/IEC 27001 A.12.4.2 default).
hmac_sha256_integrity_policy(secure_key key)
Construct from a secure_key.
bool verify(const std::string &record, const std::string &signature) const override
Verify that signature matches record.
static std::string compute_hmac(const std::string &message, const secure_key &key)
std::string sign(const std::string &record) const override
Produce a signature for record.
std::string name() const override
Short identifier used as a prefix in serialized signatures (e.g. "HMAC-SHA256"). Implementations must...
Abstract interface for log integrity signing.
virtual bool verify(const std::string &record, const std::string &signature) const =0
Verify that signature matches record.
virtual std::string sign(const std::string &record) const =0
Produce a signature for record.
virtual std::string name() const =0
Short identifier used as a prefix in serialized signatures (e.g. "HMAC-SHA256"). Implementations must...
RAII wrapper for encryption keys with secure memory management.
size_t size() const
Get key size in bytes.
const std::vector< uint8_t > & data() const
Get const reference to key data.
std::string format_signature_suffix(const integrity_policy &policy, const std::string &record)
Format a signature line suitable for appending to a text log record.
RAII wrapper for encryption keys with secure memory management.