Skip to content

Commit e552d31

Browse files
committed
Merge branch 'master' into wip/wal_sync_client
2 parents 4438400 + 73c68a4 commit e552d31

9 files changed

Lines changed: 245 additions & 14 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ else()
7373
message(FATAL_ERROR "unsupported RECOVERY_SORTER_KVSLIB value: ${RECOVERY_SORTER_KVSLIB_UPPERCASE}")
7474
endif()
7575
find_package(nlohmann_json 3.7.0 REQUIRED)
76+
find_package(OpenSSL REQUIRED)
7677
if (ENABLE_ALTIMETER)
7778
find_package(altimeter REQUIRED)
7879
find_package(fmt REQUIRED)

include/limestone/api/blob_pool.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ namespace limestone::api {
2323
/// @brief BLOB reference type.
2424
using blob_id_type = std::uint64_t;
2525

26+
/// @brief BLOB reference tag type.
27+
using blob_reference_tag_type = std::uint64_t;
28+
2629
/**
2730
* @brief represents a pool for provisional registration of BLOB data.
2831
*/
@@ -87,6 +90,22 @@ class blob_pool {
8790
* @throws limestone_blob_exception if an I/O error occurs during the operation
8891
*/
8992
[[nodiscard]] virtual blob_id_type duplicate_data(blob_id_type reference) = 0;
93+
94+
/**
95+
* @brief generates a BLOB reference tag for access control.
96+
* @param blob_id the BLOB reference
97+
* @param transaction_id the transaction ID
98+
* @return the generated BLOB reference tag
99+
* @throws limestone_blob_exception if an internal error occurs during tag generation.
100+
* Possible reasons include:
101+
* - Environment issues (e.g., cryptographic library not initialized or misconfigured)
102+
* - Resource exhaustion (e.g., out of memory)
103+
* - Other unexpected internal errors
104+
* @note No validation is performed for blob_id or transaction_id values; any value is accepted.
105+
*/
106+
[[nodiscard]] virtual blob_reference_tag_type generate_reference_tag(
107+
blob_id_type blob_id,
108+
std::uint64_t transaction_id) = 0;
90109
};
91110

92111
} // namespace limestone::api

src/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ target_link_libraries(${package_name}
8282
PRIVATE grpc++
8383
PRIVATE grpc
8484
PRIVATE protobuf
85+
PRIVATE OpenSSL::SSL
86+
PRIVATE OpenSSL::Crypto
8587
)
8688

8789
if (ENABLE_ALTIMETER)
@@ -115,6 +117,8 @@ target_link_libraries(limestone-impl
115117
INTERFACE protobuf
116118
INTERFACE gpr
117119
INTERFACE absl_synchronization
120+
INTERFACE OpenSSL::SSL
121+
INTERFACE OpenSSL::Crypto
118122
)
119123

120124
# utils

src/limestone/blob_pool_impl.cpp

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,46 @@
1717
#include "blob_pool_impl.h"
1818
#include "limestone_exception_helper.h"
1919
#include "limestone/api/datastore.h"
20+
#include "datastore_impl.h"
2021
#include <filesystem>
2122
#include <boost/filesystem.hpp>
2223
#include <iostream>
2324
#include <fstream>
2425
#include <cstdio>
2526
#include <memory>
27+
#include <cstring>
28+
29+
#include <openssl/hmac.h>
30+
#include <openssl/evp.h>
31+
#include <openssl/err.h>
2632

2733

2834

