OpenVPN 3 Core Library
Loading...
Searching...
No Matches
psid_cookie_impl.hpp
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) 2022- OpenVPN Inc.
8//
9// SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-only WITH openvpn3-openssl-exception
10//
11
12// A 64-bit protocol session ID, used by ProtoContext. But, unlike being random
13// in psid.hpp, the PsidCookieImpl class derives it via an HMAC of information
14// on the incoming client's OpenVPN HARD_RESET control message. This creates a
15// session id that acts like a syn-cookie on the OpenVPN startup 3-way
16// handshake.
17
18#pragma once
19
21
23#include <openvpn/common/rc.hpp>
26
27#include <openvpn/ssl/psid.hpp>
30
31#include <optional>
32
33namespace openvpn {
34
46{
47 public:
48 static constexpr int SID_SIZE = ProtoSessionID::SIZE;
49 static constexpr int OPCODE_SIZE = 1;
50
51 // must be called _before_ the server implementation starts threads; it guarantees
52 // that all per thread instances get the same psid cookie hmac key
53 static void pre_threading_setup()
54 {
55 get_key();
56 }
57
59 : pcfg_(*psfp->proto_context_config),
60 now_(pcfg_.now), handwindow_(pcfg_.handshake_window)
61 {
63 {
66
67 // init tls_auth hmac (see ProtoContext.reset() case TLS_AUTH; also TLSAuthPreValidate ctor)
68 if (pcfg_.key_direction >= 0)
69 {
70 // key-direction is 0 or 1
73 | OpenVPNStaticKey::ENCRYPT | key_dir));
75 | OpenVPNStaticKey::DECRYPT | key_dir));
76 }
77 else
78 {
79 // key-direction bidirectional mode
82 }
83 }
84
85 // initialize psid HMAC context with digest type and key
86 const StaticKey &key = get_key();
87 hmac_ctx_.init(digest_, key.data(), key.size());
88 }
89
90 Intercept intercept(Buffer &pkt_buf, const PsidCookieAddrInfoBase &pcaib) override
91 {
94
95 if (pkt_buf.empty())
96 return Intercept::EARLY_DROP; // packet validation fails, no opcode
97
98 CookieHelper chelp(pkt_buf[0]);
99
100 const bool is_tls_crypt_v2 = (chelp.is_tls_crypt_v2() && pcfg_.tls_crypt_v2_enabled());
101
102 if (chelp.is_clients_initial_reset())
103 {
104 return is_tls_crypt_v2
105 ? process_clients_initial_reset_tls_crypt(pkt_buf, pcaib, chelp)
107 }
108 if (chelp.is_clients_server_reset_ack())
109 {
110 return is_tls_crypt_v2
113 }
114
115 // JMD_TODO: log failure? Logging DDoS?
116 return Intercept::EARLY_DROP; // bad op field
117 }
118
120 {
123 return ret_val;
124 }
125
127 {
128 pctb_ = std::move(pctb);
129 }
130
131#ifndef UNIT_TEST
132 private:
133#endif
135
137 {
138 static const size_t hmac_size = ta_hmac_recv_->output_size();
139
140 // ovpn_hmac_cmp checks for adequate pkt_buf.size()
141 bool pkt_hmac_valid = ta_hmac_recv_->ovpn_hmac_cmp(pkt_buf.c_data(),
142 pkt_buf.size(),
144 hmac_size,
146 if (!pkt_hmac_valid)
147 {
148 // JMD_TODO: log failure? Logging DDoS?
149 return Intercept::DROP_1ST;
150 }
151
152 // check for adequate packet size to complete this function
153 static const size_t reqd_packet_size
154 // clang-format off
155 // [op_field] [cli_psid] [HMAC] [cli_auth_pktid] [cli_pktid]
157 // clang-format on
158 if (pkt_buf.size() < reqd_packet_size)
159 {
160 // JMD_TODO: log failure? Logging DDoS?
161 return Intercept::DROP_1ST;
162 }
163
164 // "buf_copy" here uses the same underlying data, but has it's own offset; skip
165 // past client's op_field.
166 ConstBuffer recv_buf_copy(pkt_buf.c_data() + 1, pkt_buf.size() - 1, true);
167 // decapsulate_tls_auth
168 const ProtoSessionID cli_psid(recv_buf_copy);
169 recv_buf_copy.advance(hmac_size);
170
171 PacketIDControl cli_auth_pktid; // a.k.a, replay_packet_id in draft RFC
172 cli_auth_pktid.read(recv_buf_copy);
173
174 uint8_t cli_net_id[4]; // a.k.a., packet_id in draft RFC
175
176 recv_buf_copy.read(cli_net_id, sizeof(cli_net_id));
177
178 // start building the server reply HARD_RESET packet
179 BufferAllocated send_buf;
180 static const Frame &frame = *pcfg_.frame;
181 frame.prepare(Frame::WRITE_SSL_INIT, send_buf);
182
183 // set server packet id (a.k.a., msg seq no) which would come from the
184 // reliability layer, if we had one
185 const reliable::id_t net_id = 0; // no htonl(0) since result is 0
186 send_buf.prepend(static_cast<const void *>(&net_id), sizeof(net_id));
187
188 // prepend_dest_psid_and_acks
189 cli_psid.prepend(send_buf);
190 send_buf.prepend(cli_net_id, sizeof(cli_net_id));
191 send_buf.push_front((unsigned char)1);
192
193 // gen head
194 PacketIDControlSend svr_auth_pid{};
195 svr_auth_pid.write_next(send_buf, true, now_->seconds_since_epoch());
196 // make space for tls-auth HMAC
198 // write source PSID
199 const ProtoSessionID srv_psid = calculate_session_id_hmac(cli_psid, pcaib, 0);
200 srv_psid.prepend(send_buf);
201 // write opcode
202 const unsigned char op_field = CookieHelper::get_server_hard_reset_opfield();
203 send_buf.push_front(op_field);
204 // write hmac
205 ta_hmac_send_->ovpn_hmac_gen(send_buf.data(),
206 send_buf.size(),
210
211 // consumer's implementation to send the SERVER_HARD_RESET to the client
212 bool send_ok = pctb_->psid_cookie_send_const(send_buf, pcaib);
213 if (send_ok)
214 {
216 }
217
218 return Intercept::DROP_1ST;
219 }
220
222 {
223 static const size_t hmac_size = ta_hmac_recv_->output_size();
224 // ovpn_hmac_cmp checks for adequate pkt_buf.size()
225 bool pkt_hmac_valid = ta_hmac_recv_->ovpn_hmac_cmp(pkt_buf.c_data(),
226 pkt_buf.size(),
228 hmac_size,
230 if (!pkt_hmac_valid)
231 {
232 // JMD_TODO: log failure? Logging DDoS?
233 return Intercept::DROP_2ND;
234 }
235
236 static const size_t reqd_packet_size
237 // clang-format off
238 // [op_field] [cli_psid] [HMAC] [cli_auth_pktid] [acked] [srv_psid]
239 = OPCODE_SIZE + SID_SIZE + hmac_size + PacketIDControl::size() + 5 + SID_SIZE;
240 // the fixed size, 5, of the [acked] field recognizes that the client's first
241 // response will ack exactly one packet, the server's HARD_RESET
242 // clang-format on
243 if (pkt_buf.size() < reqd_packet_size)
244 {
245 // JMD_TODO: log failure? Logging DDoS?
246 return Intercept::DROP_2ND;
247 }
248
249 // "buf_copy" here uses the same underlying data, but has it's own offset; skip
250 // past client's op_field.
251 ConstBuffer recv_buf_copy(pkt_buf.c_data() + 1, pkt_buf.size() - 1, true);
252 // decapsulate_tls_auth
253 const ProtoSessionID cli_psid(recv_buf_copy);
254 recv_buf_copy.advance(hmac_size);
255
256 PacketIDControl cli_auth_pktid; // a.k.a, replay_packet_id in draft RFC
257 cli_auth_pktid.read(recv_buf_copy);
258
259 unsigned int ack_count = recv_buf_copy[0];
260 if (ack_count != 1)
261 {
262 return Intercept::DROP_2ND;
263 }
264 recv_buf_copy.advance(5);
265 cookie_psid_.read(recv_buf_copy);
266
267 // verify client's Psid Cookie
268 bool is_cookie_valid = check_session_id_hmac(cookie_psid_, cli_psid, pcaib);
269 if (is_cookie_valid)
270 {
272 }
273
274 return Intercept::DROP_2ND;
275 }
276
278 const PsidCookieAddrInfoBase &pcaib,
279 const CookieHelper &ch)
280 {
281 static const size_t hmac_size = pcfg_.tls_crypt_context->digest_size();
282
283 ConstBuffer recv_buf_copy(pkt_buf.c_data() + 1, pkt_buf.size() - 1, true);
284
285 ProtoSessionID client_session_id(recv_buf_copy);
286 PacketIDControl replay_packet_id;
287 replay_packet_id.read(recv_buf_copy);
288
289 // This could be user-configurable so that we could just drop packets here if
290 // we don't want to allow clients that don't support re-sending the WKc.
291 if (!ch.supports_early_negotiation(replay_packet_id))
293
294 auto pipes = init_tls_crypt_v2(pkt_buf);
295
296 if (!pipes)
297 return Intercept::DROP_1ST;
298
299 auto [send, recv] = *pipes;
300
301 // Create synthetic RESET packet payload.
302 BufferAllocated payload;
304
306
307 PacketIDControl packet_id{.id = 0, .time = 0};
308 packet_id.write(payload, true);
309
310 client_session_id.prepend(payload);
311
312 const reliable::id_t acked_packet_id = 0;
313 payload.prepend(&acked_packet_id, sizeof(acked_packet_id));
314 payload.push_front((unsigned char)1);
315
317 // in 'work' we store all the fields that are not supposed to be encrypted
319 // make space for HMAC
320 work.prepend_alloc(hmac_size);
321 // write tls-crypt packet ID
322 PacketIDControlSend svr_auth_pid;
323 svr_auth_pid.write_next(work, true, now_->seconds_since_epoch());
324 // write source PSID
325 const ProtoSessionID srv_psid = calculate_session_id_hmac(client_session_id, pcaib, 0);
326 srv_psid.prepend(work);
327 // write opcode
329
330 // compute HMAC using header fields (from 'work') and plaintext
331 // payload
332 send->hmac_gen(work.data(), TLSCryptContext::hmac_offset, payload.c_data(), payload.size());
333
334 const size_t data_offset = TLSCryptContext::hmac_offset + hmac_size;
335
336 // encrypt the content of 'payload' (packet payload) into 'work'
337 const size_t encrypt_bytes = send->encrypt(work.c_data() + TLSCryptContext::hmac_offset,
338 work.data() + data_offset,
339 work.max_size() - data_offset,
340 payload.c_data(),
341 payload.size());
342 work.inc_size(encrypt_bytes);
343
344 // consumer's implementation to send the SERVER_HARD_RESET to the client
345 bool send_ok = pctb_->psid_cookie_send_const(work, pcaib);
346 if (send_ok)
348
349 return Intercept::DROP_1ST;
350 }
351
353 {
354 auto pipes = init_tls_crypt_v2(pkt_buf);
355
356 if (!pipes)
357 return Intercept::DROP_2ND;
358
359 auto [send, recv] = *pipes;
360
361 static const size_t hmac_size = pcfg_.tls_crypt_context->digest_size();
362
363 const size_t head_size = OPCODE_SIZE + ProtoSessionID::SIZE + PacketIDControl::size();
364 const unsigned char *orig_data = pkt_buf.c_data();
365
366 ConstBuffer recv_buf_copy(pkt_buf.c_data() + 1, pkt_buf.size() - 1, true);
367
368 ProtoSessionID client_session_id(recv_buf_copy);
369 recv_buf_copy.advance(PacketIDControl::size() + hmac_size);
370
373
374 // Decrypt into `work`.
375 const size_t decrypt_bytes = recv->decrypt(orig_data + head_size,
376 work.data(),
377 work.max_size(),
378 recv_buf_copy.c_data(),
379 recv_buf_copy.size());
380 if (!decrypt_bytes)
381 return Intercept::DROP_2ND;
382
383 work.inc_size(decrypt_bytes);
384
385 // Verify HMAC.
386 if (!recv->hmac_cmp(orig_data, TLSCryptContext::hmac_offset, work.c_data(), work.size()))
387 return Intercept::DROP_2ND;
388
389 // We _should_ have one ACK (for the CONTROL_HARD_RESET_V2 previous message).
390 if (work[0] != 1)
391 return Intercept::DROP_2ND;
392
393 // Discard the opcode and the acked packet ID.
394 work.advance(OPCODE_SIZE + sizeof(uint32_t));
395
396 // Retrieve the peer session ID (this must match).
398
399 // verify client's Psid Cookie
400 if (check_session_id_hmac(cookie_psid_, client_session_id, pcaib))
402
403 return Intercept::DROP_2ND;
404 }
405
406 // key must be common to all threads
408 {
409 StrongRandomAPI::Ptr rng(new SSLLib::RandomAPI());
411
412 // guarantee that the key is large enough
413 StaticKey key;
414 key.init_from_rng(*rng, alg.size());
415 return key;
416 }
417
418 static const StaticKey &get_key()
419 {
420 static const StaticKey key = create_key();
421 return key;
422 }
423
433 const PsidCookieAddrInfoBase &pcaib,
434 unsigned int offset)
435 {
437
438 // Get the time window for which the ProtoSessionID hmac is valid. The window
439 // size is an interval given by handwindow/2, one half of the configured
440 // handshake timeout, typically 30 seconds. The valid_time is the count of
441 // intervals since the beginning of the epoch. With offset zero, the valid_time
442 // is the server's current interval; with offsets 1 to n, it is the server's nth
443 // previous interval.
444 //
445 // There is the theoretical issue of valid_time wrapping after 2^32 intervals.
446 // With 30 second intervals, around the year 4010. Will not spoil my weekend.
447 uint64_t interval = (handwindow_.raw() + 1) / 2;
448 uint32_t valid_time = static_cast<uint32_t>(now_->raw() / interval - offset);
449 // no endian concerns; hmac is created and checked by the same host
450 hmac_ctx_.update(reinterpret_cast<const unsigned char *>(&valid_time),
451 sizeof(valid_time));
452
453 // the memory slab at cli_addr_port of size cli_addrport_size is a reproducibly
454 // hashable representation of the client's address and port
455 size_t cli_addrport_size;
456 const unsigned char *cli_addr_port = pcaib.get_abstract_cli_addrport(cli_addrport_size);
457 hmac_ctx_.update(cli_addr_port, cli_addrport_size);
458
459 // add session id of client
460 const Buffer cli_psid_buf = cli_psid.get_buf();
461 hmac_ctx_.update(cli_psid_buf.c_data(), SID_SIZE);
462
463 // finalize the hmac and package it as the server's ProtoSessionID
464 BufferAllocated hmac_result(SSLLib::CryptoAPI::HMACContext::MAX_HMAC_SIZE);
465 ProtoSessionID srv_psid;
466 hmac_ctx_.final(hmac_result.write_alloc(hmac_ctx_.size()));
467 srv_psid.read(hmac_result);
468
469 return srv_psid;
470 }
471
473 const ProtoSessionID &cli_psid,
474 const PsidCookieAddrInfoBase &pcaib)
475 {
476 // check the current timestamp and the previous one in case the server's clock
477 // has moved to the one following that given to the client
478 for (unsigned int offset = 0; offset <= 1; ++offset)
479 {
480 ProtoSessionID calc_psid = calculate_session_id_hmac(cli_psid, pcaib, offset);
481
482 if (srv_psid.match(calc_psid))
483 {
484 return true;
485 }
486 }
487 return false;
488 }
489
496 std::optional<std::pair<TLSCryptInstance::Ptr, TLSCryptInstance::Ptr>> init_tls_crypt_v2(Buffer &pkt_buf)
497 {
500
502
503 if (ProtoContext::KeyContext::unwrap_tls_crypt_wkc(pkt_buf, pcfg_, *tls_crypt_server) != Error::SUCCESS)
504 return std::nullopt;
505
506 const unsigned int key_dir = pcfg_.ssl_factory->mode().is_server()
509
512
513 send->init(pcfg_.ssl_factory->libctx(),
516
517 recv->init(pcfg_.ssl_factory->libctx(),
520
521 return std::pair{send, recv};
522 }
523
525
528 const Time::Duration &handwindow_;
529
532
533 // the psid cookie specific hmac object
534 SSLLib::CryptoAPI::HMACContext hmac_ctx_;
535
538};
539
540} // namespace openvpn
const T * c_data() const
Returns a const pointer to the start of the buffer.
Definition buffer.hpp:1193
T * prepend_alloc(const size_t size)
Allocate space for prepending data to the buffer.
Definition buffer.hpp:1594
T * write_alloc(const size_t size)
Allocate space for writing data to the buffer.
Definition buffer.hpp:1584
void prepend(const T *data, const size_t size)
Prepend data to the buffer.
Definition buffer.hpp:1572
size_t size() const
Returns the size of the buffer in T objects.
Definition buffer.hpp:1241
T * data()
Get a mutable pointer to the start of the array.
Definition buffer.hpp:1447
void advance(const size_t delta)
Advances the buffer by the specified delta.
Definition buffer.hpp:1276
bool empty() const
Returns true if the buffer is empty.
Definition buffer.hpp:1235
void push_front(const T &value)
Append a T object to the array, with possible resize.
Definition buffer.hpp:1487
void read(NCT *data, const size_t size)
Read data from the buffer into the specified memory location.
Definition buffer.hpp:1330
size_t prepare(const unsigned int context, Buffer &buf) const
Definition frame.hpp:263
bool is_server() const
Definition mode.hpp:36
StaticKey slice(unsigned int key_specifier) const
virtual OvpnHMACInstance::Ptr new_obj()=0
virtual void ovpn_hmac_gen(unsigned char *data, const size_t data_size, const size_t l1, const size_t l2, const size_t l3)=0
virtual void init(const StaticKey &key)=0
virtual bool ovpn_hmac_cmp(const unsigned char *data, const size_t data_size, const size_t l1, const size_t l2, const size_t l3)=0
virtual size_t output_size() const =0
void write_next(Buffer &buf, const bool prepend, const PacketIDControl::time_t now)
static Error::Type unwrap_tls_crypt_wkc(Buffer &recv, ProtoConfig &proto_config, TLSCryptInstance &tls_crypt_server, TLSCryptMetadata::Ptr tls_crypt_metadata=nullptr)
Extract and process the TLS crypt WKc information.
Definition proto.hpp:2371
SSLFactoryAPI::Ptr ssl_factory
Definition proto.hpp:342
TLSCryptContext::Ptr tls_crypt_context
Definition proto.hpp:424
OpenVPNStaticKey tls_auth_key
leave this undefined to disable tls_auth
Definition proto.hpp:399
OpenVPNStaticKey wrapped_tls_crypt_key
For TLS crypt V2, this (if defined()) is the wrapped WKc client key.
Definition proto.hpp:405
OvpnHMACContext::Ptr tls_auth_context
Definition proto.hpp:420
static unsigned char get_server_hard_reset_opfield()
Definition proto.hpp:3662
bool supports_early_negotiation(const PacketIDControl &pidc) const noexcept
Returns true if the peer supports early negotiation (i.e. is able to reply with CONTROL_WKC_V1).
Definition proto.hpp:3639
static void prepend_TLV(Buffer &payload)
Adds an {EARLY_NEG_FLAGS, 2, EARLY_NEG_FLAG_RESEND_WKC} TLV to a payload buffer (use with TLS crypt V...
Definition proto.hpp:3650
bool is_tls_crypt_v2() const noexcept
Returns true if this is a TLS crypt V2 protocol packet.
Definition proto.hpp:3633
bool match(const ProtoSessionID &other) const
Definition psid.hpp:90
const Buffer get_buf() const
Definition psid.hpp:76
void read(BufType &buf)
Definition psid.hpp:59
void prepend(Buffer &buf) const
Definition psid.hpp:70
Interface to communicate the server's address semantics.
virtual const unsigned char * get_abstract_cli_addrport(size_t &slab_size) const =0
Implements the PsidCookie interface.
static StaticKey create_key()
SSLLib::CryptoAPI::HMACContext hmac_ctx_
OvpnHMACInstance::Ptr ta_hmac_recv_
ProtoSessionID get_cookie_psid() override
Get the cookie psid from client's 2nd packet.
static const StaticKey & get_key()
const Time::Duration & handwindow_
bool check_session_id_hmac(const ProtoSessionID &srv_psid, const ProtoSessionID &cli_psid, const PsidCookieAddrInfoBase &pcaib)
PsidCookieTransportBase::Ptr pctb_
Intercept process_clients_initial_reset_tls_crypt(Buffer &pkt_buf, const PsidCookieAddrInfoBase &pcaib, const CookieHelper &ch)
static constexpr int SID_SIZE
ProtoContext::ProtoConfig & pcfg_
Intercept intercept(Buffer &pkt_buf, const PsidCookieAddrInfoBase &pcaib) override
Called when a potential new client session packet is received.
ProtoSessionID calculate_session_id_hmac(const ProtoSessionID &cli_psid, const PsidCookieAddrInfoBase &pcaib, unsigned int offset)
Calculate the psid cookie, the ProtoSessionID hmac.
Intercept process_clients_initial_reset_tls_auth(ConstBuffer &pkt_buf, const PsidCookieAddrInfoBase &pcaib)
static constexpr CryptoAlgs::Type digest_
std::optional< std::pair< TLSCryptInstance::Ptr, TLSCryptInstance::Ptr > > init_tls_crypt_v2(Buffer &pkt_buf)
Set up a couple of TLSCryptInstance (send, recv) from a TLS crypt V2 packet's WKc.
Intercept process_clients_server_reset_ack_tls_crypt(Buffer &pkt_buf, const PsidCookieAddrInfoBase &pcaib)
static constexpr int OPCODE_SIZE
Intercept process_clients_server_reset_ack_tls_auth(ConstBuffer &pkt_buf, const PsidCookieAddrInfoBase &pcaib)
void provide_psid_cookie_transport(PsidCookieTransportBase::Ptr pctb) override
Give this component the transport needed to send the server's HARD_RESET.
PsidCookieImpl(ServerProto::Factory *psfp)
OvpnHMACInstance::Ptr ta_hmac_send_
virtual bool psid_cookie_send_const(Buffer &send_buf, const PsidCookieAddrInfoBase &pcaib)=0
Interface to integrate this component into the server implementation.
Intercept
Values returned by the intercept() function.
virtual SSLLib::Ctx libctx()=0
virtual const Mode & mode() const =0
void init_from_rng(StrongRandomAPI &rng, const size_t key_size)
const unsigned char * data() const
size_t size() const
virtual TLSCryptInstance::Ptr new_obj_send()=0
virtual TLSCryptInstance::Ptr new_obj_recv()=0
constexpr static const size_t hmac_offset
virtual size_t digest_size() const =0
virtual void init(SSLLib::Ctx libctx, const StaticKey &key_hmac, const StaticKey &key_crypt)=0
T raw() const
Definition time.hpp:404
base_type seconds_since_epoch() const
Definition time.hpp:289
void work(openvpn_io::io_context &io_context, ThreadCommon &tc, MyRunContext &runctx, const unsigned int unit)
const Alg & get(const Type type)
static constexpr std::size_t id_size
Definition relcommon.hpp:23
std::uint32_t id_t
Definition relcommon.hpp:22
static constexpr size_t size()
static constexpr size_t idsize