Botan 3.13.0
Crypto and TLS for C&
stateful_key_index_registry.cpp
Go to the documentation of this file.
1/*
2 * (C) 2016 Matthias Gierlings
3 * 2026 Jack Lloyd
4 *
5 * Botan is released under the Simplified BSD License (see license.txt)
6 */
7
8#include <botan/internal/stateful_key_index_registry.h>
9
10#include <botan/assert.h>
11#include <botan/exceptn.h>
12#include <botan/hash.h>
13#include <botan/internal/os_utils.h>
14#include <algorithm>
15
16namespace Botan {
17
22
23Stateful_Key_Index_Registry::Stateful_Key_Index_Registry() : m_process_id(OS::get_process_id()) {}
24
26
28 std::span<const uint8_t> algo_params,
29 uint64_t max_operations,
30 std::span<const uint8_t> key_material_1,
31 std::span<const uint8_t> key_material_2) :
32 m_process_id(OS::get_process_id()), m_max_operations(max_operations) {
33 auto hash = HashFunction::create_or_throw("SHA-256");
34
35 hash->update("Botan Stateful_Key_Index_Registry KeyID");
36 hash->update_be(static_cast<uint64_t>(algo_name.size()));
37 hash->update(algo_name);
38 hash->update_be(static_cast<uint64_t>(algo_params.size()));
39 hash->update(algo_params);
40 hash->update_be(static_cast<uint64_t>(key_material_1.size()));
41 hash->update(key_material_1);
42 hash->update_be(static_cast<uint64_t>(key_material_2.size()));
43 hash->update(key_material_2);
44
45 BOTAN_ASSERT_NOMSG(hash->output_length() == m_val.size());
46
47 hash->final(m_val);
48}
49
50// Lock must be held while this function is called
51Stateful_Key_Index_Registry::RegistryMap::iterator Stateful_Key_Index_Registry::lookup(const KeyId& key_id) {
52 if(this->fork_detected(key_id)) {
53 throw Invalid_State("Stateful key index registry cannot be used after fork");
54 }
55
56 auto [i, inserted] = m_registry.emplace(key_id, 0);
57
58 if(!inserted && i->first.max_operations() != key_id.max_operations()) {
59 throw Internal_Error("Stateful key was already registered with a different maximum operation count");
60 }
61
62 return i;
63}
64
65/*
66* Fork detection for the process-wide one-time signature counters.
67*
68* Reusing a stateful signature leaf index is catastrophic (signing two messages
69* under the same index compromises the key), and after fork() both processes
70* hold copy-on-write duplicates of the counters. The registry fails closed
71* rather than risk index reuse.
72*
73* There are two PIDs stored, one in the registry and one in each KeyId, because
74* there are two possible cases where reuse might occur after a fork:
75*
76* - If the registry was used before the fork, then its stored PID will
77* not match the current PID, in which case the latch closes.
78*
79* - KeyId's PID catches the rarer case where a key was created prior to the
80* fork, but not used. In that case the registry has not yet been created in
81* either process, and it would be possible to use the same key (with the same
82* starting-from-0 index) in both processes.
83*
84* Fork detection is a one way latch, and intentionally coarse. Once any fork is
85* seen, the whole registry is disabled for the rest of this process, including
86* for keys that were freshly loaded in the child. This could be loosened
87* (allowing newly loaded keys to be used in the child process) but the
88* combination of stateful signature schemes plus fork() is a hazardous scenario
89* and seems best to prohibit it entirely.
90*
91* lookup() runs this on every access, so read-only queries (current_index,
92* remaining_operations, and to_bytes which serializes the current index) also
93* throw in a forked child rather than returning a possibly-unsafe value.
94*/
95bool Stateful_Key_Index_Registry::fork_detected(const KeyId& key_id) {
96 if(m_fork_detected) {
97 return true;
98 }
99
100 const uint32_t current_process_id = OS::get_process_id();
101
102 /*
103 We assume OS::get_process_id returns 0 only if processes are not a thing on
104 this system, in which case there is also no fork syscall
105 */
106 if(current_process_id == 0) {
107 return false;
108 }
109
110 /*
111 * If either the registry PID or the KeyId PID do not match the current process,
112 * assume a fork occurred and latch closed.
113 */
114 if((m_process_id != 0 && m_process_id != current_process_id) ||
115 (key_id.m_process_id != 0 && key_id.m_process_id != current_process_id)) {
116 m_fork_detected = true;
117 }
118
119 return m_fork_detected;
120}
121
123 const lock_guard_type<mutex_type> lock(m_mutex);
124 auto idx = this->lookup(key_id);
125 return idx->second;
126}
127
128std::optional<uint64_t> Stateful_Key_Index_Registry::reserve_next_index(const KeyId& key_id) {
129 const lock_guard_type<mutex_type> lock(m_mutex);
130 auto idx = this->lookup(key_id);
131 const uint64_t cur = idx->second;
132 if(cur >= key_id.max_operations()) {
133 return std::nullopt;
134 }
135 idx->second = cur + 1;
136 return cur;
137}
138
140 BOTAN_ARG_CHECK(min <= key_id.max_operations(), "Index lower bound exceeds maximum operation count");
141 const lock_guard_type<mutex_type> lock(m_mutex);
142 auto idx = this->lookup(key_id);
143 idx->second = std::max(idx->second, min);
144}
145
147 const lock_guard_type<mutex_type> lock(m_mutex);
148 const uint64_t idx = this->lookup(key_id)->second;
149 const uint64_t max = key_id.max_operations();
150
151 if(idx >= max) {
152 return 0;
153 } else {
154 return max - idx;
155 }
156}
157
158} // namespace Botan
#define BOTAN_ASSERT_NOMSG(expr)
Definition assert.h:75
#define BOTAN_ARG_CHECK(expr, msg)
Definition assert.h:33
static std::unique_ptr< HashFunction > create_or_throw(std::string_view algo_spec, std::string_view provider="")
Definition hash.cpp:308
uint64_t remaining_operations(const KeyId &key_id)
std::optional< uint64_t > reserve_next_index(const KeyId &key_id)
void set_index_lower_bound(const KeyId &key_id, uint64_t min)
static Stateful_Key_Index_Registry & global()
Stateful_Key_Index_Registry(const Stateful_Key_Index_Registry &)=delete
uint32_t BOTAN_TEST_API get_process_id()
Definition os_utils.cpp:77
secure_vector< T > lock(const std::vector< T > &in)
Definition secmem.h:145
lock_guard< T > lock_guard_type
Definition mutex.h:58