22 std::unique_ptr<log_writer_interface> wrapped,
25 config_(std::move(config)),
26 last_key_rotation_(std::chrono::system_clock::now())
30 throw std::invalid_argument(
31 "Invalid key size: expected 32 bytes, got " +
32 std::to_string(config_.key.size())
36#ifdef LOGGER_HAS_OPENSSL_CRYPTO
37 auto init_result = init_cipher_context();
38 if (init_result.is_err()) {
39 throw std::runtime_error(
40 "Failed to initialize cipher context: " +
41 get_logger_error_message(init_result)
51 "encrypted_writer initialized",
52 {{
"algorithm", std::to_string(static_cast<int>(config_.algorithm))},
53 {
"wrapped_writer", this->wrapped().get_name()}}
57encrypted_writer::~encrypted_writer() {
58 is_initialized_.store(
false);
60#ifdef LOGGER_HAS_OPENSSL_CRYPTO
61 cleanup_cipher_context();
69 if (!is_initialized_.load()) {
71 logger_error_code::encryption_failed,
72 "encrypted_writer not initialized"
77 auto rotate_result = auto_rotate_key_if_needed();
78 if (rotate_result.is_err()) {
80 security::audit_logger::log_audit_event(
81 security::audit_logger::audit_event::suspicious_activity,
82 "Key rotation failed",
88 std::ostringstream oss;
89 auto time_t_val = std::chrono::system_clock::to_time_t(entry.
timestamp);
92 localtime_s(&tm_val, &time_t_val);
94 localtime_r(&time_t_val, &tm_val);
98 auto level =
static_cast<common::interfaces::log_level
>(
static_cast<int>(entry.
level));
100 oss << std::put_time(&tm_val,
"%Y-%m-%dT%H:%M:%S")
101 <<
" [" <<
static_cast<int>(level) <<
"] "
105 std::string file = entry.
location->file.to_string();
106 std::string function = entry.
location->function.to_string();
108 oss <<
" (" << file <<
":" << entry.
location->line <<
" " << function <<
")";
112 std::string plaintext = oss.str();
115 std::vector<uint8_t> encrypted_data;
117 std::lock_guard lock(write_mutex_);
118 auto encrypt_result = encrypt_data(plaintext, encrypted_data);
119 if (encrypt_result.is_err()) {
120 return encrypt_result;
125 std::string binary_data(
126 reinterpret_cast<const char*
>(encrypted_data.data()),
127 encrypted_data.size()
132 auto write_result = wrapped().write(encrypted_entry);
133 if (write_result.is_ok()) {
134 entries_encrypted_.fetch_add(1);
142 if (new_key.
size() != 32) {
144 logger_error_code::invalid_key_size,
145 "New key must be 32 bytes for AES-256"
149 std::lock_guard lock(write_mutex_);
155 config_.key = std::move(new_key);
156 last_key_rotation_ = std::chrono::system_clock::now();
159 if (!config_.key_rotation_path.empty()) {
160 auto save_result = security::secure_key_storage::save_key(
162 config_.key_rotation_path,
163 config_.key_storage_base
165 if (save_result.is_err()) {
171 security::audit_logger::log_audit_event(
172 security::audit_logger::audit_event::encryption_key_rotated,
173 "Encryption key rotated",
174 {{
"entries_encrypted", std::to_string(entries_encrypted_.load())}}
180uint64_t encrypted_writer::get_entries_encrypted()
const {
181 return entries_encrypted_.load();
184std::chrono::system_clock::time_point encrypted_writer::get_last_key_rotation()
const {
185 return last_key_rotation_;
189 const std::vector<uint8_t>& encrypted_data,
192#ifdef LOGGER_HAS_OPENSSL_CRYPTO
196 logger_error_code::decryption_failed,
197 "Encrypted data too small"
203 std::memcpy(&header, encrypted_data.data(),
sizeof(header));
206 if (header.
magic != encrypted_log_header::kMagic) {
208 logger_error_code::decryption_failed,
209 "Invalid magic number in encrypted data"
214 if (header.
version != encrypted_log_header::kVersion) {
216 logger_error_code::decryption_failed,
217 "Unsupported encrypted log version"
223 if (encrypted_data.size() < expected_size) {
225 logger_error_code::decryption_failed,
226 "Encrypted data truncated"
231 EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
234 logger_error_code::decryption_failed,
235 "Failed to create cipher context"
240 const EVP_CIPHER* cipher =
nullptr;
242 case encryption_algorithm::aes_256_gcm:
243 cipher = EVP_aes_256_gcm();
245 case encryption_algorithm::aes_256_cbc:
246 cipher = EVP_aes_256_cbc();
248 case encryption_algorithm::chacha20_poly1305:
249 cipher = EVP_chacha20_poly1305();
252 EVP_CIPHER_CTX_free(ctx);
254 logger_error_code::decryption_failed,
255 "Unknown encryption algorithm"
260 if (EVP_DecryptInit_ex(ctx, cipher,
nullptr,
nullptr,
nullptr) != 1) {
261 EVP_CIPHER_CTX_free(ctx);
263 logger_error_code::decryption_failed,
264 "Failed to initialize decryption"
269 if (header.
algorithm ==
static_cast<uint8_t
>(encryption_algorithm::aes_256_gcm)) {
270 if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, 16,
nullptr) != 1) {
271 EVP_CIPHER_CTX_free(ctx);
273 logger_error_code::decryption_failed,
274 "Failed to set IV length"
280 if (EVP_DecryptInit_ex(ctx,
nullptr,
nullptr,
281 key.
data().data(), header.
iv.data()) != 1) {
282 EVP_CIPHER_CTX_free(ctx);
284 logger_error_code::decryption_failed,
285 "Failed to set key and IV"
290 if (header.
algorithm ==
static_cast<uint8_t
>(encryption_algorithm::aes_256_gcm)) {
291 if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16,
292 const_cast<uint8_t*
>(header.
tag.data())) != 1) {
293 EVP_CIPHER_CTX_free(ctx);
295 logger_error_code::decryption_failed,
296 "Failed to set authentication tag"
303 int plaintext_len = 0;
305 const uint8_t* ciphertext = encrypted_data.data() +
sizeof(header);
306 if (EVP_DecryptUpdate(ctx, plaintext.data(), &plaintext_len,
308 EVP_CIPHER_CTX_free(ctx);
310 logger_error_code::decryption_failed,
316 if (EVP_DecryptFinal_ex(ctx, plaintext.data() + plaintext_len, &final_len) != 1) {
317 EVP_CIPHER_CTX_free(ctx);
319 logger_error_code::decryption_failed,
320 "Decryption finalization failed (authentication failure)"
324 EVP_CIPHER_CTX_free(ctx);
326 plaintext_len += final_len;
328 reinterpret_cast<char*
>(plaintext.data()),
333 logger_error_code::decryption_failed,
334 "OpenSSL not available - decryption disabled"
340 const std::string& plaintext,
341 std::vector<uint8_t>& output
343#ifdef LOGGER_HAS_OPENSSL_CRYPTO
346 auto iv_result = generate_iv(iv);
347 if (iv_result.is_err()) {
353 header.
algorithm =
static_cast<uint8_t
>(config_.algorithm);
355 std::memcpy(header.
iv.data(), iv, encrypted_log_header::kIvSize);
358 if (EVP_EncryptInit_ex(cipher_ctx_,
nullptr,
nullptr,
359 config_.key.data().data(), iv) != 1) {
361 logger_error_code::encryption_failed,
362 "Failed to initialize encryption"
367 size_t max_ciphertext_len = plaintext.size() + EVP_MAX_BLOCK_LENGTH;
368 output.resize(
sizeof(header) + max_ciphertext_len);
371 int ciphertext_len = 0;
372 if (EVP_EncryptUpdate(cipher_ctx_,
373 output.data() +
sizeof(header),
375 reinterpret_cast<const uint8_t*
>(plaintext.data()),
376 static_cast<int>(plaintext.size())) != 1) {
378 logger_error_code::encryption_failed,
379 "Encryption update failed"
384 if (EVP_EncryptFinal_ex(cipher_ctx_,
385 output.data() +
sizeof(header) + ciphertext_len,
388 logger_error_code::encryption_failed,
389 "Encryption finalization failed"
393 ciphertext_len += final_len;
397 if (config_.algorithm == encryption_algorithm::aes_256_gcm) {
398 if (EVP_CIPHER_CTX_ctrl(cipher_ctx_, EVP_CTRL_GCM_GET_TAG,
399 encrypted_log_header::kTagSize, header.
tag.data()) != 1) {
401 logger_error_code::encryption_failed,
402 "Failed to get authentication tag"
408 std::memcpy(output.data(), &header,
sizeof(header));
411 output.resize(
sizeof(header) + ciphertext_len);
416 logger_error_code::encryption_failed,
417 "OpenSSL not available - encryption disabled"
423#ifdef LOGGER_HAS_OPENSSL_CRYPTO
424 if (RAND_bytes(iv, 16) != 1) {
426 logger_error_code::encryption_failed,
427 "Failed to generate random IV"
433 logger_error_code::encryption_failed,
434 "OpenSSL not available"
439bool encrypted_writer::should_rotate_key()
const {
440 if (!config_.key_rotation_interval.has_value()) {
444 auto now = std::chrono::system_clock::now();
445 auto elapsed = std::chrono::duration_cast<std::chrono::hours>(
446 now - last_key_rotation_
449 return elapsed >= config_.key_rotation_interval.value();
453 if (!should_rotate_key()) {
458 auto key_result = security::secure_key_storage::generate_key(32);
459 if (!key_result.has_value()) {
461 key_result.error_code(),
462 key_result.error_message()
466 return rotate_key(std::move(key_result.value()));
469#ifdef LOGGER_HAS_OPENSSL_CRYPTO
471 cipher_ctx_ = EVP_CIPHER_CTX_new();
474 logger_error_code::encryption_failed,
475 "Failed to create cipher context"
480 const EVP_CIPHER* cipher =
nullptr;
481 switch (config_.algorithm) {
482 case encryption_algorithm::aes_256_gcm:
483 cipher = EVP_aes_256_gcm();
485 case encryption_algorithm::aes_256_cbc:
486 cipher = EVP_aes_256_cbc();
488 case encryption_algorithm::chacha20_poly1305:
489 cipher = EVP_chacha20_poly1305();
492 EVP_CIPHER_CTX_free(cipher_ctx_);
493 cipher_ctx_ =
nullptr;
494 return make_logger_void_result(
495 logger_error_code::encryption_failed,
496 "Unknown encryption algorithm"
501 if (EVP_EncryptInit_ex(cipher_ctx_, cipher,
nullptr,
nullptr,
nullptr) != 1) {
502 EVP_CIPHER_CTX_free(cipher_ctx_);
503 cipher_ctx_ =
nullptr;
504 return make_logger_void_result(
505 logger_error_code::encryption_failed,
506 "Failed to initialize cipher"
511 if (config_.algorithm == encryption_algorithm::aes_256_gcm) {
512 if (EVP_CIPHER_CTX_ctrl(cipher_ctx_, EVP_CTRL_GCM_SET_IVLEN, 16,
nullptr) != 1) {
513 EVP_CIPHER_CTX_free(cipher_ctx_);
514 cipher_ctx_ =
nullptr;
516 logger_error_code::encryption_failed,
517 "Failed to set IV length"
525void encrypted_writer::cleanup_cipher_context() {
527 EVP_CIPHER_CTX_free(cipher_ctx_);
528 cipher_ctx_ =
nullptr;
540#ifdef LOGGER_HAS_OPENSSL_CRYPTO
541 cipher_ctx_ = EVP_CIPHER_CTX_new();
546#ifdef LOGGER_HAS_OPENSSL_CRYPTO
548 EVP_CIPHER_CTX_free(cipher_ctx_);
549 cipher_ctx_ =
nullptr;
555 const std::filesystem::path& input_path,
556 const std::filesystem::path& output_path
558 std::ofstream output(output_path, std::ios::binary | std::ios::trunc);
562 "Failed to open output file: " + output_path.string()
566 size_t entry_count = 0;
567 auto callback = [&output, &entry_count](
const std::string& decrypted) {
568 output << decrypted <<
"\n";
573 if (!stream_result.has_value()) {
574 return stream_result;
581 const std::filesystem::path& input_path,
582 std::function<
void(
const std::string&)> callback
584#ifdef LOGGER_HAS_OPENSSL_CRYPTO
585 std::ifstream input(input_path, std::ios::binary);
589 "Failed to open input file: " + input_path.string()
593 size_t entry_count = 0;
594 std::vector<uint8_t> buffer;
596 while (input.good()) {
599 input.read(
reinterpret_cast<char*
>(&header),
sizeof(header));
601 if (input.gcount() == 0) {
605 if (input.gcount() !=
sizeof(header)) {
608 "Incomplete header at entry " + std::to_string(entry_count)
616 "Invalid magic number at entry " + std::to_string(entry_count)
622 std::memcpy(buffer.data(), &header,
sizeof(header));
624 reinterpret_cast<char*
>(buffer.data() +
sizeof(header)),
631 "Incomplete data at entry " + std::to_string(entry_count)
637 if (!decrypt_result.has_value()) {
639 decrypt_result.error_code(),
640 "Decryption failed at entry " + std::to_string(entry_count) +
641 ": " + decrypt_result.error_message()
645 callback(decrypt_result.value());
653 "OpenSSL not available - decryption disabled"
Secure audit logging with tamper detection.
Abstract base class for decorator pattern log writers.
encrypted_writer(std::unique_ptr< log_writer_interface > wrapped, encryption_config config)
Construct encrypted writer with wrapped writer.
std::atomic< bool > is_initialized_
static result< std::string > decrypt_entry(const std::vector< uint8_t > &encrypted_data, const security::secure_key &key)
Decrypt a single encrypted log entry.
encryption_config config_
result< size_t > decrypt_file_streaming(const std::filesystem::path &input_path, std::function< void(const std::string &)> callback)
Decrypt file with callback for each entry.
~log_decryptor()
Destructor - cleans up key material.
result< size_t > decrypt_file(const std::filesystem::path &input_path, const std::filesystem::path &output_path)
Decrypt entire file to output file.
security::secure_key key_
static void log_audit_event(audit_event event, const std::string &details, const std::map< std::string, std::string > &metadata={})
Log an audit event.
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 to_string() const
Convert to std::string.
Encryption wrapper for log writers providing AES-256-GCM encryption.
Data structures for representing log entries and source locations kcenon.
common::VoidResult make_logger_void_result(logger_error_code code, const std::string &message="")
std::string get_logger_error_message(const common::VoidResult &result)
Get error message from a VoidResult.
encryption_algorithm
Supported encryption algorithms for log encryption.
Configuration for encrypted_writer.
security::secure_key key
Encryption key (must be 32 bytes for AES-256)
Represents a single log entry with all associated metadata.
std::optional< source_location > location
Optional source code location information.
log_level level
Severity level of the log message.
small_string_256 message
The actual log message.
std::chrono::system_clock::time_point timestamp
Timestamp when the log entry was created.