2935
namespace limestone::internal {
3036

37+
38+
void blob_pool_impl::handle_hmac_result(unsigned char const* result) {
39+
if (result == nullptr) {
40+
// Retrieve all OpenSSL error codes and error strings
41+
std::string msg = "Failed to calculate reference tag: ";
42+
// NOLINTNEXTLINE(google-runtime-int) : OpenSSL API requires unsigned long
43+
unsigned long openssl_err = 0;
44+
bool has_error = false;
45+
while ((openssl_err = ERR_get_error()) != 0) {
46+
has_error = true;
47+
std::array<char, 256> err_msg_buf{};
48+
ERR_error_string_n(openssl_err,
49+
err_msg_buf.data(),
50+
err_msg_buf.size());
51+
msg += "[" + std::to_string(openssl_err) + ": " + err_msg_buf.data() + "] ";
52+
}
53+
if (! has_error) {
54+
msg += "No OpenSSL error code available.";
55+
}
56+
LOG_AND_THROW_BLOB_EXCEPTION_NO_ERRNO(msg);
57+
}
58+
}
59+
3160
blob_pool_impl::blob_pool_impl(std::function<blob_id_type()> id_generator,
3261
limestone::internal::blob_file_resolver& resolver,
3362
limestone::api::datastore& datastore)
@@ -312,4 +341,40 @@ blob_id_type blob_pool_impl::register_data(std::string_view data) {
312341
return id;
313342
}
314343

344+
blob_reference_tag_type blob_pool_impl::generate_reference_tag(
345+
blob_id_type blob_id,
346+
std::uint64_t transaction_id) {
347+
348+
// Prepare input data: concatenate blob_id and transaction_id using portable approach
349+
std::array<unsigned char, sizeof(blob_id_type) + sizeof(std::uint64_t)> input_bytes{};
350+
std::memcpy(input_bytes.data(), &blob_id, sizeof(blob_id_type));
351+
std::memcpy(input_bytes.data() + sizeof(blob_id_type), &transaction_id, sizeof(std::uint64_t));
352+
353+
// Get the secret key from datastore_impl
354+
const auto& secret_key = datastore_.get_impl()->get_hmac_secret_key();
355+
356+
// Clear OpenSSL error queue to avoid noise from previous API calls
357+
ERR_clear_error();
358+
359+
// Calculate HMAC-SHA256
360+
std::array<unsigned char, EVP_MAX_MD_SIZE> md{};
361+
unsigned int md_len = 0;
362+
363+
unsigned char* result = HMAC(EVP_sha256(),
364+
secret_key.data(),
365+
static_cast<int>(secret_key.size()),
366+
input_bytes.data(),
367+
input_bytes.size(),
368+
md.data(),
369+
&md_len);
370+
371+
this->handle_hmac_result(result);
372+
373+
// Use the first 8 bytes of the HMAC result as the tag
374+
blob_reference_tag_type tag = 0;
375+
std::memcpy(&tag, md.data(), sizeof(blob_reference_tag_type));
376+
377+
return tag;
378+
}
379+
315380
} // namespace limestone::internal

src/limestone/blob_pool_impl.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ class blob_pool_impl : public blob_pool {
3333

3434
[[nodiscard]] blob_id_type duplicate_data(blob_id_type reference) override;
3535

36+
[[nodiscard]] blob_reference_tag_type generate_reference_tag(
37+
blob_id_type blob_id,
38+
std::uint64_t transaction_id) override;
39+
3640
protected:
3741
// These protected fields and methods include:
3842
// - Test-specific methods
@@ -94,6 +98,13 @@ class blob_pool_impl : public blob_pool {
9498
std::lock_guard<std::mutex> lock(mutex_);
9599
return blob_ids_;
96100
}
101+
102+
/**
103+
* @brief Checks HMAC result and throws exception if failed.
104+
* @param result HMAC result pointer (nullptr if failed)
105+
*/
106+
void handle_hmac_result(unsigned char const* result);
107+
97108
private:
98109
/**
99110
* @brief Generates a unique ID for a BLOB.

src/limestone/datastore_impl.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717

1818
#include <cstdlib>
1919
#include <iostream>
20+
#include <cstring>
21+
#include <stdexcept>
22+
#include <openssl/hmac.h>
23+
#include <openssl/rand.h>
24+
#include <openssl/evp.h>
2025

2126
#include "blob_file_resolver.h"
2227
#include "blob_file_scanner.h"
@@ -60,6 +65,9 @@ datastore_impl::datastore_impl(datastore& ds)
6065
replica_exists_.store(has_replica, std::memory_order_release);
6166
LOG_LP(INFO) << "Replica " << (has_replica ? "enabled" : "disabled")
6267
<< "; endpoint valid: " << replication_endpoint_.is_valid();
68+
69+
// Generate HMAC secret key for BLOB reference tag generation
70+
generate_hmac_secret_key();
6371
}
6472

6573
// Default destructor.
@@ -351,4 +359,17 @@ std::set<boost::filesystem::path> datastore_impl::get_files() const {
351359
return datastore_.get_files();
352360
}
353361

362+
void datastore_impl::generate_hmac_secret_key() {
363+
// Generate 16 random bytes using OpenSSL RAND_bytes()
364+
// TODO: Future improvement - throw exception instead of abort when public API allows it
365+
if (RAND_bytes(hmac_secret_key_.data(), static_cast<int>(hmac_secret_key_.size())) != 1) {
366+
LOG_LP(ERROR) << "Failed to generate random bytes for BLOB access control secret key";
367+
std::abort(); // Current: abort due to noexcept constraint in public API
368+
}
369+
}
370+
371+
const std::array<std::uint8_t, 16>& datastore_impl::get_hmac_secret_key() const noexcept {
372+
return hmac_secret_key_;
373+
}
374+
354375
} // namespace limestone::api

src/limestone/datastore_impl.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@
1616
#pragma once
1717

1818
#include <limestone/api/datastore.h>
19+
#include <limestone/api/blob_pool.h>
1920

2021
#include <atomic>
2122
#include <memory>
2223
#include <optional>
24+
#include <array>
2325

2426
#include "manifest.h"
2527
#include "replication/replica_connector.h"
@@ -131,6 +133,11 @@ class datastore_impl {
131133
* @return A set of file paths.
132134
*/
133135
[[nodiscard]] std::set<boost::filesystem::path> get_files() const;
136+
/**
137+
* @brief gets the HMAC secret key for BLOB reference tag generation.
138+
* @return reference to the HMAC secret key
139+
*/
140+
[[nodiscard]] const std::array<std::uint8_t, 16>& get_hmac_secret_key() const noexcept;
134141

135142
private:
136143
datastore& datastore_;
@@ -158,6 +165,14 @@ class datastore_impl {
158165

159166
// Durable epoch ID at boot time
160167
std::atomic<epoch_id_type> boot_durable_epoch_id_{0};
168+
169+
// HMAC secret key for BLOB reference tag generation (16 bytes)
170+
std::array<std::uint8_t, 16> hmac_secret_key_{};
171+
172+
/**
173+
* @brief generates HMAC secret key for BLOB reference tag generation.
174+
*/
175+
void generate_hmac_secret_key();
161176
};
162177

163178
} // namespace limestone::api

