OpenVPN 3 Core Library
Loading...
Searching...
No Matches
data_epoch.cpp
Go to the documentation of this file.
1// OpenVPN -- An application to securely tunnel IP networks
2// over a single port, with support for SSL/TLS-based
3// session authentication and key exchange,
4// packet encryption, packet authentication, and
5// packet compression.
6//
7// Copyright (C) 2012- OpenVPN Inc.
8//
9// SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-only WITH openvpn3-openssl-exception
10//
11
12#include "data_epoch.hpp"
13
14#include <cstdint>
15
20
21void openvpn::ovpn_hkdf_expand(const uint8_t *secret,
22 const uint8_t *info,
23 int info_len,
24 uint8_t *out,
25 int out_len)
26{
27 static constexpr int digest_size = 32;
29
30 auto hmac = digest_factory->new_hmac(openvpn::CryptoAlgs::SHA256, secret, 32);
31
32 /* T(0) = empty string */
33 uint8_t t_prev[digest_size];
34 int t_prev_len = 0;
35
36 for (uint8_t block = 1; (block - 1) * digest_size < out_len; block++)
37 {
38 hmac->reset();
39
40 /* calculate T(block) */
41 hmac->update(t_prev, t_prev_len);
42 hmac->update(info, info_len);
43 hmac->update(&block, 1);
44 hmac->final(t_prev);
45
46 t_prev_len = digest_size;
47
48 /* Copy a full hmac output or remaining bytes */
49 int out_offset = (block - 1) * digest_size;
50 int copylen = std::min(digest_size, out_len - out_offset);
51
52 std::memcpy(out + out_offset, t_prev, copylen);
53 }
54}
55
56void openvpn::ovpn_expand_label(const uint8_t *secret, size_t secret_len, const uint8_t *label, size_t label_len, const uint8_t *context, size_t context_len, uint8_t *out, size_t out_len)
57{
59
60 if (secret_len != 32)
61 {
62 /* Our current implementation is not a general purpose one
63 * and assumes that the secret size matches the size of the
64 * hash (SHA256) key */
65 throw std::runtime_error("hkdf secret length mismatch");
66 }
67
68 /* 2 byte for the outlen encoded as uint16, 5 bytes for "ovpn ",
69 * 1 byte for label length, 1 byte for context length */
70 size_t prefix_len = 5;
71 size_t hkdf_label_len = 2 + prefix_len + 1 + label_len + 1 + context_len;
72
73 if (hkdf_label_len >= UINT16_MAX)
74 {
75 throw std::runtime_error("HKDF input parameters are too large");
76 }
77
78 openvpn::BufferAllocated hkdf_label{hkdf_label_len, 0};
79
80 const std::uint16_t net_out_len = htons(static_cast<std::uint16_t>(out_len));
81 hkdf_label.write((const unsigned char *)&net_out_len, sizeof(net_out_len));
82
83 const std::uint8_t label_len_net = static_cast<std::uint8_t>(label_len + prefix_len);
84 hkdf_label.write(&label_len_net, 1);
85 hkdf_label.write("ovpn ", prefix_len);
86 hkdf_label.write(label, label_len);
87 const std::uint8_t context_len_net = static_cast<std::uint8_t>(context_len);
88 if (context_len > 0)
89 {
90 hkdf_label.write(context, context_len);
91 }
92 hkdf_label.write(&context_len_net, 1);
93
94 if (hkdf_label.length() != hkdf_label_len)
95 {
96 throw std::runtime_error("hkdf label length mismatch");
97 }
98
99 ovpn_hkdf_expand(secret, hkdf_label.c_data(), static_cast<int>(hkdf_label.length()), out, static_cast<uint16_t>(out_len));
100}
101
102
103
105{
106 const uint8_t epoch_update_label[] = "datakey upd";
107
108 /* E_N+1 = OVPN-Expand-Label(E_N, "datakey upd", "", 32) */
109
110 decltype(keydata) new_keydata{};
111
112 ovpn_expand_label(keydata.data(), keydata.size(), epoch_update_label, 11, nullptr, 0, new_keydata.data(), new_keydata.size());
113
114 epoch++;
115 keydata = new_keydata;
116}
117
118std::pair<openvpn::StaticKey, openvpn::StaticKey> openvpn::EpochKey::data_key(openvpn::CryptoAlgs::Type cipher)
119{
120
123
124 /* Generate data key from epoch key:
125 * K_i = OVPN-Expand-Label(E_i, "data_key", "", key_size)
126 * implicit_iv = OVPN-Expand-Label(E_i, "data_iv", "", implicit_iv_len)
127 */
128
129 const uint8_t epoch_key_label[] = "data_key";
130 const uint8_t epoch_iv_label[] = "data_iv";
131
132 ovpn_expand_label(keydata.data(), keydata.size(), epoch_key_label, 8, nullptr, 0, data_key.data(), data_key.size());
133
134 ovpn_expand_label(keydata.data(), keydata.size(), epoch_iv_label, 7, nullptr, 0, data_iv.data(), data_iv.size());
135
136 return {data_key, data_iv};
137}
138
140{
141 auto [key, iv] = data_key(cipher);
142
144 throw epoch_key_exception("IV size mismatch. Expected IV size to be 12");
145
147
148 ret.epoch = epoch;
149 ret.cipher.init(libctx, cipher, key.data(), numeric_cast<unsigned, size_t>(key.size()), mode);
150 std::memcpy(ret.implicit_iv.data(), iv.data(), iv.size());
151
152 return ret;
153}
154
156 : epoch(1)
157{
158 if (key.size() < keydata.size())
159 throw epoch_key_exception("Secret key too short to create epoch key");
160
161 std::memcpy(keydata.data(), key.data(), keydata.size());
162}
163
164
166{
167 /* We want the number of receive keys starting with the currently used
168 * keys. */
169 uint16_t current_epoch_recv = decrypt_ctx.epoch;
170
171 if (current_epoch_recv == 0)
172 throw epoch_key_exception("Current receive key not initialised");
173
174 /* Either we have not generated any future keys yet or the last
175 * index is the same as our current epoch key */
176 if (future_keys.size() > 0 && future_keys.back().epoch != receive.epoch)
177 throw epoch_key_exception("Epoch key generation and future keys mismatch detected");
178
179 /* free the keys that are not used anymore */
180 for (auto it = future_keys.begin(); it != future_keys.end();)
181 {
182 /* Key is in the past */
183 if (it->epoch <= current_epoch_recv)
184 {
185 it = future_keys.erase(it);
186 }
187 else
188 {
189 it++;
190 }
191 }
192
193 /* regenerate the array elements at the end */
194 while (future_keys.size() < future_keys_count)
195 {
196 receive.iterate();
197
198 auto key_ctx = receive.key_context(libctx, cipher, openvpn::SSLLib::CryptoAPI::CipherContextAEAD::DECRYPT);
199
200 PacketIDDataReceive pid_recv;
201 pid_recv.init("Epoch receive packet ID", receive.epoch, true);
202
203 /* Avoid using emplace_back and use push_back instead as emplace_back triggers an internal error in
204 * older GCC versions used by RHEL8 and Ubuntu 20.04 */
205 auto ctx = EpochDataChannelDecryptContext{std::move(key_ctx), std::move(pid_recv)};
206 future_keys.push_back(std::move(ctx));
207 }
208}
209openvpn::DataChannelEpoch::DataChannelEpoch(decltype(cipher) cipher, openvpn::StaticKey e1send, openvpn::StaticKey e1recv, SSLLib::Ctx libctx, uint16_t future_key_count)
210 : cipher(cipher), libctx(libctx), future_keys_count(future_key_count), send(std::move(e1send)), receive(std::move(e1recv))
211{
212
213 auto key_ctx = EpochDataChannelCryptoContext{receive.key_context(libctx, cipher, openvpn::SSLLib::CryptoAPI::CipherContextAEAD::DECRYPT)};
214 decrypt_ctx = EpochDataChannelDecryptContext{std::move(key_ctx)};
215
218}
219
221{
222 if (send.epoch >= UINT16_MAX)
223 throw epoch_key_exception("Send epoch at limit");
224
225 send.iterate();
226 generate_encrypt_ctx();
227}
228
230{
231 auto key_ctx = send.key_context(libctx, cipher, openvpn::SSLLib::CryptoAPI::CipherContextAEAD::ENCRYPT);
232 encrypt_ctx = EpochDataChannelEncryptContext{std::move(key_ctx), PacketIDDataSend{true, send.epoch}};
233}
234
235void openvpn::DataChannelEpoch::replace_update_recv_key(std::uint16_t new_epoch, const SessionStats::Ptr &stats_arg)
236{
237 if (new_epoch <= decrypt_ctx.epoch)
238 {
239 /* the new epoch is not higher than the epoch of the current decryption key, nothing to do */
240 return;
241 }
242
243 auto is_epoch =
244 [new_epoch](const EpochDataChannelCryptoContext &ctx)
245 {
246 return ctx.epoch == new_epoch;
247 };
248
249 /* Find the key of the new epoch in future keys */
250 auto fki = std::find_if(future_keys.begin(), future_keys.end(), is_epoch);
251
252 /* we should only ever be called when we successfully decrypted/authenticated
253 * a packet from a peer, ie the epoch recv key *MUST* be in that
254 * array */
255 if (fki == future_keys.end())
256 throw epoch_key_exception("Updating to new epoch receive key that is not a valid candidate");
257
258 /* Check if the new recv key epoch is higher than the send key epoch. If
259 * yes we will replace the send key as well */
260 if (send.epoch < new_epoch)
261 {
262 /* Update the epoch_key for send to match the current key being used.
263 * This is a bit of extra work but since we are a maximum of 16
264 * keys behind, a maximum 16 HMAC invocations are a small price to
265 * pay for a simple implementation */
266 while (send.epoch < new_epoch)
267 {
268 send.iterate();
269 }
270 generate_encrypt_ctx();
271 }
272
273 /* Replace receive key */
274 retiring_decrypt_ctx = std::move(decrypt_ctx);
275
276 decrypt_ctx = std::move(*fki);
277 // Explicitly invalidate the old context
278 *fki = {};
279
280 /* Generate new future keys */
281 generate_future_receive_keys();
282}
283
286{
287 /* Current decrypt key is the most likely one */
288 if (decrypt_ctx.epoch == epoch)
289 {
290 return &decrypt_ctx;
291 }
292 else if (retiring_decrypt_ctx.epoch > 0 && retiring_decrypt_ctx.epoch == epoch)
293 {
294 return &retiring_decrypt_ctx;
295 }
296 else if (epoch > decrypt_ctx.epoch
297 && epoch <= decrypt_ctx.epoch + future_keys_count)
298 {
299 /* If we have reached the edge of the valid keys we do not return
300 * the key anymore since regenerating the new keys would move us
301 * over the window of valid keys and would need all kind of
302 * special casing, so we stop returning the key in this case */
303 if (epoch > (UINT16_MAX - future_keys_count - 1))
304 {
305 return nullptr;
306 }
307 else
308 {
309 /* Key in the range of future keys */
310 int index = epoch - (decrypt_ctx.epoch + 1);
311 return &future_keys.at(index);
312 }
313 }
314 else
315 {
316 return nullptr;
317 }
318}
319
320void openvpn::EpochDataChannelCryptoContext::calculate_iv(uint8_t *packet_id, std::array<uint8_t, IV_SIZE> &iv_dest)
321{
322 /* Calculate the IV with XOR */
323 for (std::size_t i = 0; i < 8; i++)
324 {
325 iv_dest[i] = packet_id[i] ^ implicit_iv[i];
326 }
327 /* copy the remaining 4 bytes directly from the implicit IV */
328 std::memcpy(iv_dest.data() + 8, implicit_iv.data() + 8, IV_SIZE - 8);
329}
330
332{
333 if (send.epoch == UINT16_MAX)
334 {
335 /* limit of epoch keys reached, cannot move to a newer key anymore, pid writing will throw an error instead */
336 return;
337 }
338 if (encrypt_ctx.cipher.get_usage_limit().usage_limit_reached() || encrypt_ctx.pid.at_limit())
339 {
340 iterate_send_key();
341 }
342}
EpochDataChannelDecryptContext * lookup_decrypt_key(uint16_t epoch)
EpochDataChannelDecryptContext decrypt_ctx
void replace_update_recv_key(std::uint16_t new_epoch, const SessionStats::Ptr &stats_arg)
openvpn::CryptoAlgs::Type cipher
virtual HMACInstance::Ptr new_hmac(const CryptoAlgs::Type digest_type, const unsigned char *key, const size_t key_size)=0
EpochDataChannelCryptoContext key_context(openvpn::SSLLib::Ctx libctx, openvpn::CryptoAlgs::Type cipher, int mode)
std::uint16_t epoch
std::array< uint8_t, SECRET_SIZE > keydata
EpochKey()=default
std::pair< StaticKey, StaticKey > data_key(openvpn::CryptoAlgs::Type cipher)
void init(const char *name_arg, const int unit_arg, bool wide_arg)
void reset() noexcept
Points this RCPtr<T> to nullptr safely.
Definition rc.hpp:290
const unsigned char * data() const
size_t size() const
void ovpn_expand_label(const uint8_t *secret, size_t secret_len, const uint8_t *label, size_t label_len, const uint8_t *context, size_t context_len, uint8_t *out, size_t out_len)
void ovpn_hkdf_expand(const uint8_t *secret, const uint8_t *info, int info_len, uint8_t *out, int out_len)
@ DESTRUCT_ZERO
if enabled, destructor will zero data before deletion
Definition buffer.hpp:871
@ ARRAY
if enabled, use as array
Definition buffer.hpp:873
void calculate_iv(uint8_t *packet_id, std::array< uint8_t, IV_SIZE > &iv_dest)
std::string ret
static std::stringstream out
Definition test_path.cpp:10
int prefix_len(const IPv4::Addr::base_type mask)