OpenVPN 3 Core Library
Loading...
Searching...
No Matches
test_psid_cookie.cpp
Go to the documentation of this file.
1#include "test_common.hpp"
2
4
5using namespace openvpn;
6
7
9{
11
12 ASSERT_TRUE(true);
13}
14
15// The following uland_addr46 type is a userland adaptation of an unpublished
16// ovpn_addr46 type from James Yonan's kernel work. The main idea is to create
17// a reliably hashable representation of an IP address, be it IPv4 or IPv6
18/* Discriminated union for IPv4/v6 addresses that should replace
19 ovpn_addr. The advantage of this approach over ovpn_addr is
20 better alignment/packing and potential use as an rhashtable key. */
22 /* IPv4 */
23 struct
24 {
25 /* treat as IPv4-mapped IPv6 addresses */
26 uint64_t a4_pre64; /* 0 */
27 uint32_t a4_pre32; /* htonl(0xFFFF) */
28 struct in_addr a4; /* the IPv4 address */
29 };
30
31 /* IPv6 */
32 struct in6_addr a6;
33 uint64_t a6_64[2];
34};
35
37{
38 public:
40 {
41 prng.rand_fill(addrport_);
42 }
43 const unsigned char *get_abstract_cli_addrport(size_t &slab_size) const override
44 {
45 slab_size = slab_size_;
46 return addrport_.c;
47 }
48 // unused for these tests
49 const void *get_impl_info() const override
50 {
51 return nullptr;
52 }
53
54 virtual ~ClientAddressMock() = default;
55
56 private:
57 // the detail here is not used; the slab is just randomly filled with data for the
58 // hmac; this segment is here to show the motivation for slab_size_
59 static constexpr size_t slab_size_ = sizeof(union uland_addr46) + sizeof(std::uint16_t);
60 union {
61 unsigned char c[slab_size_];
62 struct
63 {
64 union uland_addr46 oaddr46;
65 std::uint16_t port;
66 } s;
68};
69
70class PsidCookieTest : public testing::Test
71{
72 openvpn_io::io_context dummy_io_context;
76
77 protected:
79 : dummy_io_context(1), pcfg(new ProtoContext::ProtoConfig())
80 {
81 const std::string tls_key_fn = UNITTEST_SOURCE_DIR "/input/psid_cookie_tls.key";
82 pcfg->tls_auth_key.parse_from_file(tls_key_fn);
84 pcfg->set_tls_auth_digest(CryptoAlgs::lookup("SHA256"));
85 pcfg->now = &now;
86 pcfg->handshake_window = Time::Duration::seconds(60);
87 pcfg->key_direction = 0;
88 pcfg->rng.reset(new SSLLib::RandomAPI());
89 pcfg->prng.reset(new MTRand(2020303));
90
92 spf->proto_context_config = pcfg;
93
95 }
96
98 {
99 now = setting;
100 return setting;
101 }
102
103 Time advance_clock(uint64_t binary_ms)
104 {
105 now += Time::Duration::binary_ms(binary_ms);
106 return now;
107 }
108
109 void SetUp() override
110 {
111 }
112
113 void TearDown() override
114 {
115 }
116
117 std::unique_ptr<PsidCookieImpl> pcookie_impl;
118};
119
120
122{
123 const PsidCookieImpl *pci_dut = pcookie_impl.get();
124 ASSERT_NE(pci_dut, nullptr);
125
126 // check test clock's equivalence to the PsidCookieImpl clock
127 const Time start(set_clock(Time::now()));
128 EXPECT_TRUE(start == *pci_dut->now_);
129
130 // spot check other aspects of successful pci_dut creation
131 EXPECT_TRUE(pci_dut->pcfg_.tls_auth_key.defined());
132}
133
135{
136 PsidCookieImpl &pci_dut(*pcookie_impl.get());
137 const ClientAddressMock cli_addr(*pci_dut.pcfg_.prng);
138 ProtoSessionID cli_psid;
139 ProtoSessionID srv_psid;
140 // interval duplicates the computation in calculate_session_id_hmac()
141 const uint64_t interval = (pci_dut.pcfg_.handshake_window.raw() + 1) / 2;
142 bool hmac_ok;
143
144 cli_psid.randomize(*pci_dut.pcfg_.rng);
145
146 set_clock(Time::now());
147 srv_psid = pci_dut.calculate_session_id_hmac(cli_psid, cli_addr, 0);
148
149 // server is in the same interval in which it offered the hmac
150 hmac_ok = pci_dut.check_session_id_hmac(srv_psid, cli_psid, cli_addr);
151 EXPECT_TRUE(hmac_ok);
152
153 advance_clock(interval);
154 // server is in the next interval after which it offered the hmac
155 hmac_ok = pci_dut.check_session_id_hmac(srv_psid, cli_psid, cli_addr);
156 EXPECT_TRUE(hmac_ok);
157
158 advance_clock(interval);
159 // server is two intervals after which it offered the hmac
160 hmac_ok = pci_dut.check_session_id_hmac(srv_psid, cli_psid, cli_addr);
161 EXPECT_FALSE(hmac_ok);
162}
163
164
165// Tests that exercise PsidCookieImpl::intercept() against crafted third
166// packets of the OpenVPN 3-way handshake (the client reply to the server's
167// HARD_RESET). The cookie code only ever sees this packet when no peer
168// state exists yet, so it must positively identify the packet as the
169// handshake-completing one before letting the caller create state.
171{
172 protected:
173 // Build a complete third-packet (tls-auth path) suitable for intercept().
174 // Each on-the-wire field is parameterized so that individual tests can
175 // perturb exactly one field while leaving everything else valid.
177 const ProtoSessionID &cookie_psid,
178 std::uint32_t acked_pktid_be,
179 std::uint32_t own_pktid_be,
180 unsigned char ack_count,
181 unsigned char op_field)
182 {
184 // The server validates the incoming HMAC with ta_hmac_recv_; with
185 // pcfg_.key_direction == 0 that key differs from ta_hmac_send_'s, so
186 // we must sign the synthetic client packet with the recv key here.
187 const size_t hmac_size = pci.ta_hmac_recv_->output_size();
188
189 BufferAllocated buf;
190 buf.reset(/*headroom=*/256, /*capacity=*/512, BufAllocFlags::GROW);
191
192 // Fields are prepended in reverse on-the-wire order, mirroring how
193 // process_clients_initial_reset_tls_auth() builds the server reply.
194 buf.prepend(&own_pktid_be, sizeof(own_pktid_be));
195 cookie_psid.prepend(buf);
196 buf.prepend(&acked_pktid_be, sizeof(acked_pktid_be));
197 buf.push_front(ack_count);
198
200 pid.write_next(buf, /*prepend=*/true, pci.now_->seconds_since_epoch());
201
202 buf.prepend_alloc(hmac_size);
203 cli_psid.prepend(buf);
204 buf.push_front(op_field);
205
207 buf.size(),
209 hmac_size,
211 return buf;
212 }
213
220
222 {
225
226 Fixture f{ClientAddressMock(*pci.pcfg_.prng), {}, {}};
227 f.cli_psid.randomize(*pci.pcfg_.rng);
228 f.cookie_psid = pci.calculate_session_id_hmac(f.cli_psid, f.cli_addr, 0);
229 return f;
230 }
231};
232
234{
235 auto f = make_fixture();
236 BufferAllocated pkt = build_third_packet_tls_auth(f.cli_psid,
237 f.cookie_psid,
238 /*acked_pktid_be=*/0,
239 /*own_pktid_be=*/0,
240 /*ack_count=*/1,
242
243 EXPECT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
244 EXPECT_TRUE(pcookie_impl->get_cookie_psid().match(f.cookie_psid));
245}
246
247TEST_F(PsidCookieInterceptTest, ThirdPacketAcceptsAckedPktidOne)
248{
249 // Both acked-pktid 0 (default) and 1 are tolerated as part of the early
250 // handshake; only > 1 is treated as mid-session. This mirrors OpenVPN 2.
251 auto f = make_fixture();
252 BufferAllocated pkt = build_third_packet_tls_auth(f.cli_psid,
253 f.cookie_psid,
254 /*acked_pktid_be=*/htonl(1),
255 /*own_pktid_be=*/0,
256 /*ack_count=*/1,
258
259 EXPECT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
260}
261
262TEST_F(PsidCookieInterceptTest, ThirdPacketRejectsAckedPktidAboveOne)
263{
264 auto f = make_fixture();
265 BufferAllocated pkt = build_third_packet_tls_auth(f.cli_psid,
266 f.cookie_psid,
267 /*acked_pktid_be=*/htonl(2),
268 /*own_pktid_be=*/0,
269 /*ack_count=*/1,
271
272 EXPECT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::DROP_2ND);
273}
274
275TEST_F(PsidCookieInterceptTest, ThirdPacketAcceptsOwnPktidOne)
276{
277 auto f = make_fixture();
278 BufferAllocated pkt = build_third_packet_tls_auth(f.cli_psid,
279 f.cookie_psid,
280 /*acked_pktid_be=*/0,
281 /*own_pktid_be=*/htonl(1),
282 /*ack_count=*/1,
284
285 EXPECT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
286}
287
288TEST_F(PsidCookieInterceptTest, ThirdPacketRejectsOwnPktidAboveOne)
289{
290 auto f = make_fixture();
291 BufferAllocated pkt = build_third_packet_tls_auth(f.cli_psid,
292 f.cookie_psid,
293 /*acked_pktid_be=*/0,
294 /*own_pktid_be=*/htonl(2),
295 /*ack_count=*/1,
297
298 EXPECT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::DROP_2ND);
299}
300
301TEST_F(PsidCookieInterceptTest, ThirdPacketRejectsAckCountNotOne)
302{
303 auto f = make_fixture();
304 BufferAllocated pkt = build_third_packet_tls_auth(f.cli_psid,
305 f.cookie_psid,
306 /*acked_pktid_be=*/0,
307 /*own_pktid_be=*/0,
308 /*ack_count=*/2,
310
311 EXPECT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::DROP_2ND);
312}
313
314TEST_F(PsidCookieInterceptTest, ThirdPacketAcceptsAckV1)
315{
316 // P_ACK_V1 has no own message-id on the wire; intercept() must accept
317 // it and skip the message-id check. The packet builder still writes 4
318 // bytes for own_pktid into the buffer, but the validator's reqd_size is
319 // 4 bytes shorter for ACK_V1 so those bytes are simply ignored.
320 auto f = make_fixture();
321 BufferAllocated pkt = build_third_packet_tls_auth(f.cli_psid,
322 f.cookie_psid,
323 /*acked_pktid_be=*/0,
324 /*own_pktid_be=*/0,
325 /*ack_count=*/1,
327
328 EXPECT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
329}
330
331TEST_F(PsidCookieInterceptTest, ThirdPacketRejectsNonZeroKeyId)
332{
333 auto f = make_fixture();
334 BufferAllocated pkt = build_third_packet_tls_auth(f.cli_psid,
335 f.cookie_psid,
336 /*acked_pktid_be=*/0,
337 /*own_pktid_be=*/0,
338 /*ack_count=*/1,
340
341 EXPECT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::EARLY_DROP);
342}
343
344TEST_F(PsidCookieInterceptTest, ThirdPacketRejectsBadCookie)
345{
346 auto f = make_fixture();
347 // Tamper with the cookie psid: still valid HMAC over the packet, but
348 // the embedded server psid does not match what calculate_session_id_hmac
349 // would produce for this client.
350 ProtoSessionID bogus;
351 bogus.randomize(*pcookie_impl->pcfg_.rng);
352
353 BufferAllocated pkt = build_third_packet_tls_auth(f.cli_psid,
354 bogus,
355 /*acked_pktid_be=*/0,
356 /*own_pktid_be=*/0,
357 /*ack_count=*/1,
359
360 EXPECT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::DROP_2ND);
361}
362
363TEST_F(PsidCookieInterceptTest, ThirdPacketRejectsBadHmac)
364{
365 auto f = make_fixture();
366 BufferAllocated pkt = build_third_packet_tls_auth(f.cli_psid,
367 f.cookie_psid,
368 /*acked_pktid_be=*/0,
369 /*own_pktid_be=*/0,
370 /*ack_count=*/1,
372 // Flip a byte in the HMAC field (right after the opcode + own session id).
374
375 EXPECT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::DROP_2ND);
376}
static constexpr size_t slab_size_
ClientAddressMock(RandomAPI &prng)
union ClientAddressMock::@122 addrport_
const unsigned char * get_abstract_cli_addrport(size_t &slab_size) const override
virtual ~ClientAddressMock()=default
const void * get_impl_info() const override
BufferAllocated build_third_packet_tls_auth(const ProtoSessionID &cli_psid, const ProtoSessionID &cookie_psid, std::uint32_t acked_pktid_be, std::uint32_t own_pktid_be, unsigned char ack_count, unsigned char op_field)
void SetUp() override
Time advance_clock(uint64_t binary_ms)
Time set_clock(Time setting)
ProtoContext::ProtoConfig::Ptr pcfg
ServerProto::Factory::Ptr spf
openvpn_io::io_context dummy_io_context
std::unique_ptr< PsidCookieImpl > pcookie_impl
void TearDown() override
void reset(const size_t min_capacity, const BufferFlags flags=BufAllocFlags::NO_FLAGS)
Resets the buffer with the specified minimum capacity and flags.
Definition buffer.hpp:1770
T * prepend_alloc(const size_t size)
Allocate space for prepending data to the buffer.
Definition buffer.hpp:1594
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 push_front(const T &value)
Append a T object to the array, with possible resize.
Definition buffer.hpp:1487
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 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:399
StrongRandomAPI::Ptr rng
Definition proto.hpp:359
static unsigned char op_compose(const unsigned int opcode, const unsigned int key_id)
Definition proto.hpp:316
void randomize(StrongRandomAPI &rng)
Definition psid.hpp:52
void prepend(Buffer &buf) const
Definition psid.hpp:70
Interface to communicate the server's address semantics.
Implements the PsidCookie interface.
OvpnHMACInstance::Ptr ta_hmac_recv_
bool check_session_id_hmac(const ProtoSessionID &srv_psid, const ProtoSessionID &cli_psid, const PsidCookieAddrInfoBase &pcaib)
static constexpr int SID_SIZE
ProtoContext::ProtoConfig & pcfg_
ProtoSessionID calculate_session_id_hmac(const ProtoSessionID &cli_psid, const PsidCookieAddrInfoBase &pcaib, unsigned int offset)
Calculate the psid cookie, the ProtoSessionID hmac.
static constexpr int OPCODE_SIZE
Interface to integrate this component into the server implementation.
void reset() noexcept
Points this RCPtr<T> to nullptr safely.
Definition rc.hpp:290
T * get() const noexcept
Returns the raw pointer to the object T, or nullptr.
Definition rc.hpp:321
Abstract base class for random number generators.
Definition randapi.hpp:39
void rand_fill(T &obj)
Fill a data object with random bytes.
Definition randapi.hpp:75
static TimeType now()
Definition time.hpp:302
base_type seconds_since_epoch() const
Definition time.hpp:289
constexpr BufferFlags GROW(1U<< 2)
if enabled, buffer will grow (otherwise buffer_full exception will be thrown)
Type lookup(const std::string &name)
static constexpr size_t idsize
auto f(const Thing1 t)
uint64_t a6_64[2]
struct in6_addr a6
struct in_addr a4