Database System 0.1.0
Advanced C++20 Database System with Multi-Backend Support
Loading...
Searching...
No Matches
secure_connection.cpp
Go to the documentation of this file.
1// BSD 3-Clause License
2// Copyright (c) 2025, 🍀☀🌕🌥 🌊
3// See the LICENSE file in the project root for full license information.
4
5#include "secure_connection.h"
6
7#include <algorithm>
8#include <fstream>
9#include <iomanip>
10#include <iostream>
11#include <random>
12#include <sstream>
13#include <stdexcept>
14
15// DATABASE_HAS_OPENSSL is defined by CMake when OpenSSL is found and linked.
16// Do not use __has_include — it detects headers but not library availability.
17#ifdef DATABASE_HAS_OPENSSL
18#include <openssl/evp.h>
19#include <openssl/rand.h>
20#include <openssl/err.h>
21#endif
22
23namespace database::security
24{
25
26 // ─────────────────────────────────────────────
27 // credential_manager
28 // ─────────────────────────────────────────────
29
30 bool credential_manager::store_credentials(const std::string& connection_id,
31 const security_credentials& credentials)
32 {
33 std::lock_guard<std::mutex> lock(credentials_mutex_);
34
35 // Serialize credentials to a simple format and encrypt
36 std::ostringstream oss;
37 oss << credentials.username << "\n"
38 << credentials.password_hash << "\n"
39 << credentials.certificate_path << "\n"
40 << credentials.private_key_path << "\n"
41 << credentials.ca_cert_path << "\n"
42 << static_cast<int>(credentials.auth_method) << "\n"
43 << static_cast<int>(credentials.encryption);
44
45 std::string serialized = oss.str();
46 std::string encrypted = encrypt_data(serialized);
47 encrypted_credentials_[connection_id] = encrypted;
48 return true;
49 }
50
51 std::optional<security_credentials> credential_manager::get_credentials(
52 const std::string& connection_id) const
53 {
54 std::lock_guard<std::mutex> lock(credentials_mutex_);
55
56 auto it = encrypted_credentials_.find(connection_id);
57 if (it == encrypted_credentials_.end())
58 {
59 return std::nullopt;
60 }
61
62 std::string decrypted = decrypt_data(it->second);
63 if (decrypted.empty())
64 {
65 return std::nullopt;
66 }
67
68 // Deserialize credentials
69 std::istringstream iss(decrypted);
71 std::getline(iss, creds.username);
72 std::getline(iss, creds.password_hash);
73 std::getline(iss, creds.certificate_path);
74 std::getline(iss, creds.private_key_path);
75 std::getline(iss, creds.ca_cert_path);
76
77 int auth_method_int = 0;
78 int encryption_int = 0;
79 iss >> auth_method_int >> encryption_int;
80 creds.auth_method = static_cast<authentication_method>(auth_method_int);
81 creds.encryption = static_cast<encryption_type>(encryption_int);
82
83 return creds;
84 }
85
86 bool credential_manager::remove_credentials(const std::string& connection_id)
87 {
88 std::lock_guard<std::mutex> lock(credentials_mutex_);
89 return encrypted_credentials_.erase(connection_id) > 0;
90 }
91
92 void credential_manager::set_master_key(const std::string& key)
93 {
94 std::lock_guard<std::mutex> lock(credentials_mutex_);
95 master_key_ = key;
96 }
97
99 {
100 std::lock_guard<std::mutex> lock(credentials_mutex_);
101
102 if (master_key_.empty())
103 {
104 return false;
105 }
106
107 // Generate new key
108 std::string old_key = master_key_;
109 std::random_device rd;
110 std::mt19937 gen(rd());
111 std::uniform_int_distribution<int> dist(33, 126); // printable ASCII
112 std::string new_key;
113 new_key.reserve(32);
114 for (int i = 0; i < 32; ++i)
115 {
116 new_key.push_back(static_cast<char>(dist(gen)));
117 }
118
119 // Re-encrypt all stored credentials with new key
120 std::unordered_map<std::string, std::string> rotated;
121 for (const auto& [id, encrypted] : encrypted_credentials_)
122 {
123 // Decrypt with old key
124 std::string decrypted = decrypt_data(encrypted);
125 if (decrypted.empty())
126 {
127 return false; // Rotation failed — abort to preserve data
128 }
129
130 // Re-encrypt with new key (temporarily set new key)
131 master_key_ = new_key;
132 std::string re_encrypted = encrypt_data(decrypted);
133 master_key_ = old_key; // Restore old key in case of further failures
134
135 if (re_encrypted.empty())
136 {
137 return false;
138 }
139 rotated[id] = re_encrypted;
140 }
141
142 // Commit: swap all credentials and update key
143 encrypted_credentials_ = std::move(rotated);
144 master_key_ = new_key;
145 return true;
146 }
147
148 namespace
149 {
150 std::string bytes_to_hex(const std::vector<uint8_t>& bytes)
151 {
152 std::ostringstream oss;
153 for (uint8_t b : bytes)
154 {
155 oss << std::hex << std::setfill('0') << std::setw(2) << static_cast<int>(b);
156 }
157 return oss.str();
158 }
159
160 std::vector<uint8_t> hex_to_bytes(const std::string& hex)
161 {
162 std::vector<uint8_t> bytes;
163 bytes.reserve(hex.size() / 2);
164 for (size_t i = 0; i + 1 < hex.size(); i += 2)
165 {
166 unsigned int val = 0;
167 std::istringstream iss(hex.substr(i, 2));
168 iss >> std::hex >> val;
169 bytes.push_back(static_cast<uint8_t>(val));
170 }
171 return bytes;
172 }
173 } // anonymous namespace
174
175 std::string credential_manager::hash_password(const std::string& password) const
176 {
177 if (password.empty())
178 {
179 return {};
180 }
181
182#ifdef DATABASE_HAS_OPENSSL
183 // PBKDF2-HMAC-SHA256: cryptographically secure password hashing
184 constexpr int iterations = 100000;
185 constexpr int salt_len = 16;
186 constexpr int hash_len = 32;
187
188 std::vector<uint8_t> salt(salt_len);
189 RAND_bytes(salt.data(), salt_len);
190
191 std::vector<uint8_t> hash(hash_len);
192 PKCS5_PBKDF2_HMAC(
193 password.c_str(), static_cast<int>(password.size()),
194 salt.data(), salt_len,
195 iterations,
196 EVP_sha256(),
197 hash_len, hash.data()
198 );
199
200 // Format: "pbkdf2:<iterations>:<salt_hex>:<hash_hex>"
201 std::ostringstream oss;
202 oss << "pbkdf2:" << iterations << ":" << bytes_to_hex(salt) << ":" << bytes_to_hex(hash);
203 return oss.str();
204#else
205 // WARNING: FNV1a is NOT cryptographically secure.
206 // Build with OpenSSL to enable PBKDF2-HMAC-SHA256 password hashing.
207 static bool warned = false;
208 if (!warned)
209 {
210 std::cerr << "[database_system] WARNING: Using FNV1a placeholder for password hashing. "
211 << "Build with OpenSSL for PBKDF2-HMAC-SHA256 support.\n";
212 warned = true;
213 }
214
215 uint64_t hash = 0xcbf29ce484222325ULL;
216 for (char c : password)
217 {
218 hash ^= static_cast<uint64_t>(c);
219 hash *= 0x100000001b3ULL;
220 }
221
222 std::ostringstream oss;
223 oss << std::hex << std::setfill('0') << std::setw(16) << hash;
224 return "fnv1a:" + oss.str();
225#endif
226 }
227
229 const std::string& hash) const
230 {
231 if (password.empty() || hash.empty())
232 {
233 return false;
234 }
235
236#ifdef DATABASE_HAS_OPENSSL
237 // Parse format: "pbkdf2:<iterations>:<salt_hex>:<hash_hex>"
238 if (hash.substr(0, 7) == "pbkdf2:")
239 {
240 auto first_colon = hash.find(':', 7);
241 auto second_colon = hash.find(':', first_colon + 1);
242 if (first_colon == std::string::npos || second_colon == std::string::npos)
243 {
244 return false;
245 }
246
247 int iterations = std::stoi(hash.substr(7, first_colon - 7));
248 auto salt = hex_to_bytes(hash.substr(first_colon + 1, second_colon - first_colon - 1));
249 auto expected_hash = hex_to_bytes(hash.substr(second_colon + 1));
250
251 std::vector<uint8_t> computed(expected_hash.size());
252 PKCS5_PBKDF2_HMAC(
253 password.c_str(), static_cast<int>(password.size()),
254 salt.data(), static_cast<int>(salt.size()),
255 iterations,
256 EVP_sha256(),
257 static_cast<int>(computed.size()), computed.data()
258 );
259
260 return computed == expected_hash;
261 }
262
263 // Legacy FNV1a format migration: verify with old algorithm
264 if (hash.substr(0, 6) == "fnv1a:")
265 {
266 uint64_t h = 0xcbf29ce484222325ULL;
267 for (char c : password)
268 {
269 h ^= static_cast<uint64_t>(c);
270 h *= 0x100000001b3ULL;
271 }
272 std::ostringstream oss;
273 oss << std::hex << std::setfill('0') << std::setw(16) << h;
274 return hash == ("fnv1a:" + oss.str());
275 }
276#endif
277
278 // Fallback: direct comparison (covers fnv1a format without OpenSSL)
279 return hash_password(password) == hash;
280 }
281
282 std::string credential_manager::encrypt_data(const std::string& data) const
283 {
284 if (data.empty())
285 {
286 return {};
287 }
288
289#ifdef DATABASE_HAS_OPENSSL
290 // AES-256-GCM encryption
291 std::string key = master_key_.empty() ? "default_key_placeholder!!" : master_key_;
292 // Pad or truncate key to 32 bytes for AES-256
293 key.resize(32, '\0');
294
295 constexpr int iv_len = 12;
296 constexpr int tag_len = 16;
297
298 std::vector<uint8_t> iv(iv_len);
299 RAND_bytes(iv.data(), iv_len);
300
301 EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
302 if (!ctx) return {};
303
304 std::vector<uint8_t> ciphertext(data.size() + EVP_MAX_BLOCK_LENGTH);
305 std::vector<uint8_t> tag(tag_len);
306 int len = 0, ciphertext_len = 0;
307
308 EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr);
309 EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, nullptr);
310 EVP_EncryptInit_ex(ctx, nullptr, nullptr,
311 reinterpret_cast<const unsigned char*>(key.data()), iv.data());
312 EVP_EncryptUpdate(ctx, ciphertext.data(), &len,
313 reinterpret_cast<const unsigned char*>(data.data()),
314 static_cast<int>(data.size()));
315 ciphertext_len = len;
316 EVP_EncryptFinal_ex(ctx, ciphertext.data() + len, &len);
317 ciphertext_len += len;
318 EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, tag_len, tag.data());
319 EVP_CIPHER_CTX_free(ctx);
320
321 ciphertext.resize(ciphertext_len);
322
323 // Format: "aes:<iv_hex>:<ciphertext_hex>:<tag_hex>"
324 return "aes:" + bytes_to_hex(iv) + ":" + bytes_to_hex(ciphertext) + ":" + bytes_to_hex(tag);
325#else
326 // WARNING: XOR obfuscation is NOT cryptographically secure.
327 static bool warned = false;
328 if (!warned)
329 {
330 std::cerr << "[database_system] WARNING: Using XOR placeholder for data encryption. "
331 << "Build with OpenSSL for AES-256-GCM support.\n";
332 warned = true;
333 }
334
335 std::string key = master_key_.empty() ? "default_key" : master_key_;
336 std::string result = data;
337 for (size_t i = 0; i < result.size(); ++i)
338 {
339 result[i] ^= key[i % key.size()];
340 }
341
342 std::ostringstream oss;
343 for (unsigned char c : result)
344 {
345 oss << std::hex << std::setfill('0') << std::setw(2)
346 << static_cast<int>(c);
347 }
348 return "xor:" + oss.str();
349#endif
350 }
351
352 std::string credential_manager::decrypt_data(const std::string& encrypted_data) const
353 {
354 if (encrypted_data.empty())
355 {
356 return {};
357 }
358
359#ifdef DATABASE_HAS_OPENSSL
360 // AES-256-GCM decryption: parse "aes:<iv_hex>:<ciphertext_hex>:<tag_hex>"
361 if (encrypted_data.substr(0, 4) == "aes:")
362 {
363 auto first = encrypted_data.find(':', 4);
364 auto second = encrypted_data.find(':', first + 1);
365 if (first == std::string::npos || second == std::string::npos)
366 {
367 return {};
368 }
369
370 auto iv = hex_to_bytes(encrypted_data.substr(4, first - 4));
371 auto ciphertext = hex_to_bytes(encrypted_data.substr(first + 1, second - first - 1));
372 auto tag = hex_to_bytes(encrypted_data.substr(second + 1));
373
374 std::string key = master_key_.empty() ? "default_key_placeholder!!" : master_key_;
375 key.resize(32, '\0');
376
377 EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
378 if (!ctx) return {};
379
380 std::vector<uint8_t> plaintext(ciphertext.size() + EVP_MAX_BLOCK_LENGTH);
381 int len = 0, plaintext_len = 0;
382
383 EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr);
384 EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, static_cast<int>(iv.size()), nullptr);
385 EVP_DecryptInit_ex(ctx, nullptr, nullptr,
386 reinterpret_cast<const unsigned char*>(key.data()), iv.data());
387 EVP_DecryptUpdate(ctx, plaintext.data(), &len,
388 ciphertext.data(), static_cast<int>(ciphertext.size()));
389 plaintext_len = len;
390 EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, static_cast<int>(tag.size()),
391 const_cast<uint8_t*>(tag.data()));
392
393 int ret = EVP_DecryptFinal_ex(ctx, plaintext.data() + len, &len);
394 EVP_CIPHER_CTX_free(ctx);
395
396 if (ret <= 0) return {}; // Authentication failed
397 plaintext_len += len;
398
399 return std::string(plaintext.begin(), plaintext.begin() + plaintext_len);
400 }
401#endif
402
403 // Legacy XOR format (with or without "xor:" prefix)
404 std::string hex_data = encrypted_data;
405 if (hex_data.substr(0, 4) == "xor:")
406 {
407 hex_data = hex_data.substr(4);
408 }
409
410 std::string decoded;
411 decoded.reserve(hex_data.size() / 2);
412 for (size_t i = 0; i + 1 < hex_data.size(); i += 2)
413 {
414 unsigned int byte = 0;
415 std::istringstream iss(hex_data.substr(i, 2));
416 iss >> std::hex >> byte;
417 decoded.push_back(static_cast<char>(byte));
418 }
419
420 std::string key = master_key_.empty() ? "default_key" : master_key_;
421 for (size_t i = 0; i < decoded.size(); ++i)
422 {
423 decoded[i] ^= key[i % key.size()];
424 }
425
426 return decoded;
427 }
428
429 // ─────────────────────────────────────────────
430 // encryption_manager
431 // ─────────────────────────────────────────────
432
433 std::string encryption_manager::encrypt_field_data(const std::string& data,
434 const std::string& field_name) const
435 {
436 std::lock_guard<std::mutex> lock(encryption_mutex_);
437
438 if (data.empty())
439 {
440 return {};
441 }
442
443 std::string key = derive_key(field_name);
444 if (key.empty())
445 {
446 return {};
447 }
448
449#ifdef DATABASE_HAS_OPENSSL
450 // AES-256-GCM field encryption with derived key
451 key.resize(32, '\0'); // Ensure 256-bit key
452
453 constexpr int iv_len = 12;
454 constexpr int tag_len = 16;
455
456 std::vector<uint8_t> iv(iv_len);
457 RAND_bytes(iv.data(), iv_len);
458
459 EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
460 if (!ctx) return {};
461
462 std::vector<uint8_t> ciphertext(data.size() + EVP_MAX_BLOCK_LENGTH);
463 std::vector<uint8_t> tag(tag_len);
464 int len = 0, ct_len = 0;
465
466 EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr);
467 EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, iv_len, nullptr);
468 EVP_EncryptInit_ex(ctx, nullptr, nullptr,
469 reinterpret_cast<const unsigned char*>(key.data()), iv.data());
470 EVP_EncryptUpdate(ctx, ciphertext.data(), &len,
471 reinterpret_cast<const unsigned char*>(data.data()),
472 static_cast<int>(data.size()));
473 ct_len = len;
474 EVP_EncryptFinal_ex(ctx, ciphertext.data() + len, &len);
475 ct_len += len;
476 EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, tag_len, tag.data());
477 EVP_CIPHER_CTX_free(ctx);
478
479 ciphertext.resize(ct_len);
480 return "aes:" + bytes_to_hex(iv) + ":" + bytes_to_hex(ciphertext) + ":" + bytes_to_hex(tag);
481#else
482 // XOR-based field encryption (placeholder)
483 std::string result = data;
484 for (size_t i = 0; i < result.size(); ++i)
485 {
486 result[i] ^= key[i % key.size()];
487 }
488
489 std::ostringstream oss;
490 for (unsigned char c : result)
491 {
492 oss << std::hex << std::setfill('0') << std::setw(2)
493 << static_cast<int>(c);
494 }
495 return "xor:" + oss.str();
496#endif
497 }
498
499 std::string encryption_manager::decrypt_field_data(const std::string& encrypted_data,
500 const std::string& field_name) const
501 {
502 std::lock_guard<std::mutex> lock(encryption_mutex_);
503
504 if (encrypted_data.empty())
505 {
506 return {};
507 }
508
509 std::string key = derive_key(field_name);
510 if (key.empty())
511 {
512 return {};
513 }
514
515#ifdef DATABASE_HAS_OPENSSL
516 // AES-256-GCM decryption
517 if (encrypted_data.substr(0, 4) == "aes:")
518 {
519 key.resize(32, '\0');
520
521 auto first = encrypted_data.find(':', 4);
522 auto second = encrypted_data.find(':', first + 1);
523 if (first == std::string::npos || second == std::string::npos) return {};
524
525 auto iv = hex_to_bytes(encrypted_data.substr(4, first - 4));
526 auto ciphertext = hex_to_bytes(encrypted_data.substr(first + 1, second - first - 1));
527 auto tag = hex_to_bytes(encrypted_data.substr(second + 1));
528
529 EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new();
530 if (!ctx) return {};
531
532 std::vector<uint8_t> plaintext(ciphertext.size() + EVP_MAX_BLOCK_LENGTH);
533 int len = 0, pt_len = 0;
534
535 EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), nullptr, nullptr, nullptr);
536 EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, static_cast<int>(iv.size()), nullptr);
537 EVP_DecryptInit_ex(ctx, nullptr, nullptr,
538 reinterpret_cast<const unsigned char*>(key.data()), iv.data());
539 EVP_DecryptUpdate(ctx, plaintext.data(), &len,
540 ciphertext.data(), static_cast<int>(ciphertext.size()));
541 pt_len = len;
542 EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, static_cast<int>(tag.size()),
543 const_cast<uint8_t*>(tag.data()));
544
545 int ret = EVP_DecryptFinal_ex(ctx, plaintext.data() + len, &len);
546 EVP_CIPHER_CTX_free(ctx);
547
548 if (ret <= 0) return {};
549 pt_len += len;
550 return std::string(plaintext.begin(), plaintext.begin() + pt_len);
551 }
552#endif
553
554 // Legacy XOR format
555 std::string hex_data = encrypted_data;
556 if (hex_data.substr(0, 4) == "xor:")
557 {
558 hex_data = hex_data.substr(4);
559 }
560
561 std::string decoded;
562 decoded.reserve(hex_data.size() / 2);
563 for (size_t i = 0; i + 1 < hex_data.size(); i += 2)
564 {
565 unsigned int byte = 0;
566 std::istringstream iss(hex_data.substr(i, 2));
567 iss >> std::hex >> byte;
568 decoded.push_back(static_cast<char>(byte));
569 }
570
571 for (size_t i = 0; i < decoded.size(); ++i)
572 {
573 decoded[i] ^= key[i % key.size()];
574 }
575
576 return decoded;
577 }
578
579 bool encryption_manager::generate_field_key(const std::string& field_name)
580 {
581 std::lock_guard<std::mutex> lock(encryption_mutex_);
582
583 // Generate a random key for the field
584 std::random_device rd;
585 std::mt19937 gen(rd());
586 std::uniform_int_distribution<> dis(0, 255);
587
588 std::string key(32, '\0');
589 for (auto& c : key)
590 {
591 c = static_cast<char>(dis(gen));
592 }
593
594 // Hex-encode the key for storage
595 std::ostringstream oss;
596 for (unsigned char c : key)
597 {
598 oss << std::hex << std::setfill('0') << std::setw(2)
599 << static_cast<int>(c);
600 }
601 field_keys_[field_name] = oss.str();
602 return true;
603 }
604
605 bool encryption_manager::rotate_field_key(const std::string& field_name)
606 {
607 std::lock_guard<std::mutex> lock(encryption_mutex_);
608
609 auto it = field_keys_.find(field_name);
610 if (it == field_keys_.end())
611 {
612 return false;
613 }
614
615 // Generate new key (in production, would re-encrypt existing data)
616 std::random_device rd;
617 std::mt19937 gen(rd());
618 std::uniform_int_distribution<> dis(0, 255);
619
620 std::string key(32, '\0');
621 for (auto& c : key)
622 {
623 c = static_cast<char>(dis(gen));
624 }
625
626 std::ostringstream oss;
627 for (unsigned char c : key)
628 {
629 oss << std::hex << std::setfill('0') << std::setw(2)
630 << static_cast<int>(c);
631 }
632 it->second = oss.str();
633 return true;
634 }
635
637 {
638 std::lock_guard<std::mutex> lock(encryption_mutex_);
639 master_key_ = key;
640 }
641
643 const std::string& column,
644 encryption_type type)
645 {
646 std::lock_guard<std::mutex> lock(encryption_mutex_);
647 std::string key = table + "." + column;
648 encrypted_columns_[key] = type;
649
650 // Auto-generate field key if not present
651 if (field_keys_.find(key) == field_keys_.end())
652 {
653 // Temporarily release lock to call generate_field_key
654 // Instead, inline the key generation
655 std::random_device rd;
656 std::mt19937 gen(rd());
657 std::uniform_int_distribution<> dis(0, 255);
658
659 std::string field_key(32, '\0');
660 for (auto& c : field_key)
661 {
662 c = static_cast<char>(dis(gen));
663 }
664
665 std::ostringstream oss;
666 for (unsigned char ch : field_key)
667 {
668 oss << std::hex << std::setfill('0') << std::setw(2)
669 << static_cast<int>(ch);
670 }
671 field_keys_[key] = oss.str();
672 }
673
674 return true;
675 }
676
677 bool encryption_manager::is_column_encrypted(const std::string& table,
678 const std::string& column) const
679 {
680 std::lock_guard<std::mutex> lock(encryption_mutex_);
681 std::string key = table + "." + column;
682 return encrypted_columns_.find(key) != encrypted_columns_.end();
683 }
684
685 std::string encryption_manager::derive_key(const std::string& field_name) const
686 {
687 // Check for field-specific key first
688 auto it = field_keys_.find(field_name);
689 if (it != field_keys_.end())
690 {
691 return it->second;
692 }
693
694 // Fall back to master key
695 if (!master_key_.empty())
696 {
697 return master_key_;
698 }
699
700 return {};
701 }
702
703 // ─────────────────────────────────────────────
704 // audit_logger
705 // ─────────────────────────────────────────────
706
707 audit_logger::audit_logger(const std::string& log_file_path)
708 : log_file_path_(log_file_path)
709 {
710 }
711
713 {
714 if (log_file_path_.empty())
715 {
716 return;
717 }
718
719 std::ofstream file(log_file_path_, std::ios::app);
720 if (!file.is_open())
721 {
722 return;
723 }
724
725 auto time_t = std::chrono::system_clock::to_time_t(entry.timestamp);
726 file << time_t << ","
727 << entry.user_id << ","
728 << entry.session_id << ","
729 << entry.operation << ","
730 << entry.table_name << ","
731 << entry.query_hash << ","
732 << (entry.success ? "true" : "false") << ","
733 << entry.error_message << ","
734 << entry.client_ip << ","
735 << entry.user_agent << "\n";
736 file.flush();
737 }
738
739 void audit_logger::log_database_access(const std::string& user_id,
740 const std::string& session_id,
741 const std::string& operation,
742 const std::string& table,
743 const std::string& query_hash,
744 bool success,
745 const std::string& error_message)
746 {
747 audit_log_entry entry;
748 entry.timestamp = std::chrono::system_clock::now();
749 entry.user_id = user_id;
750 entry.session_id = session_id;
751 entry.operation = operation;
752 entry.table_name = table;
753 entry.query_hash = query_hash;
754 entry.success = success;
755 entry.error_message = error_message;
756
757 std::lock_guard<std::mutex> lock(audit_mutex_);
758 audit_logs_.push_back(entry);
759 persist_entry(entry);
760 }
761
762 void audit_logger::log_authentication_event(const std::string& user_id,
763 const std::string& client_ip,
764 bool success,
765 const std::string& method)
766 {
767 audit_log_entry entry;
768 entry.timestamp = std::chrono::system_clock::now();
769 entry.user_id = user_id;
770 entry.client_ip = client_ip;
771 entry.operation = "authentication";
772 entry.success = success;
773 entry.error_message = success ? "" : "Authentication failed via " + method;
774
775 std::lock_guard<std::mutex> lock(audit_mutex_);
776 audit_logs_.push_back(entry);
777 persist_entry(entry);
778 }
779
780 void audit_logger::log_authorization_failure(const std::string& user_id,
781 const std::string& operation,
782 const std::string& table,
783 const std::string& reason)
784 {
785 audit_log_entry entry;
786 entry.timestamp = std::chrono::system_clock::now();
787 entry.user_id = user_id;
788 entry.operation = "authorization_failure:" + operation;
789 entry.table_name = table;
790 entry.success = false;
791 entry.error_message = reason;
792
793 std::lock_guard<std::mutex> lock(audit_mutex_);
794 audit_logs_.push_back(entry);
795 persist_entry(entry);
796 }
797
798 std::vector<audit_log_entry> audit_logger::get_audit_logs(
799 std::chrono::hours window) const
800 {
801 std::lock_guard<std::mutex> lock(audit_mutex_);
802
803 auto cutoff = std::chrono::system_clock::now() - window;
804 std::vector<audit_log_entry> result;
805
806 for (const auto& entry : audit_logs_)
807 {
808 if (entry.timestamp >= cutoff)
809 {
810 result.push_back(entry);
811 }
812 }
813
814 return result;
815 }
816
817 std::vector<audit_log_entry> audit_logger::get_user_audit_logs(
818 const std::string& user_id, std::chrono::hours window) const
819 {
820 std::lock_guard<std::mutex> lock(audit_mutex_);
821
822 auto cutoff = std::chrono::system_clock::now() - window;
823 std::vector<audit_log_entry> result;
824
825 for (const auto& entry : audit_logs_)
826 {
827 if (entry.timestamp >= cutoff && entry.user_id == user_id)
828 {
829 result.push_back(entry);
830 }
831 }
832
833 return result;
834 }
835
837 std::chrono::hours window) const
838 {
839 auto logs = get_audit_logs(window);
840
841 size_t total = logs.size();
842 size_t failures = 0;
843 size_t auth_events = 0;
844
845 for (const auto& entry : logs)
846 {
847 if (!entry.success)
848 {
849 ++failures;
850 }
851 if (entry.operation == "authentication")
852 {
853 ++auth_events;
854 }
855 }
856
857 std::ostringstream oss;
858 oss << "Security Report (last " << window.count() << " hours)\n"
859 << " Total events: " << total << "\n"
860 << " Failures: " << failures << "\n"
861 << " Auth events: " << auth_events << "\n"
862 << " Failure rate: "
863 << (total > 0 ? (100.0 * failures / total) : 0.0) << "%\n";
864
865 return oss.str();
866 }
867
869 std::chrono::hours window) const
870 {
871 auto logs = get_audit_logs(window);
872 std::vector<std::string> alerts;
873
874 // Detect users with high failure rates
875 std::unordered_map<std::string, size_t> user_failures;
876 for (const auto& entry : logs)
877 {
878 if (!entry.success)
879 {
880 ++user_failures[entry.user_id];
881 }
882 }
883
884 for (const auto& [user_id, count] : user_failures)
885 {
886 if (count >= 5)
887 {
888 alerts.push_back("User '" + user_id + "' has " +
889 std::to_string(count) +
890 " failed operations in the last " +
891 std::to_string(window.count()) + " hours");
892 }
893 }
894
895 return alerts;
896 }
897
898 void audit_logger::set_log_retention_period(std::chrono::hours retention)
899 {
900 std::lock_guard<std::mutex> lock(audit_mutex_);
901 retention_period_ = retention;
902 }
903
905 {
906 std::lock_guard<std::mutex> lock(audit_mutex_);
907
908 auto cutoff = std::chrono::system_clock::now() - retention_period_;
909 audit_logs_.erase(
910 std::remove_if(audit_logs_.begin(), audit_logs_.end(),
911 [&cutoff](const audit_log_entry& entry) {
912 return entry.timestamp < cutoff;
913 }),
914 audit_logs_.end());
915 }
916
917 bool audit_logger::export_logs_to_file(const std::string& filename) const
918 {
919 std::lock_guard<std::mutex> lock(audit_mutex_);
920
921 std::ofstream file(filename);
922 if (!file.is_open())
923 {
924 return false;
925 }
926
927 // Write CSV header
928 file << "timestamp,user_id,session_id,operation,table_name,"
929 "query_hash,success,error_message,client_ip,user_agent\n";
930
931 for (const auto& entry : audit_logs_)
932 {
933 auto time_t = std::chrono::system_clock::to_time_t(entry.timestamp);
934 file << time_t << ","
935 << entry.user_id << ","
936 << entry.session_id << ","
937 << entry.operation << ","
938 << entry.table_name << ","
939 << entry.query_hash << ","
940 << (entry.success ? "true" : "false") << ","
941 << entry.error_message << ","
942 << entry.client_ip << ","
943 << entry.user_agent << "\n";
944 }
945
946 return true;
947 }
948
949} // namespace database::security
void log_database_access(const std::string &user_id, const std::string &session_id, const std::string &operation, const std::string &table, const std::string &query_hash, bool success, const std::string &error_message="")
std::string generate_security_report(std::chrono::hours window) const
std::vector< audit_log_entry > get_user_audit_logs(const std::string &user_id, std::chrono::hours window) const
void set_log_retention_period(std::chrono::hours retention)
audit_logger()=default
Default constructor - used by database_context.
std::vector< audit_log_entry > audit_logs_
std::vector< std::string > detect_suspicious_activity(std::chrono::hours window) const
void persist_entry(const audit_log_entry &entry)
void log_authentication_event(const std::string &user_id, const std::string &client_ip, bool success, const std::string &method)
bool export_logs_to_file(const std::string &filename) const
void log_authorization_failure(const std::string &user_id, const std::string &operation, const std::string &table, const std::string &reason)
std::vector< audit_log_entry > get_audit_logs(std::chrono::hours window) const
std::optional< security_credentials > get_credentials(const std::string &connection_id) const
std::string encrypt_data(const std::string &data) const
void set_master_key(const std::string &key)
bool verify_password(const std::string &password, const std::string &hash) const
std::string hash_password(const std::string &password) const
bool remove_credentials(const std::string &connection_id)
std::unordered_map< std::string, std::string > encrypted_credentials_
bool store_credentials(const std::string &connection_id, const security_credentials &credentials)
std::string decrypt_data(const std::string &encrypted_data) const
std::unordered_map< std::string, std::string > field_keys_
bool configure_encrypted_column(const std::string &table, const std::string &column, encryption_type type)
bool generate_field_key(const std::string &field_name)
bool is_column_encrypted(const std::string &table, const std::string &column) const
bool rotate_field_key(const std::string &field_name)
std::unordered_map< std::string, encryption_type > encrypted_columns_
std::string derive_key(const std::string &field_name) const
void set_master_encryption_key(const std::string &key)
std::string decrypt_field_data(const std::string &encrypted_data, const std::string &field_name) const
std::string encrypt_field_data(const std::string &data, const std::string &field_name) const
authentication_method
Authentication methods supported.
encryption_type
Types of encryption supported.
Audit log entry for security events.
std::string operation
std::string table_name
std::string error_message
std::string user_agent
std::string user_id
std::string query_hash
std::string client_ip
std::chrono::system_clock::time_point timestamp
std::string session_id
bool success