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
32namespace openvpn {
33
45{
46 public:
47 static constexpr int SID_SIZE = ProtoSessionID::SIZE;
48
49 // must be called _before_ the server implementation starts threads; it guarantees
50 // that all per thread instances get the same psid cookie hmac key
51 static void pre_threading_setup()
52 {
53 get_key();
54 }
55
57 : pcfg_(*psfp->proto_context_config),
58 not_tls_auth_mode_(!pcfg_.tls_auth_enabled() || pcfg_.tls_crypt_enabled() || pcfg_.tls_crypt_v2_enabled()),
59 now_(pcfg_.now), handwindow_(pcfg_.handshake_window)
60 {
62 return;
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 // initialize psid HMAC context with digest type and key
85 const StaticKey &key = get_key();
86 hmac_ctx_.init(digest_, key.data(), key.size());
87 }
88
89 virtual ~PsidCookieImpl() = default;
90
91 Intercept intercept(ConstBuffer &pkt_buf, const PsidCookieAddrInfoBase &pcaib) override
92 {
93 // tls auth enabled is the only config we handle
95 { // test discovered in TLSAuthPreValidate
96 return Intercept::DECLINE_HANDLING; // let existing code handle these cases
97 }
98
99 if (!pkt_buf.size())
100 {
101 return Intercept::EARLY_DROP; // packet validation fails, no opcode
102 }
103 CookieHelper chelp(pkt_buf[0]);
104 if (chelp.is_clients_initial_reset())
105 {
106 return process_clients_initial_reset(pkt_buf, pcaib);
107 }
108 else if (chelp.is_clients_server_reset_ack())
109 {
110 return process_clients_server_reset_ack(pkt_buf, pcaib);
111 }
112
113 // JMD_TODO: log failure? Logging DDoS?
114 return Intercept::EARLY_DROP; // bad op field
115 }
116
118 {
121 return ret_val;
122 }
123
125 {
126 pctb_ = std::move(pctb);
127 }
128
129#ifndef UNIT_TEST
130 private:
131#endif
133
135 {
136 static const size_t hmac_size = ta_hmac_recv_->output_size();
137 // ovpn_hmac_cmp checks for adequate pkt_buf.size()
138 bool pkt_hmac_valid = ta_hmac_recv_->ovpn_hmac_cmp(pkt_buf.c_data(), pkt_buf.size(), 1 + SID_SIZE, hmac_size, PacketIDControl::idsize);
139 if (!pkt_hmac_valid)
140 {
141 // JMD_TODO: log failure? Logging DDoS?
142 return Intercept::DROP_1ST;
143 }
144
145 // check for adequate packet size to complete this function
146 static const size_t reqd_packet_size
147 // clang-format off
148 // [op_field] [cli_psid] [HMAC] [cli_auth_pktid] [cli_pktid]
150 // clang-format on
151 if (pkt_buf.size() < reqd_packet_size)
152 {
153 // JMD_TODO: log failure? Logging DDoS?
154 return Intercept::DROP_1ST;
155 }
156
157 // "buf_copy" here uses the same underlying data, but has it's own offset; skip
158 // past client's op_field.
159 ConstBuffer recv_buf_copy(pkt_buf.c_data() + 1, pkt_buf.size() - 1, true);
160 // decapsulate_tls_auth
161 const ProtoSessionID cli_psid(recv_buf_copy);
162 recv_buf_copy.advance(hmac_size);
163
164 PacketIDControl cli_auth_pktid; // a.k.a, replay_packet_id in draft RFC
165 cli_auth_pktid.read(recv_buf_copy);
166
167 uint8_t cli_net_id[4]; // a.k.a., packet_id in draft RFC
168
169 recv_buf_copy.read(cli_net_id, sizeof(cli_net_id));
170
171 // start building the server reply HARD_RESET packet
172 BufferAllocated send_buf;
173 static const Frame &frame = *pcfg_.frame;
174 frame.prepare(Frame::WRITE_SSL_INIT, send_buf);
175
176 // set server packet id (a.k.a., msg seq no) which would come from the
177 // reliability layer, if we had one
178 const reliable::id_t net_id = 0; // no htonl(0) since result is 0
179 send_buf.prepend(static_cast<const void *>(&net_id), sizeof(net_id));
180
181 // prepend_dest_psid_and_acks
182 cli_psid.prepend(send_buf);
183 send_buf.prepend(cli_net_id, sizeof(cli_net_id));
184 send_buf.push_front((unsigned char)1);
185
186 // gen head
187 PacketIDControlSend svr_auth_pid{};
188 svr_auth_pid.write_next(send_buf, true, now_->seconds_since_epoch());
189 // make space for tls-auth HMAC
191 // write source PSID
192 const ProtoSessionID srv_psid = calculate_session_id_hmac(cli_psid, pcaib, 0);
193 srv_psid.prepend(send_buf);
194 // write opcode
195 const unsigned char op_field = CookieHelper::get_server_hard_reset_opfield();
196 send_buf.push_front(op_field);
197 // write hmac
199
200 // consumer's implementation to send the SERVER_HARD_RESET to the client
201 bool send_ok = pctb_->psid_cookie_send_const(send_buf, pcaib);
202 if (send_ok)
203 {
205 }
206
207 return Intercept::DROP_1ST;
208 }
209
211 {
212 static const size_t hmac_size = ta_hmac_recv_->output_size();
213 // ovpn_hmac_cmp checks for adequate pkt_buf.size()
214 bool pkt_hmac_valid = ta_hmac_recv_->ovpn_hmac_cmp(pkt_buf.c_data(), pkt_buf.size(), 1 + SID_SIZE, hmac_size, PacketIDControl::idsize);
215 if (!pkt_hmac_valid)
216 {
217 // JMD_TODO: log failure? Logging DDoS?
218 return Intercept::DROP_2ND;
219 }
220
221 static const size_t reqd_packet_size
222 // clang-format off
223 // [op_field] [cli_psid] [HMAC] [cli_auth_pktid] [acked] [srv_psid]
224 = 1 + SID_SIZE + hmac_size + PacketIDControl::size() + 5 + SID_SIZE;
225 // the fixed size, 5, of the [acked] field recognizes that the client's first
226 // response will ack exactly one packet, the server's HARD_RESET
227 // clang-format on
228 if (pkt_buf.size() < reqd_packet_size)
229 {
230 // JMD_TODO: log failure? Logging DDoS?
231 return Intercept::DROP_2ND;
232 }
233
234 // "buf_copy" here uses the same underlying data, but has it's own offset; skip
235 // past client's op_field.
236 ConstBuffer recv_buf_copy(pkt_buf.c_data() + 1, pkt_buf.size() - 1, true);
237 // decapsulate_tls_auth
238 const ProtoSessionID cli_psid(recv_buf_copy);
239 recv_buf_copy.advance(hmac_size);
240
241 PacketIDControl cli_auth_pktid; // a.k.a, replay_packet_id in draft RFC
242 cli_auth_pktid.read(recv_buf_copy);
243
244 unsigned int ack_count = recv_buf_copy[0];
245 if (ack_count != 1)
246 {
247 return Intercept::DROP_2ND;
248 }
249 recv_buf_copy.advance(5);
250 cookie_psid_.read(recv_buf_copy);
251
252 // verify client's Psid Cookie
253 bool is_cookie_valid = check_session_id_hmac(cookie_psid_, cli_psid, pcaib);
254 if (is_cookie_valid)
255 {
257 }
258
259 return Intercept::DROP_2ND;
260 }
261
262 // key must be common to all threads
264 {
265 StrongRandomAPI::Ptr rng(new SSLLib::RandomAPI());
267
268 // guarantee that the key is large enough
269 StaticKey key;
270 key.init_from_rng(*rng, alg.size());
271 return key;
272 }
273
274 static const StaticKey &get_key()
275 {
276 static const StaticKey key = create_key();
277 return key;
278 }
279
289 const PsidCookieAddrInfoBase &pcaib,
290 unsigned int offset)
291 {
293
294 // Get the time window for which the ProtoSessionID hmac is valid. The window
295 // size is an interval given by handwindow/2, one half of the configured
296 // handshake timeout, typically 30 seconds. The valid_time is the count of
297 // intervals since the beginning of the epoch. With offset zero, the valid_time
298 // is the server's current interval; with offsets 1 to n, it is the server's nth
299 // previous interval.
300 //
301 // There is the theoretical issue of valid_time wrapping after 2^32 intervals.
302 // With 30 second intervals, around the year 4010. Will not spoil my weekend.
303 uint64_t interval = (handwindow_.raw() + 1) / 2;
304 uint32_t valid_time = static_cast<uint32_t>(now_->raw() / interval - offset);
305 // no endian concerns; hmac is created and checked by the same host
306 hmac_ctx_.update(reinterpret_cast<const unsigned char *>(&valid_time),
307 sizeof(valid_time));
308
309 // the memory slab at cli_addr_port of size cli_addrport_size is a reproducibly
310 // hashable representation of the client's address and port
311 size_t cli_addrport_size;
312 const unsigned char *cli_addr_port = pcaib.get_abstract_cli_addrport(cli_addrport_size);
313 hmac_ctx_.update(cli_addr_port, cli_addrport_size);
314
315 // add session id of client
316 const Buffer cli_psid_buf = cli_psid.get_buf();
317 hmac_ctx_.update(cli_psid_buf.c_data(), SID_SIZE);
318
319 // finalize the hmac and package it as the server's ProtoSessionID
320 BufferAllocated hmac_result(SSLLib::CryptoAPI::HMACContext::MAX_HMAC_SIZE);
321 ProtoSessionID srv_psid;
322 hmac_ctx_.final(hmac_result.write_alloc(hmac_ctx_.size()));
323 srv_psid.read(hmac_result);
324
325 return srv_psid;
326 }
327
329 const ProtoSessionID &cli_psid,
330 const PsidCookieAddrInfoBase &pcaib)
331 {
332 // check the current timestamp and the previous one in case the server's clock
333 // has moved to the one following that given to the client
334 for (unsigned int offset = 0; offset <= 1; ++offset)
335 {
336 ProtoSessionID calc_psid = calculate_session_id_hmac(cli_psid, pcaib, offset);
337
338 if (srv_psid.match(calc_psid))
339 {
340 return true;
341 }
342 }
343 return false;
344 }
345
347
351 const Time::Duration &handwindow_;
352
355
356 // the psid cookie specific hmac object
357 SSLLib::CryptoAPI::HMACContext hmac_ctx_;
358
361};
362
363} // namespace openvpn
const T * c_data() const
Returns a const pointer to the start of the buffer.
Definition buffer.hpp:1194
T * prepend_alloc(const size_t size)
Allocate space for prepending data to the buffer.
Definition buffer.hpp:1597
T * write_alloc(const size_t size)
Allocate space for writing data to the buffer.
Definition buffer.hpp:1587
void prepend(const T *data, const size_t size)
Prepend data to the buffer.
Definition buffer.hpp:1575
size_t size() const
Returns the size of the buffer in T objects.
Definition buffer.hpp:1242
T * data()
Get a mutable pointer to the start of the array.
Definition buffer.hpp:1450
void advance(const size_t delta)
Advances the buffer by the specified delta.
Definition buffer.hpp:1277
void push_front(const T &value)
Append a T object to the array, with possible resize.
Definition buffer.hpp:1490
void read(NCT *data, const size_t size)
Read data from the buffer into the specified memory location.
Definition buffer.hpp:1331
size_t prepare(const unsigned int context, Buffer &buf) const
Definition frame.hpp:266
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)
OpenVPNStaticKey tls_auth_key
leave this undefined to disable tls_auth
Definition proto.hpp:398
OvpnHMACContext::Ptr tls_auth_context
Definition proto.hpp:416
static unsigned char get_server_hard_reset_opfield()
Definition proto.hpp:3622
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)
const ProtoContext::ProtoConfig & pcfg_
Intercept intercept(ConstBuffer &pkt_buf, const PsidCookieAddrInfoBase &pcaib) override
Called when a potential new client session packet is received.
PsidCookieTransportBase::Ptr pctb_
Intercept process_clients_initial_reset(ConstBuffer &pkt_buf, const PsidCookieAddrInfoBase &pcaib)
virtual ~PsidCookieImpl()=default
static constexpr int SID_SIZE
ProtoSessionID calculate_session_id_hmac(const ProtoSessionID &cli_psid, const PsidCookieAddrInfoBase &pcaib, unsigned int offset)
Calculate the psid cookie, the ProtoSessionID hmac.
static constexpr CryptoAlgs::Type digest_
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)
Intercept process_clients_server_reset_ack(ConstBuffer &pkt_buf, const PsidCookieAddrInfoBase &pcaib)
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.
void init_from_rng(StrongRandomAPI &rng, const size_t key_size)
const unsigned char * data() const
size_t size() const
T raw() const
Definition time.hpp:409
base_type seconds_since_epoch() const
Definition time.hpp:292
const Alg & get(const Type type)
std::uint32_t id_t
Definition relcommon.hpp:22
static constexpr std::size_t id_size
Definition relcommon.hpp:23
static constexpr size_t size()
static constexpr size_t idsize