86 : config_(std::move(config))
93 secure_zero(kek_.data(), kek_.size());
95 for (
auto& [
id, key] : keys_)
96 secure_zero(key.data(), key.size());
106 const std::vector<uint8_t>& dek,
107 const std::string& key_id)
const override
109 std::lock_guard<std::mutex> lock(mu_);
110 auto master = load_key_internal(key_id);
111 if (!master)
return master.error();
113 std::array<uint8_t, 32> master_arr{};
114 std::memcpy(master_arr.data(), master->data(),
115 std::min(master->size(),
size_t(32)));
118 secure_zero(master_arr.data(), master_arr.size());
119 log_access(key_id,
"wrap");
125 const std::vector<uint8_t>& wrapped_dek,
126 const std::string& key_id)
const override
128 std::lock_guard<std::mutex> lock(mu_);
129 auto master = load_key_internal(key_id);
130 if (!master)
return master.error();
132 std::array<uint8_t, 32> master_arr{};
133 std::memcpy(master_arr.data(), master->data(),
134 std::min(master->size(),
size_t(32)));
137 secure_zero(master_arr.data(), master_arr.size());
138 log_access(key_id,
"unwrap");
146 std::lock_guard<std::mutex> lock(mu_);
147 std::array<uint8_t, 32> key{};
148 csprng_fill(key.data(), key.size());
149 std::vector<uint8_t> key_vec(key.begin(), key.end());
151 auto result = store_key(key_id, key_vec);
152 secure_zero(key.data(), key.size());
153 if (!result)
return result.error();
155 log_access(key_id,
"generate");
161 std::lock_guard<std::mutex> lock(mu_);
162 auto it = keys_.find(key_id);
163 if (it != keys_.end()) {
164 secure_zero(it->second.data(), it->second.size());
168 std::string path = key_file_path(key_id);
169 std::remove(path.c_str());
171 log_access(key_id,
"destroy");
176 [[nodiscard]]
bool has_key(
const std::string& key_id)
const {
177 std::lock_guard<std::mutex> lock(mu_);
178 if (keys_.find(key_id) != keys_.end())
return true;
179 std::ifstream ifs(key_file_path(key_id), std::ios::binary);
185 std::array<uint8_t, 32> kek_{};
186 mutable std::unordered_map<std::string, std::vector<uint8_t>> keys_;
187 mutable std::mutex mu_;
194 static std::array<uint8_t, 32> pbkdf2_sha256_32(
195 const uint8_t* password,
size_t password_len,
196 const uint8_t* salt,
size_t salt_len,
197 uint32_t iterations) {
199 std::vector<uint8_t> u1_input;
200 u1_input.reserve(salt_len + 4u);
201 u1_input.insert(u1_input.end(), salt, salt + salt_len);
203 u1_input.push_back(0x00u);
204 u1_input.push_back(0x00u);
205 u1_input.push_back(0x00u);
206 u1_input.push_back(0x01u);
209 password, password_len, u1_input.data(), u1_input.size());
212 volatile uint8_t* vp = u1_input.data();
213 for (
size_t i = 0; i < u1_input.size(); ++i) vp[i] = 0u;
216 std::array<uint8_t, 32> dk = u;
217 for (uint32_t j = 1u; j < iterations; ++j) {
219 for (
size_t k = 0; k < 32u; ++k) dk[k] ^= u[k];
223 volatile uint8_t* vpu = u.data();
224 for (
size_t i = 0; i < 32u; ++i) vpu[i] = 0u;
230 static constexpr uint8_t kek_salt[] =
"signet:local-keystore:kek:v1";
231 static constexpr uint8_t kek_info[] =
"signet:kek-derivation";
233 auto passphrase_bytes =
reinterpret_cast<const uint8_t*
>(config_.passphrase.data());
238 auto stretched = pbkdf2_sha256_32(
239 passphrase_bytes, config_.passphrase.size(),
240 kek_salt,
sizeof(kek_salt) - 1u,
241 config_.pbkdf2_iterations);
244 stretched.data(), stretched.size());
247 volatile uint8_t* vp = stretched.data();
248 for (
size_t i = 0; i < stretched.size(); ++i) vp[i] = 0u;
250 (void)
hkdf_expand(prk, kek_info,
sizeof(kek_info) - 1, kek_.data(), kek_.size());
253 static void secure_zero(
void* ptr,
size_t len) {
254 volatile auto* p =
static_cast<volatile uint8_t*
>(ptr);
255 while (len--) *p++ = 0;
258 static void csprng_fill(uint8_t* buf,
size_t len) {
259#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__OpenBSD__)
260 arc4random_buf(buf, len);
261#elif defined(__linux__)
267 static constexpr int kMaxRetries = 100;
270 auto got = getrandom(buf, len, 0);
272 if (errno == EINTR && ++retries < kMaxRetries)
continue;
278 len -=
static_cast<size_t>(got);
281 BCryptGenRandom(
nullptr, buf,
static_cast<ULONG
>(len), BCRYPT_USE_SYSTEM_PREFERRED_RNG);
285 std::string key_file_path(
const std::string& key_id)
const {
286 return config_.keystore_path +
"/keys/" + key_id +
".key";
290 [[nodiscard]] expected<std::vector<uint8_t>> load_key_internal(
const std::string& key_id)
const {
291 auto it = keys_.find(key_id);
292 if (it != keys_.end())
return it->second;
294 std::string path = key_file_path(key_id);
295 std::ifstream ifs(path, std::ios::binary);
300 std::vector<uint8_t> wrapped((std::istreambuf_iterator<char>(ifs)),
301 std::istreambuf_iterator<char>());
305 if (!plaintext)
return plaintext.error();
307 keys_[key_id] = *plaintext;
312 [[nodiscard]] expected<void> store_key(
const std::string& key_id,
313 const std::vector<uint8_t>& plaintext) {
315 if (!wrapped)
return wrapped.error();
317 std::string dir = config_.keystore_path +
"/keys";
319 (void)_mkdir(config_.keystore_path.c_str());
320 (void)_mkdir(dir.c_str());
322 (void)::mkdir(config_.keystore_path.c_str(), 0700);
323 (void)::mkdir(dir.c_str(), 0700);
326 std::string path = key_file_path(key_id);
327 std::ofstream ofs(path, std::ios::binary | std::ios::trunc);
331 ofs.write(
reinterpret_cast<const char*
>(wrapped->data()),
332 static_cast<std::streamsize
>(wrapped->size()));
336 ::chmod(path.c_str(), 0600);
339 keys_[key_id] = plaintext;
340 return expected<void>{};
344 void log_access(
const std::string& key_id,
const char* operation)
const {
345 std::string log_path = config_.keystore_path +
"/audit.log";
346 std::ofstream log_file(log_path, std::ios::app);
347 if (!log_file)
return;
349 auto now = std::chrono::system_clock::now();
350 auto epoch = std::chrono::duration_cast<std::chrono::seconds>(
351 now.time_since_epoch()).count();
353 log_file << epoch <<
" " << operation <<
" " << key_id <<
"\n";