src/limestone/datastore_restore.cpp

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -139,18 +139,24 @@ status copy_backup_files(const boost::filesystem::path& from_dir, const std::vec
139139
}
140140
try {
141141
if (!resolver.is_blob_file(src)) {
142-
// overwrite if destination exists
143-
boost::filesystem::copy_file(src, location / dst, boost::filesystem::copy_options::overwrite_existing);
142+
std::filesystem::copy_file(
143+
std::filesystem::path{src.string()},
144+
std::filesystem::path{(location / dst).string()},
145+
std::filesystem::copy_options::overwrite_existing
146+
);
144147
} else {
145148
boost::filesystem::path full_dst = resolver.resolve_path(resolver.extract_blob_id(src));
146149
boost::filesystem::path dst_dir = full_dst.parent_path();
147150
if (!boost::filesystem::exists(dst_dir)) {
148151
boost::filesystem::create_directories(dst_dir);
149152
}
150-
// overwrite blob file if destination exists
151-
boost::filesystem::copy_file(src, full_dst, boost::filesystem::copy_options::overwrite_existing);
153+
std::filesystem::copy_file(
154+
std::filesystem::path{src.string()},
155+
std::filesystem::path{full_dst.string()},
156+
std::filesystem::copy_options::overwrite_existing
157+
);
152158
}
153-
} catch (boost::filesystem::filesystem_error& ex) {
159+
} catch (std::filesystem::filesystem_error& ex) {
154160
LOG_LP(ERROR) << ex.what() << " file = " << src.string();
155161
return status::err_permission_error;
156162
}
@@ -209,18 +215,24 @@ status datastore::restore(std::string_view from, bool keep_backup, bool purge_de
209215
}
210216

211217
if (!resolver.is_blob_file(p)) {
212-
// overwrite destination file if exists
213-
boost::filesystem::copy_file(p, location_ / p.filename(), boost::filesystem::copy_options::overwrite_existing);
218+
std::filesystem::copy_file(
219+
std::filesystem::path{p.string()},
220+
std::filesystem::path{(location_ / p.filename()).string()},
221+
std::filesystem::copy_options::overwrite_existing
222+
);
214223
} else {
215224
boost::filesystem::path full_dst = resolver.resolve_path(resolver.extract_blob_id(p));
216225
boost::filesystem::path dst_dir = full_dst.parent_path();
217226
if (!boost::filesystem::exists(dst_dir)) {
218227
boost::filesystem::create_directories(dst_dir);
219228
}
220-
// overwrite blob file if exists
221-
boost::filesystem::copy_file(p, full_dst, boost::filesystem::copy_options::overwrite_existing);
229+
std::filesystem::copy_file(
230+
std::filesystem::path{p.string()},
231+
std::filesystem::path{full_dst.string()},
232+
std::filesystem::copy_options::overwrite_existing
233+
);
222234
}
223-
} catch (boost::filesystem::filesystem_error& ex) {
235+
} catch (std::filesystem::filesystem_error& ex) {
224236
LOG_LP(ERROR) << ex.what() << " file = " << p.string();
225237
return status::err_permission_error;
226238
}

0 commit comments

Comments
 (0)