@@ -27,14 +27,14 @@ namespace hmac_cpp {
2727// / Memory locking is best-effort and may fail without required privileges.
2828class secret_string {
2929public:
30- secret_string () : nonce_(), locked_(false ) {}
30+ secret_string () : nonce_(), tag_(), locked_(false ) {}
3131
32- explicit secret_string (const std::string& s) : nonce_(), locked_(false ) { set (s); }
33- explicit secret_string (const uint8_t * p, size_t n) : nonce_(), locked_(false ) { set (p, n); }
34- explicit secret_string (const secure_buffer<uint8_t >& s) : nonce_(), locked_(false ) { set (s); }
35- explicit secret_string (secure_buffer<uint8_t >&& s) : nonce_(), locked_(false ) { set (std::move (s)); }
32+ explicit secret_string (const std::string& s) : nonce_(), tag_(), locked_(false ) { set (s); }
33+ explicit secret_string (const uint8_t * p, size_t n) : nonce_(), tag_(), locked_(false ) { set (p, n); }
34+ explicit secret_string (const secure_buffer<uint8_t >& s) : nonce_(), tag_(), locked_(false ) { set (s); }
35+ explicit secret_string (secure_buffer<uint8_t >&& s) : nonce_(), tag_(), locked_(false ) { set (std::move (s)); }
3636
37- secret_string (secret_string&& other) noexcept { move_from (other); }
37+ secret_string (secret_string&& other) noexcept : ct_(), nonce_(), tag_(), locked_( false ) { move_from (other); }
3838 secret_string& operator =(secret_string&& other) noexcept {
3939 if (this != &other) { clear (); move_from (other); }
4040 return *this ;
@@ -53,8 +53,10 @@ class secret_string {
5353 locked_ = false ;
5454 }
5555 ct_.clear ();
56+ ct_.shrink_to_fit ();
5657 }
5758 secure_zero (nonce_.data (), nonce_.size ());
59+ secure_zero (tag_.data (), tag_.size ());
5860 }
5961
6062 bool empty () const noexcept { return ct_.empty (); }
@@ -79,10 +81,37 @@ class secret_string {
7981 ct_.resize (n);
8082 if (n) locked_ = lock_pages (ct_.data (), ct_.size ());
8183
82- xor_keystream_copy_inplace (ct_.data (), p, n, nonce_.data ());
84+ xor_keystream_copy (ct_.data (), p, n, nonce_.data ());
85+
86+ HmacContext c (hmac_cpp::TypeHash::SHA256 );
87+ auto & pk = process_key ();
88+ c.init (pk.data (), pk.size ());
89+ c.update (nonce_.data (), nonce_.size ());
90+ if (n) c.update (ct_.data (), ct_.size ());
91+ c.final (tag_.data (), tag_.size ());
8392 }
93+
94+ // Not thread-safe if called concurrently with set() or rotate_nonce().
95+ void with_plaintext (const std::function<void (const uint8_t *, size_t )>& fn) const {
96+ if (ct_.empty ()) {
97+ fn (nullptr , 0 );
98+ return ;
99+ }
100+
101+ uint8_t expected[32 ];
102+ {
103+ HmacContext ctx (hmac_cpp::TypeHash::SHA256 );
104+ auto & pk = process_key ();
105+ ctx.init (pk.data (), pk.size ());
106+ ctx.update (nonce_.data (), nonce_.size ());
107+ ctx.update (ct_.data (), ct_.size ());
108+ ctx.final (expected, sizeof expected);
109+ }
110+ if (!std::equal (tag_.begin (), tag_.end (), expected)) {
111+ secure_zero (expected, sizeof expected);
112+ throw std::runtime_error (" secret_string::with_plaintext: integrity check failed" );
113+ }
84114
85- bool with_plaintext (const std::function<void (const uint8_t *, size_t )>& fn) const {
86115 uint8_t subkey[32 ];
87116 {
88117 HmacContext ctx (hmac_cpp::TypeHash::SHA256 );
@@ -97,9 +126,10 @@ class secret_string {
97126 fn (tmp.data (), tmp.size ());
98127 secure_zero (tmp.data (), tmp.size ());
99128 secure_zero (subkey, sizeof subkey);
100- return true ;
129+ secure_zero (expected, sizeof expected) ;
101130 }
102131
132+ // Not thread-safe if called concurrently with set() or rotate_nonce().
103133 std::string reveal_copy () const {
104134 std::string out;
105135 out.resize (ct_.size ());
@@ -109,58 +139,71 @@ class secret_string {
109139 return out;
110140 }
111141
112- void rekey_runtime () {
142+ void rotate_nonce () {
113143 if (ct_.empty ()) return ;
114144
115- uint8_t oldk[32 ], newk[32 ];
116- auto & pk = process_key ();
145+ std::array<uint8_t ,12 > new_nonce{};
117146 {
118- HmacContext ctx (hmac_cpp::TypeHash::SHA256 );
119- ctx.init (pk.data (), pk.size ());
120- ctx.update (nonce_.data (), nonce_.size ());
121- ctx.final (oldk, sizeof oldk);
147+ auto rnd = hmac_cpp::random_bytes (new_nonce.size ());
148+ std::copy (rnd.begin (), rnd.end (), new_nonce.begin ());
122149 }
123150
151+ uint8_t oldk[32 ], newk[32 ];
152+ auto & pk = process_key ();
153+
124154 {
125- auto rnd = hmac_cpp::random_bytes (pk.size ());
126- std::copy (rnd.begin (), rnd.end (), pk.begin ());
155+ HmacContext c (hmac_cpp::TypeHash::SHA256 );
156+ c.init (pk.data (), pk.size ());
157+ c.update (nonce_.data (), nonce_.size ());
158+ c.final (oldk, sizeof oldk);
127159 }
128-
129160 {
130- HmacContext ctx (hmac_cpp::TypeHash::SHA256 );
131- ctx .init (pk.data (), pk.size ());
132- ctx .update (nonce_ .data (), nonce_ .size ());
133- ctx .final (newk, sizeof newk);
161+ HmacContext c (hmac_cpp::TypeHash::SHA256 );
162+ c .init (pk.data (), pk.size ());
163+ c .update (new_nonce .data (), new_nonce .size ());
164+ c .final (newk, sizeof newk);
134165 }
135166
136- uint8_t msg[16 ]; std::memcpy (msg, nonce_.data (), 12 );
137167 uint32_t ctr = 0 ;
138- uint8_t oldb[32 ], newb[32 ];
168+ uint8_t msg_old[16 ], msg_new[16 ], blk_old[32 ], blk_new[32 ];
169+ std::memcpy (msg_old, nonce_.data (), nonce_.size ());
170+ std::memcpy (msg_new, new_nonce.data (), new_nonce.size ());
139171
140- size_t pos = 0 ;
141- while (pos < ct_.size ()) {
142- be32 (msg + 12 , ctr++);
172+ for (size_t pos = 0 ; pos < ct_.size ();) {
173+ be32 (msg_old + 12 , ctr);
174+ be32 (msg_new + 12 , ctr);
175+ ++ctr;
143176
144177 HmacContext c1 (hmac_cpp::TypeHash::SHA256 );
145178 c1.init (oldk, sizeof oldk);
146- c1.update (msg , sizeof msg );
147- c1.final (oldb , sizeof oldb );
179+ c1.update (msg_old , sizeof msg_old );
180+ c1.final (blk_old , sizeof blk_old );
148181
149182 HmacContext c2 (hmac_cpp::TypeHash::SHA256 );
150183 c2.init (newk, sizeof newk);
151- c2.update (msg , sizeof msg );
152- c2.final (newb , sizeof newb );
184+ c2.update (msg_new , sizeof msg_new );
185+ c2.final (blk_new , sizeof blk_new );
153186
154- const size_t take = (ct_.size () - pos < sizeof oldb) ? (ct_.size () - pos) : sizeof oldb;
155- for (size_t i = 0 ; i < take; ++i) ct_[pos + i] ^= (oldb[i] ^ newb[i]);
187+ const size_t take = std::min (ct_.size () - pos, sizeof blk_old);
188+ for (size_t i = 0 ; i < take; ++i)
189+ ct_[pos + i] ^= (blk_old[i] ^ blk_new[i]);
156190 pos += take;
157191 }
158192
159193 secure_zero (oldk, sizeof oldk);
160194 secure_zero (newk, sizeof newk);
161- secure_zero (oldb, sizeof oldb);
162- secure_zero (newb, sizeof newb);
163- secure_zero (msg, sizeof msg);
195+ secure_zero (blk_old, sizeof blk_old);
196+ secure_zero (blk_new, sizeof blk_new);
197+ secure_zero (msg_old, sizeof msg_old);
198+ secure_zero (msg_new, sizeof msg_new);
199+
200+ nonce_ = new_nonce;
201+
202+ HmacContext c (hmac_cpp::TypeHash::SHA256 );
203+ c.init (pk.data (), pk.size ());
204+ c.update (nonce_.data (), nonce_.size ());
205+ if (!ct_.empty ()) c.update (ct_.data (), ct_.size ());
206+ c.final (tag_.data (), tag_.size ());
164207 }
165208
166209private:
@@ -221,7 +264,7 @@ class secret_string {
221264 secure_zero (subkey, sizeof subkey);
222265 }
223266
224- void xor_keystream_copy_inplace (uint8_t * out, const uint8_t * in, size_t len, const uint8_t * nonce12) const {
267+ void xor_keystream_copy (uint8_t * out, const uint8_t * in, size_t len, const uint8_t * nonce12) const {
225268 uint8_t subkey[32 ];
226269 {
227270 HmacContext ctx (hmac_cpp::TypeHash::SHA256 );
@@ -257,14 +300,17 @@ class secret_string {
257300 void move_from (secret_string& other) noexcept {
258301 ct_ = std::move (other.ct_ );
259302 nonce_ = other.nonce_ ;
303+ tag_ = other.tag_ ;
260304 locked_ = other.locked_ ;
261305 other.locked_ = false ;
262306 secure_zero (other.nonce_ .data (), other.nonce_ .size ());
307+ secure_zero (other.tag_ .data (), other.tag_ .size ());
263308 }
264309
265310private:
266311 std::vector<uint8_t > ct_;
267312 std::array<uint8_t ,12 > nonce_;
313+ std::array<uint8_t ,32 > tag_;
268314 bool locked_;
269315};
270316
0 commit comments