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
3#include <cstring>
4
8
9using namespace openvpn;
10
11
13{
15
16 ASSERT_TRUE(true);
17}
18
19// The following uland_addr46 type is a userland adaptation of an unpublished
20// ovpn_addr46 type from James Yonan's kernel work. The main idea is to create
21// a reliably hashable representation of an IP address, be it IPv4 or IPv6
22/* Discriminated union for IPv4/v6 addresses that should replace
23 ovpn_addr. The advantage of this approach over ovpn_addr is
24 better alignment/packing and potential use as an rhashtable key. */
26 /* IPv4 */
27 struct
28 {
29 /* treat as IPv4-mapped IPv6 addresses */
30 uint64_t a4_pre64; /* 0 */
31 uint32_t a4_pre32; /* htonl(0xFFFF) */
32 struct in_addr a4; /* the IPv4 address */
33 };
34
35 /* IPv6 */
36 struct in6_addr a6;
37 uint64_t a6_64[2];
38};
39
41{
42 public:
44 {
45 prng.rand_fill(addrport_);
46 }
47 const unsigned char *get_abstract_cli_addrport(size_t &slab_size) const override
48 {
49 slab_size = slab_size_;
50 return addrport_.c;
51 }
52 // unused for these tests
53 const void *get_impl_info() const override
54 {
55 return nullptr;
56 }
57
58 virtual ~ClientAddressMock() = default;
59
60 private:
61 // the detail here is not used; the slab is just randomly filled with data for the
62 // hmac; this segment is here to show the motivation for slab_size_
63 static constexpr size_t slab_size_ = sizeof(union uland_addr46) + sizeof(std::uint16_t);
64 union {
65 unsigned char c[slab_size_];
66 struct
67 {
68 union uland_addr46 oaddr46;
69 std::uint16_t port;
70 } s;
72};
73
74class PsidCookieTest : public testing::Test
75{
76 openvpn_io::io_context dummy_io_context;
79
80 protected:
83
85 : dummy_io_context(1), pcfg(new ProtoContext::ProtoConfig())
86 {
87 const std::string tls_key_fn = UNITTEST_SOURCE_DIR "/input/psid_cookie_tls.key";
88 pcfg->tls_auth_key.parse_from_file(tls_key_fn);
90 pcfg->set_tls_auth_digest(CryptoAlgs::lookup("SHA256"));
91 pcfg->now = &now;
92 pcfg->handshake_window = Time::Duration::seconds(60);
93 pcfg->key_direction = 0;
94 pcfg->rng.reset(new SSLLib::RandomAPI());
95 pcfg->prng.reset(new MTRand(2020303));
96
98 spf->proto_context_config = pcfg;
99
101 }
102
104 {
105 now = setting;
106 return setting;
107 }
108
109 Time advance_clock(uint64_t binary_ms)
110 {
111 now += Time::Duration::binary_ms(binary_ms);
112 return now;
113 }
114
115 void SetUp() override
116 {
117 }
118
119 void TearDown() override
120 {
121 }
122
123 std::unique_ptr<PsidCookieImpl> pcookie_impl;
124};
125
126
128{
129 const PsidCookieImpl *pci_dut = pcookie_impl.get();
130 ASSERT_NE(pci_dut, nullptr);
131
132 // check test clock's equivalence to the PsidCookieImpl clock
133 const Time start(set_clock(Time::now()));
134 EXPECT_TRUE(start == *pci_dut->now_);
135
136 // spot check other aspects of successful pci_dut creation
137 EXPECT_TRUE(pci_dut->pcfg_.tls_auth_key.defined());
138}
139
141{
142 PsidCookieImpl &pci_dut(*pcookie_impl.get());
143 const ClientAddressMock cli_addr(*pci_dut.pcfg_.prng);
144 ProtoSessionID cli_psid;
145 ProtoSessionID srv_psid;
146 // interval duplicates the computation in calculate_session_id_hmac()
147 const uint64_t interval = (pci_dut.pcfg_.handshake_window.raw() + 1) / 2;
148 bool hmac_ok;
149
150 cli_psid.randomize(*pci_dut.pcfg_.rng);
151
152 set_clock(Time::now());
153 srv_psid = pci_dut.calculate_session_id_hmac(cli_psid, cli_addr, 0);
154
155 // server is in the same interval in which it offered the hmac
156 hmac_ok = pci_dut.check_session_id_hmac(srv_psid, cli_psid, cli_addr);
157 EXPECT_TRUE(hmac_ok);
158
159 advance_clock(interval);
160 // server is in the next interval after which it offered the hmac
161 hmac_ok = pci_dut.check_session_id_hmac(srv_psid, cli_psid, cli_addr);
162 EXPECT_TRUE(hmac_ok);
163
164 advance_clock(interval);
165 // server is two intervals after which it offered the hmac
166 hmac_ok = pci_dut.check_session_id_hmac(srv_psid, cli_psid, cli_addr);
167 EXPECT_FALSE(hmac_ok);
168}
169
170
171// Tests that exercise PsidCookieImpl::intercept() against crafted third
172// packets of the OpenVPN 3-way handshake (the client reply to the server's
173// HARD_RESET). The cookie code only ever sees this packet when no peer
174// state exists yet, so it must positively identify the packet as the
175// handshake-completing one before letting the caller create state.
177{
178 protected:
179 // Build a complete third-packet (tls-auth path) suitable for intercept().
180 // Each on-the-wire field is parameterized so that individual tests can
181 // perturb exactly one field while leaving everything else valid.
183 const ProtoSessionID &cookie_psid,
184 std::uint32_t acked_pktid_be,
185 std::uint32_t own_pktid_be,
186 unsigned char ack_count,
187 unsigned char op_field)
188 {
190 // The server validates the incoming HMAC with ta_hmac_recv_; with
191 // pcfg_.key_direction == 0 that key differs from ta_hmac_send_'s, so
192 // we must sign the synthetic client packet with the recv key here.
193 const size_t hmac_size = pci.ta_hmac_recv_->output_size();
194
195 BufferAllocated buf;
196 buf.reset(/*headroom=*/256, /*capacity=*/512, BufAllocFlags::GROW);
197
198 // Fields are prepended in reverse on-the-wire order, mirroring how
199 // process_clients_initial_reset_tls_auth() builds the server reply.
200 buf.prepend(&own_pktid_be, sizeof(own_pktid_be));
201 cookie_psid.prepend(buf);
202 buf.prepend(&acked_pktid_be, sizeof(acked_pktid_be));
203 buf.push_front(ack_count);
204
206 pid.write_next(buf, /*prepend=*/true, pci.now_->seconds_since_epoch());
207
208 buf.prepend_alloc(hmac_size);
209 cli_psid.prepend(buf);
210 buf.push_front(op_field);
211
213 buf.size(),
215 hmac_size,
217 return buf;
218 }
219
226
228 {
231
232 Fixture f{ClientAddressMock(*pci.pcfg_.prng), {}, {}};
233 f.cli_psid.randomize(*pci.pcfg_.rng);
234 f.cookie_psid = pci.calculate_session_id_hmac(f.cli_psid, f.cli_addr, 0);
235 return f;
236 }
237};
238
240{
241 auto f = make_fixture();
242 BufferAllocated pkt = build_third_packet_tls_auth(f.cli_psid,
243 f.cookie_psid,
244 /*acked_pktid_be=*/0,
245 /*own_pktid_be=*/0,
246 /*ack_count=*/1,
248
249 EXPECT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
250 EXPECT_TRUE(pcookie_impl->get_cookie_psid().match(f.cookie_psid));
251}
252
253TEST_F(PsidCookieInterceptTest, ThirdPacketAcceptsAckedPktidOne)
254{
255 // Both acked-pktid 0 (default) and 1 are tolerated as part of the early
256 // handshake; only > 1 is treated as mid-session. This mirrors OpenVPN 2.
257 auto f = make_fixture();
258 BufferAllocated pkt = build_third_packet_tls_auth(f.cli_psid,
259 f.cookie_psid,
260 /*acked_pktid_be=*/htonl(1),
261 /*own_pktid_be=*/0,
262 /*ack_count=*/1,
264
265 EXPECT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
266}
267
268TEST_F(PsidCookieInterceptTest, ThirdPacketRejectsAckedPktidAboveOne)
269{
270 auto f = make_fixture();
271 BufferAllocated pkt = build_third_packet_tls_auth(f.cli_psid,
272 f.cookie_psid,
273 /*acked_pktid_be=*/htonl(2),
274 /*own_pktid_be=*/0,
275 /*ack_count=*/1,
277
278 EXPECT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::DROP_2ND);
279}
280
281TEST_F(PsidCookieInterceptTest, ThirdPacketAcceptsOwnPktidOne)
282{
283 auto f = make_fixture();
284 BufferAllocated pkt = build_third_packet_tls_auth(f.cli_psid,
285 f.cookie_psid,
286 /*acked_pktid_be=*/0,
287 /*own_pktid_be=*/htonl(1),
288 /*ack_count=*/1,
290
291 EXPECT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
292}
293
294TEST_F(PsidCookieInterceptTest, ThirdPacketRejectsOwnPktidAboveOne)
295{
296 auto f = make_fixture();
297 BufferAllocated pkt = build_third_packet_tls_auth(f.cli_psid,
298 f.cookie_psid,
299 /*acked_pktid_be=*/0,
300 /*own_pktid_be=*/htonl(2),
301 /*ack_count=*/1,
303
304 EXPECT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::DROP_2ND);
305}
306
307TEST_F(PsidCookieInterceptTest, ThirdPacketRejectsAckCountNotOne)
308{
309 auto f = make_fixture();
310 BufferAllocated pkt = build_third_packet_tls_auth(f.cli_psid,
311 f.cookie_psid,
312 /*acked_pktid_be=*/0,
313 /*own_pktid_be=*/0,
314 /*ack_count=*/2,
316
317 EXPECT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::DROP_2ND);
318}
319
320TEST_F(PsidCookieInterceptTest, ThirdPacketAcceptsAckV1)
321{
322 // P_ACK_V1 has no own message-id on the wire; intercept() must accept
323 // it and skip the message-id check. The packet builder still writes 4
324 // bytes for own_pktid into the buffer, but the validator's reqd_size is
325 // 4 bytes shorter for ACK_V1 so those bytes are simply ignored.
326 auto f = make_fixture();
327 BufferAllocated pkt = build_third_packet_tls_auth(f.cli_psid,
328 f.cookie_psid,
329 /*acked_pktid_be=*/0,
330 /*own_pktid_be=*/0,
331 /*ack_count=*/1,
333
334 EXPECT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
335}
336
337TEST_F(PsidCookieInterceptTest, ThirdPacketRejectsNonZeroKeyId)
338{
339 auto f = make_fixture();
340 BufferAllocated pkt = build_third_packet_tls_auth(f.cli_psid,
341 f.cookie_psid,
342 /*acked_pktid_be=*/0,
343 /*own_pktid_be=*/0,
344 /*ack_count=*/1,
346
347 EXPECT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::EARLY_DROP);
348}
349
350TEST_F(PsidCookieInterceptTest, ThirdPacketRejectsBadCookie)
351{
352 auto f = make_fixture();
353 // Tamper with the cookie psid: still valid HMAC over the packet, but
354 // the embedded server psid does not match what calculate_session_id_hmac
355 // would produce for this client.
356 ProtoSessionID bogus;
357 bogus.randomize(*pcookie_impl->pcfg_.rng);
358
359 BufferAllocated pkt = build_third_packet_tls_auth(f.cli_psid,
360 bogus,
361 /*acked_pktid_be=*/0,
362 /*own_pktid_be=*/0,
363 /*ack_count=*/1,
365
366 EXPECT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::DROP_2ND);
367}
368
369TEST_F(PsidCookieInterceptTest, ThirdPacketRejectsBadHmac)
370{
371 auto f = make_fixture();
372 BufferAllocated pkt = build_third_packet_tls_auth(f.cli_psid,
373 f.cookie_psid,
374 /*acked_pktid_be=*/0,
375 /*own_pktid_be=*/0,
376 /*ack_count=*/1,
378 // Flip a byte in the HMAC field (right after the opcode + own session id).
380
381 EXPECT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::DROP_2ND);
382}
383
386{
387 public:
388 explicit MetadataRecorder(bool accept)
389 : accept_(accept)
390 {
391 }
392
393 bool verify(int type, Buffer &metadata) const override
394 {
395 ++n_calls;
396 type_seen = type;
397 payload_seen = buf_to_string(metadata);
398 return accept_;
399 }
400
401 mutable unsigned int n_calls = 0;
402 mutable int type_seen = -2;
403 mutable std::string payload_seen;
404
405 private:
406 const bool accept_;
407};
408
417{
418 public:
420 {
421 ++n_created;
423 return last;
424 }
425
426 bool accept = true;
427 unsigned int n_created = 0;
429};
430
442{
443 protected:
445 {
447
448 // server key IDs, the way PG deploys it: the WKc names its server key by
449 // K_id, loaded from tls_crypt_v2_serverkey_dir as <2 hex digits>/<K_id>.key
450 pcfg.tls_crypt_factory.reset(new CryptoTLSCryptFactory<SSLLib::CryptoAPI>());
451 pcfg.set_tls_crypt_algs();
452 pcfg.tls_crypt_ = ProtoContext::ProtoConfig::TLSCrypt::V2;
453 pcfg.tls_crypt_v2_serverkey_id = true;
454 pcfg.tls_crypt_v2_serverkey_dir = UNITTEST_SOURCE_DIR "/../ssl";
455 pcfg.frame = frame_init_simple(2048);
456
457 // our own copy of that key, to wrap with; unwrap overwrites pcfg.tls_crypt_key
458 TLSCryptV2ServerKey server_key;
459 server_key.parse(read_text(UNITTEST_SOURCE_DIR "/../ssl/06/063FE634.key"));
460 server_key.extract_key(server_key_);
461
462 // The cookie code needs an ssl_factory for libctx() and, via mode(), for the
463 // client key's direction. No handshake happens here, but a server-mode context
464 // insists on a certificate.
465 SSLLib::SSLAPI::Config::Ptr sslcfg(new SSLLib::SSLAPI::Config());
466 sslcfg->set_mode(Mode(Mode::SERVER));
467 sslcfg->set_frame(pcfg.frame);
468 sslcfg->set_rng(pcfg.rng);
469 sslcfg->load_ca(read_text(UNITTEST_SOURCE_DIR "/../ssl/ca.crt"), true);
470 sslcfg->load_cert(read_text(UNITTEST_SOURCE_DIR "/../ssl/server.crt"));
471 sslcfg->load_private_key(read_text(UNITTEST_SOURCE_DIR "/../ssl/server.key"));
472 pcfg.ssl_factory = sslcfg->new_factory();
473
475 pcfg.tls_crypt_metadata_factory = meta_factory;
476
477 // only wanted by the tests below that drive a ProtoContext, which builds a
478 // KeyContext, which wants somewhere to derive key material from and a protocol
479 // to know whether it is reliable
481 pcfg.protocol = Protocol(Protocol::UDPv4);
482
483 // Kc, the client key the WKc wraps. Kept as raw bytes to write into the
484 // WKc plaintext, and mirrored into a static key to key the client-side
485 // tls-crypt instance with the same material.
486 pcfg.prng->rand_bytes(client_key_raw_, sizeof(client_key_raw_));
487 std::memcpy(client_key_.raw_alloc(), client_key_raw_, sizeof(client_key_raw_));
488 }
489
502 BufferAllocated make_wkc(const std::string &metadata, int metadata_type = 0x00)
503 {
504 return wrap_wkc(client_key_raw_, metadata, metadata_type);
505 }
506
510 BufferAllocated wrap_wkc(const unsigned char *kc,
511 const std::string &metadata,
512 int metadata_type,
513 size_t kc_size = OpenVPNStaticKey::KEY_SIZE)
514 {
516 const size_t hmac_size = pcfg.tls_crypt_context->digest_size();
517
518 // a single key set, so sliced without direction or mode, as unwrap does
519 TLSCryptInstance::Ptr wrap = pcfg.tls_crypt_context->new_obj_send();
520 wrap->init(pcfg.ssl_factory->libctx(),
523
524 // the encrypted part: Kc, then the metadata behind its type byte
526 inner.write(kc, kc_size);
527 if (!metadata.empty())
528 {
529 inner.push_back(static_cast<unsigned char>(metadata_type));
530 inner.write(metadata.c_str(), metadata.size());
531 }
532
533 // A WKc names its server key by K_id only where the server looks keys up that way.
534 const bool with_k_id = pcfg.tls_crypt_v2_serverkey_id;
535 const std::uint32_t k_id_be = htonl(SERVER_KEY_ID);
536 const size_t k_id_size = with_k_id ? sizeof(k_id_be) : 0;
537
538 // the trailing length counts itself, the tag, the ciphertext and K_id
539 const std::uint16_t wkc_len = static_cast<std::uint16_t>(sizeof(std::uint16_t) + hmac_size
540 + inner.size() + k_id_size);
541 const std::uint16_t wkc_len_be = htons(wkc_len);
542
543 // the tag covers the length prefix and K_id as well as the plaintext
544 BufferAllocated hmac_input(sizeof(wkc_len_be) + k_id_size + inner.size(), BufAllocFlags::GROW);
545 hmac_input.write(&wkc_len_be, sizeof(wkc_len_be));
546 if (with_k_id)
547 hmac_input.write(&k_id_be, sizeof(k_id_be));
548 hmac_input.write(inner.c_data(), inner.size());
549
551 unsigned char *tag = wkc.write_alloc(hmac_size);
552 wrap->hmac_gen(tag, 0, hmac_input.c_data(), hmac_input.size());
553
554 // the tag doubles as the CTR IV, as on the server's decrypt
555 const size_t ciphertext_bytes = wrap->encrypt(tag,
556 wkc.data() + hmac_size,
557 wkc.max_size() - hmac_size,
558 inner.c_data(),
559 inner.size());
560 wkc.inc_size(ciphertext_bytes);
561 if (with_k_id)
562 wkc.write(&k_id_be, sizeof(k_id_be));
563 wkc.write(&wkc_len_be, sizeof(wkc_len_be));
564
565 return wkc;
566 }
567
574 const ProtoSessionID &cookie_psid,
575 const BufferAllocated &wkc,
576 unsigned char op_field)
577 {
578 return wrap_third_packet(client_key_, cli_psid, cookie_psid, wkc, op_field, 0);
579 }
580
584 const ProtoSessionID &cli_psid,
585 const ProtoSessionID &cookie_psid,
586 const BufferAllocated &wkc,
587 unsigned char op_field,
588 std::uint32_t own_pktid_be)
589 {
591 const size_t hmac_size = pcfg.tls_crypt_context->digest_size();
592
593 // ENCRYPT|INVERSE, as a client slices it: the server's DECRYPT|NORMAL key set
594 TLSCryptInstance::Ptr send = pcfg.tls_crypt_context->new_obj_send();
595 send->init(pcfg.ssl_factory->libctx(),
598
599 // the layout validate_3whs_ack_payload() walks
600 BufferAllocated payload;
601 pcfg.frame->prepare(Frame::WRITE_SSL_INIT, payload);
602 payload.push_back(1); // ACK count
603 const std::uint32_t acked_pktid_be = 0;
604 payload.write(&acked_pktid_be, sizeof(acked_pktid_be));
605 cookie_psid.write(payload);
606 payload.write(&own_pktid_be, sizeof(own_pktid_be));
607
608 // header fields, prepended in reverse on-the-wire order
610 pcfg.frame->prepare(Frame::ENCRYPT_WORK, work);
611 work.prepend_alloc(hmac_size);
613 pid.write_next(work, /*prepend=*/true, pcookie_impl->now_->seconds_since_epoch());
614 cli_psid.prepend(work);
615 work.push_front(op_field);
616
617 send->hmac_gen(work.data(), TLSCryptContext::hmac_offset, payload.c_data(), payload.size());
618
619 const size_t data_offset = TLSCryptContext::hmac_offset + hmac_size;
620 const size_t encrypt_bytes = send->encrypt(work.c_data() + TLSCryptContext::hmac_offset,
621 work.data() + data_offset,
622 work.max_size() - data_offset,
623 payload.c_data(),
624 payload.size());
625 work.inc_size(encrypt_bytes);
626
627 // the WKc rides at the very end of the packet, on the opcodes that carry one
628 if (!wkc.empty())
629 work.write(wkc.c_data(), wkc.size());
630
631 return work;
632 }
633
642 std::uint32_t own_pktid_be = 0)
643 {
644 unsigned char kc_raw[OpenVPNStaticKey::KEY_SIZE];
645 pcookie_impl->pcfg_.prng->rand_bytes(kc_raw, sizeof(kc_raw));
647 std::memcpy(kc.raw_alloc(), kc_raw, sizeof(kc_raw));
648
649 ProtoSessionID cli_psid;
650 cli_psid.randomize(*pcookie_impl->pcfg_.rng);
651
652 return wrap_third_packet(kc,
653 cli_psid,
654 cookie_psid,
655 wrap_wkc(kc_raw, "v=1,type=external", 0x00),
657 own_pktid_be);
658 }
659
669 const BufferAllocated &wkc)
670 {
671 const size_t frame_size = ProtoContext::KeyContext::tls_crypt_frame_size(pcookie_impl->pcfg_);
672
673 BufferAllocated pkt(frame_size + wkc.size(), BufAllocFlags::GROW);
675 cli_psid.write(pkt);
676
677 // the packet id; its id field is what supports_early_negotiation() weighs
678 const std::uint32_t early_neg_be = htonl(ProtoContext::EARLY_NEG_START);
679 pkt.write(&early_neg_be, sizeof(early_neg_be));
680 const std::uint32_t pid_time_be = 0;
681 pkt.write(&pid_time_be, sizeof(pid_time_be));
682
683 // the hmac and payload behind it, which this path does not look at
684 while (pkt.size() < frame_size)
685 pkt.push_back(0);
686
687 pkt.write(wkc.c_data(), wkc.size());
688 return pkt;
689 }
690
692 static unsigned char wkc_v1_op_field()
693 {
695 }
696
698 static unsigned char control_v1_op_field()
699 {
701 }
702
704 static constexpr std::uint32_t SERVER_KEY_ID = 0x063FE634;
705
710};
711
712// The session created for this client strips the WKc itself, uniformly for this copy and
713// the retransmissions that follow, so intercept() must hand the packet on as it arrived.
714TEST_F(PsidCookieTlsCryptV2Test, ThirdPacketIsForwardedIntact)
715{
716 auto f = make_fixture();
717 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
718 f.cookie_psid,
719 make_wkc("v=1,type=external"),
720 wkc_v1_op_field());
721
722 const size_t wire_size = pkt.size();
723 const std::string wire = buf_to_string(pkt);
724
725 ASSERT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
726
727 EXPECT_EQ(pkt.size(), wire_size);
728 EXPECT_EQ(buf_to_string(pkt), wire);
729 // ... and the session takes the WKc off it
730 EXPECT_TRUE(ProtoContext::KeyContext::strip_resent_wkc(pkt, pcookie_impl->pcfg_));
731 EXPECT_LT(pkt.size(), wire_size);
732}
733
734// Nothing travels: the cookie layer unwrapped a key to answer the packet with and threw
735// it away, so the config every session is cloned from is as it was before the client
736// showed up, and one client's handshake cannot furnish another's session.
737TEST_F(PsidCookieTlsCryptV2Test, NothingIsLeftOnTheSharedConfig)
738{
739 auto f = make_fixture();
740 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
741 f.cookie_psid,
742 make_wkc("v=1,type=external"),
743 wkc_v1_op_field());
744
745 ASSERT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
746
747 // the shared config holds what it was configured with: the server key ID to look
748 // WKc-wrapping keys up by, and no client's key
749 EXPECT_TRUE(pcookie_impl->pcfg_.tls_crypt_v2_serverkey_id);
750 EXPECT_FALSE(pcookie_impl->pcfg_.tls_crypt_key.defined());
751 EXPECT_FALSE(spf->clone_proto_config()->tls_crypt_key.defined());
752}
753
756{
757 public:
758 size_t n_sent = 0;
759
760 private:
761 bool psid_cookie_send_const(Buffer &send_buf, const PsidCookieAddrInfoBase &pcaib) override
762 {
763 ++n_sent;
764 return true;
765 }
766};
767
770{
771 public:
772 size_t n_sent = 0;
773
774 private:
775 void control_net_send(const Buffer &net_buf) override
776 {
777 ++n_sent;
778 }
779 void control_recv(BufferPtr &&app_bp) override
780 {
781 }
782 bool supports_epoch_data() override
783 {
784 return false;
785 }
786 void active(bool primary) override
787 {
788 }
789};
790
793{
794 public:
795 std::string names;
796
797 private:
798 void error(const size_t type, const std::string *text = nullptr) override
799 {
800 if (!names.empty())
801 names += " ";
802 names += Error::name(type);
803 }
804};
805
813{
818
820 : proto(&cb, cfg, stats)
821 {
822 proto.reset(cookie_psid);
823 proto.start(cookie_psid);
824 proto.flush(true);
825 cb.n_sent = 0; // count only what the packets below draw out
826 }
827
829 size_t recv(const BufferAllocated &pkt)
830 {
831 const size_t before = cb.n_sent;
832
835 if (pt.is_control())
836 proto.control_net_recv(pt, std::move(bp));
837 proto.flush(true);
838
839 return cb.n_sent - before;
840 }
841};
842
843// The point of the whole arrangement: the session keys its control channel off the WKc
844// still riding on the packet it is handed, having been given no key by anyone. It accepts
845// the packet, which it could only do with the right key, and answers it.
846TEST_F(PsidCookieTlsCryptV2Test, SessionDerivesItsKeyFromTheWkcOnItsFirstPacket)
847{
848 auto f = make_fixture();
849 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
850 f.cookie_psid,
851 make_wkc("v=1,type=external"),
852 wkc_v1_op_field());
853
854 ASSERT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
855 // the cookie layer unwrapped the WKc to answer the packet, and judged nothing
856 ASSERT_EQ(meta_factory->n_created, 0u);
857
858 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
859 EXPECT_GT(session.recv(pkt), 0u);
860
861 // One hook for the client, made where the record is judged and shown it once
862 EXPECT_EQ(meta_factory->n_created, 1u);
863 EXPECT_EQ(meta_factory->last->n_calls, 1u);
864}
865
866TEST_F(PsidCookieTlsCryptV2Test, ThirdPacketParsesWKcMetadata)
867{
868 auto f = make_fixture();
869 const std::string metadata = "v=1,type=external,sn=04:e3,time=1750000000,tenant=acme";
870
871 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
872 f.cookie_psid,
873 make_wkc(metadata),
874 wkc_v1_op_field());
875
876 ASSERT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
877 EXPECT_TRUE(pcookie_impl->get_cookie_psid().match(f.cookie_psid));
878
879 // the record reaches the hook from the session's own unwrap of the same WKc, once
880 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
881 ASSERT_GT(session.recv(pkt), 0u);
882
883 ASSERT_EQ(meta_factory->n_created, 1u);
884 ASSERT_TRUE(meta_factory->last);
885 EXPECT_EQ(meta_factory->last->n_calls, 1u);
886 EXPECT_EQ(meta_factory->last->type_seen, 0x00);
887 EXPECT_EQ(meta_factory->last->payload_seen, metadata);
888}
889
890// A WKc that does not unwrap keys nothing, so the packet it rode in on is dropped rather
891// than answered -- and the handshake is not left half-converted, see
892// ProtoContext::KeyContext::tls_crypt_v2_wanted().
893TEST_F(PsidCookieTlsCryptV2Test, SessionDropsAPacketWhoseWkcDoesNotUnwrap)
894{
895 auto f = make_fixture();
896 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
897 f.cookie_psid,
898 make_wkc("v=1,type=external"),
899 wkc_v1_op_field());
900
901 ASSERT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
902
903 // flip a bit in the WKc ciphertext, past the tls-crypt frame the session decapsulates
904 pkt.data()[pkt.size() - 32] ^= 0x01;
905
906 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
907 EXPECT_EQ(session.recv(pkt), 0u);
908}
909
916{
917 protected:
919 {
921 pcfg.tls_crypt_v2_serverkey_id = false;
922 pcfg.tls_crypt_v2_serverkey_dir.clear();
923 pcfg.tls_crypt_key = server_key_;
924 }
925};
926
927// Keying the server context was the caller's job in this mode, and only
928// ProtoContext::reset_tls_crypt_server() did it. The cookie layer hands over a context
929// straight from new_obj_recv(), so the first client packet threw ovpn_tls_crypt_wrong_mode
930// out of intercept() and into the embedder's packet loop.
931TEST_F(PsidCookieSingleServerKeyTest, ThirdPacketUnwrapsWithNoServerKeyIdOnTheWire)
932{
933 auto f = make_fixture();
934 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
935 f.cookie_psid,
936 make_wkc("v=1,type=external"),
937 wkc_v1_op_field());
938
939 EXPECT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
940}
941
942// ... and the session behind it keys itself from the same WKc, with no key directory to
943// consult and no K_id to consult it with.
944TEST_F(PsidCookieSingleServerKeyTest, SessionDerivesItsKeyWithNoServerKeyId)
945{
946 auto f = make_fixture();
947 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
948 f.cookie_psid,
949 make_wkc("v=1,type=external"),
950 wkc_v1_op_field());
951
952 ASSERT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
953
954 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
955 EXPECT_GT(session.recv(pkt), 0u);
956 EXPECT_EQ(meta_factory->n_created, 1u);
957}
958
959// The client key is the one part of a WKc that has to be there, and the check for it counted
960// the length prefix and the K_id along with it -- neither of which is key material, and both
961// advanced past before the key is read. A WKc short by those six bytes passed the check,
962// passed the tag comparison behind it, and underflowed the read. Only a holder of the server
963// key can wrap one, so this is a key generator's mistake, but it arrives as an exception.
964TEST_F(PsidCookieTlsCryptV2Test, WkcWrappingTooLittleKeyIsRejectedRatherThanThrown)
965{
966 auto f = make_fixture();
967
968 // short by the 2-byte length prefix and the 4-byte K_id in front of the key
969 const size_t short_kc = OpenVPNStaticKey::KEY_SIZE - sizeof(std::uint16_t) - sizeof(std::uint32_t);
970 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
971 f.cookie_psid,
972 wrap_wkc(client_key_raw_, "", 0x00, short_kc),
973 wkc_v1_op_field());
974
975 PsidCookie::Intercept ret = PsidCookie::Intercept::HANDLE_2ND;
976 ASSERT_NO_THROW(ret = pcookie_impl->intercept(pkt, f.cli_addr));
977 EXPECT_NE(ret, PsidCookie::Intercept::HANDLE_2ND);
978
979 // and the unwrap leaves its out parameter untouched, as it promises: raw_alloc() defines
980 // the key before the read that fills it, so a throw there left behind a client key that
981 // had never been unwrapped
982 BufferAllocated again = build_third_packet_tls_crypt_v2(f.cli_psid,
983 f.cookie_psid,
984 wrap_wkc(client_key_raw_, "", 0x00, short_kc),
985 wkc_v1_op_field());
986 TLSCryptInstance::Ptr server = pcookie_impl->pcfg_.tls_crypt_context->new_obj_recv();
988 EXPECT_NE(ProtoContext::KeyContext::unwrap_tls_crypt_wkc(again, pcookie_impl->pcfg_, *server, unwrapped),
990 EXPECT_FALSE(unwrapped.client_key.defined());
991}
992
993// Answering the first packet means unwrapping the WKc on it, and the unwrap trims the WKc off
994// the buffer it is handed -- so this returned the caller a packet shorter than the one it
995// passed in. Nothing forwards this packet today; anything that did would hand on a packet
996// with no WKc, and a session keyed from that can never key itself.
997TEST_F(PsidCookieTlsCryptV2Test, FirstPacketIsLeftAsItArrived)
998{
1000 pcookie_impl->provide_psid_cookie_transport(transport);
1001
1002 auto f = make_fixture();
1003 BufferAllocated pkt = build_first_packet_tls_crypt_v2(f.cli_psid, make_wkc("v=1,type=external"));
1004
1005 const size_t wire_size = pkt.size();
1006 const std::string wire = buf_to_string(pkt);
1007
1008 EXPECT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_1ST);
1009 EXPECT_EQ(transport->n_sent, 1u);
1010
1011 EXPECT_EQ(pkt.size(), wire_size);
1012 EXPECT_EQ(buf_to_string(pkt), wire);
1013}
1014
1022{
1023 protected:
1025 {
1026 pcookie_impl->pcfg_.tls_auth_key.erase();
1027 }
1028};
1029
1030// The first packet a pre-filtering embedder sees is the one the receive context is keyed
1031// from, so there is no key to check it with. Only CONTROL_HARD_RESET_CLIENT_V3 was exempt,
1032// not the CONTROL_WKC_V1 a psid cookie layer leaves the session to key itself from.
1033TEST_F(PsidCookieTlsCryptV2OnlyTest, ControlNetValidateAcceptsTheWkcBearingPacket)
1034{
1035 auto f = make_fixture();
1036 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
1037 f.cookie_psid,
1038 make_wkc("v=1,type=external"),
1039 wkc_v1_op_field());
1040
1041 ASSERT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
1042
1043 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
1044
1046 const ProtoContext::PacketType pt = session.proto.packet_type(*bp);
1047 ASSERT_TRUE(pt.is_control());
1048 EXPECT_TRUE(session.proto.control_net_validate(pt, *bp));
1049
1050 // the pre-filter and the path it filters for have to agree
1051 EXPECT_GT(session.recv(pkt), 0u);
1052}
1053
1054// The same packet on a server holding a tls-auth key as well -- what PG deploys. Such a
1055// session is still TLS_AUTH when the pre-filter runs, since only decapsulate() converts it,
1056// so the exemption has to be recognised in that arm too.
1057TEST_F(PsidCookieTlsCryptV2Test, ControlNetValidateAcceptsTheWkcBearingPacketOnATlsAuthServer)
1058{
1059 auto f = make_fixture();
1060 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
1061 f.cookie_psid,
1062 make_wkc("v=1,type=external"),
1063 wkc_v1_op_field());
1064
1065 ASSERT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
1066
1067 // the premise: this fixture keeps the tls-auth key
1068 ASSERT_TRUE(pcookie_impl->pcfg_.tls_auth_enabled());
1069
1070 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
1071
1073 const ProtoContext::PacketType pt = session.proto.packet_type(*bp);
1074 ASSERT_TRUE(pt.is_control());
1075 EXPECT_TRUE(session.proto.control_net_validate(pt, *bp));
1076
1077 // the pre-filter and the path it filters for have to agree
1078 EXPECT_GT(session.recv(pkt), 0u);
1079}
1080
1081// The exemption follows the opcode, not the mode: one carrying no WKc keys nothing and stays
1082// judged with the tls-auth key. Same packet as above but for its op field.
1083TEST_F(PsidCookieTlsCryptV2Test, ControlNetValidateStillJudgesNonWkcOpcodesOnATlsAuthServer)
1084{
1085 auto f = make_fixture();
1086 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
1087 f.cookie_psid,
1088 make_wkc("v=1,type=external"),
1089 control_v1_op_field());
1090
1091 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
1092
1094 const ProtoContext::PacketType pt = session.proto.packet_type(*bp);
1095 ASSERT_TRUE(pt.is_control());
1096
1097 // tls-crypt wrapped, so the tls-auth key cannot vouch for it -- and nothing exempts it
1098 EXPECT_FALSE(session.proto.control_net_validate(pt, *bp));
1099}
1100
1107{
1108 protected:
1110 {
1112 pcfg.tls_auth_key.erase();
1113 pcfg.tls_crypt_ = ProtoContext::ProtoConfig::TLSCrypt::V1;
1114 pcfg.tls_crypt_v2_serverkey_id = false;
1115 pcfg.tls_crypt_v2_serverkey_dir.clear();
1116 pcfg.tls_crypt_key = server_key_;
1117 }
1118
1120 BufferAllocated v1_packet(const ProtoSessionID &cli_psid, const ProtoSessionID &srv_psid)
1121 {
1122 return wrap_third_packet(server_key_, cli_psid, srv_psid, BufferAllocated(), control_v1_op_field(), 0);
1123 }
1124};
1125
1126// A pre-filter's verdict must not depend on what it was handed before. It pinned the peer
1127// psid from whatever packet reached it first, outside accept_peer(), so a holder of the
1128// shared key could pin itself from a spoofed datagram and lock the real client out.
1129TEST_F(TlsCryptV1SessionTest, ControlNetValidateDoesNotPinThePeer)
1130{
1131 auto f = make_fixture();
1132 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
1133
1134 ProtoSessionID other_psid;
1135 other_psid.randomize(*pcookie_impl->pcfg_.rng);
1136
1137 auto validate = [&](const BufferAllocated &pkt)
1138 {
1139 BufferPtr bp = BufferAllocatedRc::Create(pkt.c_data(), pkt.size(), BufAllocFlags::GROW);
1140 const ProtoContext::PacketType pt = session.proto.packet_type(*bp);
1141 return pt.is_control() && session.proto.control_net_validate(pt, *bp);
1142 };
1143
1144 // whichever arrives first, both are packets the pre-filter has no peer to judge against
1145 EXPECT_TRUE(validate(v1_packet(other_psid, f.cookie_psid)));
1146 EXPECT_TRUE(validate(v1_packet(f.cli_psid, f.cookie_psid)));
1147}
1148
1155{
1156 protected:
1158 {
1160 pcfg.tls_crypt_ = ProtoContext::ProtoConfig::TLSCrypt::None;
1161 pcfg.tls_crypt_key.erase();
1162 pcfg.tls_crypt_v2_serverkey_id = false;
1163 pcfg.tls_crypt_v2_serverkey_dir.clear();
1164 }
1165};
1166
1167// match() is false against an undefined psid, so the pre-filter turned away every packet
1168// until a peer was pinned -- including the one that would have pinned it.
1169TEST_F(TlsAuthSessionTest, ControlNetValidateAcceptsAPacketBeforeAPeerIsPinned)
1170{
1171 auto f = make_fixture();
1172 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
1173
1174 BufferAllocated pkt = build_third_packet_tls_auth(f.cli_psid,
1175 f.cookie_psid,
1176 /*acked_pktid_be=*/0,
1177 /*own_pktid_be=*/0,
1178 /*ack_count=*/1,
1180
1182 const ProtoContext::PacketType pt = session.proto.packet_type(*bp);
1183 ASSERT_TRUE(pt.is_control());
1184 EXPECT_TRUE(session.proto.control_net_validate(pt, *bp));
1185}
1186
1187// intercept() turns away only an empty datagram, so every one whose first byte carries a
1188// CONTROL_HARD_RESET_CLIENT_V3 opcode reaches the tls-crypt arm -- which read a psid and a
1189// packet id off it before establishing there was that much packet. Two bytes from anywhere
1190// threw buffer_underflow out of intercept(). The tls-auth sibling has always checked.
1191TEST_F(PsidCookieTlsCryptV2Test, ShortInitialResetIsDroppedRatherThanThrown)
1192{
1193 auto f = make_fixture();
1194
1195 for (size_t size = 1; size <= 96; ++size)
1196 {
1198 pkt.set_size(size);
1200
1201 // where there is room, claim early-negotiation support so the arm carries on into the
1202 // WKc unwrap instead of declining at the flag; both outcomes are wanted
1203 if (size >= 1 + ProtoSessionID::SIZE + sizeof(std::uint32_t))
1204 {
1205 const std::uint32_t early_neg_be = htonl(ProtoContext::EARLY_NEG_START);
1206 std::memcpy(pkt.data() + 1 + ProtoSessionID::SIZE, &early_neg_be, sizeof(early_neg_be));
1207 }
1208
1209 PsidCookie::Intercept ret = PsidCookie::Intercept::HANDLE_2ND;
1210 ASSERT_NO_THROW(ret = pcookie_impl->intercept(pkt, f.cli_addr)) << "size " << size;
1211 EXPECT_NE(ret, PsidCookie::Intercept::HANDLE_2ND) << "size " << size;
1212 }
1213}
1214
1215// A WKc that unwraps says only that this server issued the key inside it, which is true of
1216// every client's WKc -- so a packet that unwraps one and then fails to authenticate under
1217// it must leave no trace. Were its key kept, the genuine packet arriving next would find
1218// the session already keyed and be decrypted with a stranger's key.
1219TEST_F(PsidCookieTlsCryptV2Test, SessionKeepsNoKeyFromAPacketThatFailedToAuthenticate)
1220{
1221 auto f = make_fixture();
1222 BufferAllocated good = build_third_packet_tls_crypt_v2(f.cli_psid,
1223 f.cookie_psid,
1224 make_wkc("v=1,type=external"),
1225 wkc_v1_op_field());
1226
1227 ASSERT_EQ(pcookie_impl->intercept(good, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
1228
1229 // What an off-path attacker spoofing this client's address can put together: a WKc of
1230 // its own, which unwraps under the server key like any other, on a frame it cannot
1231 // wrap with the key that WKc carries.
1232 unsigned char foreign_key_raw[OpenVPNStaticKey::KEY_SIZE];
1233 pcookie_impl->pcfg_.prng->rand_bytes(foreign_key_raw, sizeof(foreign_key_raw));
1234 BufferAllocated spoofed = build_third_packet_tls_crypt_v2(f.cli_psid,
1235 f.cookie_psid,
1236 wrap_wkc(foreign_key_raw, "v=1,type=external", 0x00),
1237 wkc_v1_op_field());
1238
1239 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
1240 EXPECT_EQ(session.recv(spoofed), 0u);
1241
1242 // one datagram, and the client can still connect
1243 EXPECT_GT(session.recv(good), 0u);
1244}
1245
1246// Authenticating at the tls-crypt layer only says the sender holds the Kc it supplied its
1247// own WKc for, which every client of this server can do. So a packet rejected after that --
1248// here on the psid the cookie layer issued, which the sender cannot know -- must leave the
1249// peer unpinned. Pinned, it closes tls_crypt_v2_wanted()'s conversion window for good and
1250// the real client's packets are checked as tls-auth from then on.
1251TEST_F(PsidCookieTlsCryptV2Test, SessionKeepsNoPeerPsidFromAPacketThatFailedToAuthenticate)
1252{
1253 auto f = make_fixture();
1254 BufferAllocated good = build_third_packet_tls_crypt_v2(f.cli_psid,
1255 f.cookie_psid,
1256 make_wkc("v=1,type=external"),
1257 wkc_v1_op_field());
1258
1259 ASSERT_EQ(pcookie_impl->intercept(good, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
1260
1261 ProtoSessionID bogus_cookie_psid;
1262 bogus_cookie_psid.randomize(*pcookie_impl->pcfg_.rng);
1263
1264 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
1265 EXPECT_EQ(session.recv(build_foreign_third_packet(bogus_cookie_psid)), 0u);
1266
1267 // one datagram, and the client can still connect
1268 EXPECT_GT(session.recv(good), 0u);
1269}
1270
1271// The other side of that coin: a packet of the peer's own that yields no message must keep
1272// the key it unwrapped, because the packet after it need not carry a WKc to re-key from.
1273// Here the first packet is refused by the reliable receive window, and the CONTROL_V1 behind
1274// it has no WKc of its own -- it can only be read with the key the refused packet left.
1275TEST_F(PsidCookieTlsCryptV2Test, SessionKeepsTheKeyOfItsPeersPacketThatYieldedNoMessage)
1276{
1277 auto f = make_fixture();
1278 BufferAllocated good = build_third_packet_tls_crypt_v2(f.cli_psid,
1279 f.cookie_psid,
1280 make_wkc("v=1,type=external"),
1281 wkc_v1_op_field());
1282
1283 ASSERT_EQ(pcookie_impl->intercept(good, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
1284
1285 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
1286
1287 // the client's own third packet, numbered far outside the reliable receive window
1288 session.recv(wrap_third_packet(client_key_,
1289 f.cli_psid,
1290 f.cookie_psid,
1291 make_wkc("v=1,type=external"),
1292 wkc_v1_op_field(),
1293 htonl(5000)));
1294
1295 BufferAllocated no_wkc = wrap_third_packet(client_key_,
1296 f.cli_psid,
1297 f.cookie_psid,
1299 control_v1_op_field(),
1300 0);
1301 session.rec->names.clear();
1302 session.recv(no_wkc);
1303
1304 // CC_ERROR here would be the session finding no key at all, HMAC_ERROR a key that is
1305 // not this client's
1306 EXPECT_EQ(session.rec->names, "");
1307}
1308
1309// The session's hook is its own: it judges the record whether or not a cookie layer ever
1310// saw the client, which is how a session reached over TCP -- kotcp.hpp creates those with
1311// no cookie layer at all -- comes by its verdict.
1312TEST_F(PsidCookieTlsCryptV2Test, SessionWithoutACookieLayerJudgesTheRecordItself)
1313{
1314 auto f = make_fixture();
1315 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
1316 f.cookie_psid,
1317 make_wkc("v=1,type=external"),
1318 wkc_v1_op_field());
1319
1320 // the packet reaches the session without passing intercept() first
1321 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
1322 EXPECT_GT(session.recv(pkt), 0u);
1323
1324 EXPECT_EQ(meta_factory->n_created, 1u);
1325 EXPECT_EQ(meta_factory->last->n_calls, 1u);
1326}
1327
1328// Unwrapping a WKc says this server issued it, not that the sender holds the Kc inside: a
1329// captured one unwraps just the same. The hook ran anyway, and since a failed packet leaves
1330// the session un-keyed, an unauthenticated sender could drive it again and again.
1331TEST_F(PsidCookieTlsCryptV2Test, UnauthenticatedWkcRunsNoMetadataHook)
1332{
1333 auto f = make_fixture();
1334
1335 // a WKc this server unwraps, on a frame not wrapped with the Kc it carries
1336 unsigned char foreign_key_raw[OpenVPNStaticKey::KEY_SIZE];
1337 pcookie_impl->pcfg_.prng->rand_bytes(foreign_key_raw, sizeof(foreign_key_raw));
1338 BufferAllocated spoofed = build_third_packet_tls_crypt_v2(f.cli_psid,
1339 f.cookie_psid,
1340 wrap_wkc(foreign_key_raw, "v=1,type=ATTACKER", 0x00),
1341 wkc_v1_op_field());
1342
1343 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
1344
1345 EXPECT_EQ(session.recv(spoofed), 0u);
1346 EXPECT_EQ(meta_factory->n_created, 0u);
1347
1348 // un-keyed again, so the second attempt judges no more than the first
1349 EXPECT_EQ(session.recv(spoofed), 0u);
1350 EXPECT_EQ(meta_factory->n_created, 0u);
1351 EXPECT_FALSE(meta_factory->last);
1352
1353 // the real client still connects, its record seen once
1354 BufferAllocated good = build_third_packet_tls_crypt_v2(f.cli_psid,
1355 f.cookie_psid,
1356 make_wkc("v=1,type=external"),
1357 wkc_v1_op_field());
1358 EXPECT_GT(session.recv(good), 0u);
1359 EXPECT_EQ(meta_factory->n_created, 1u);
1360 EXPECT_EQ(meta_factory->last->payload_seen, "v=1,type=external");
1361}
1362
1363// The factory is optional: TLSCryptMetadata::verify() accepts by default, so an embedder
1364// with nothing to check has no reason to configure one. A session without it makes no
1365// handler, runs no hook, and comes up all the same.
1366TEST_F(PsidCookieTlsCryptV2Test, SessionWithoutMetadataFactoryMakesNoHandler)
1367{
1368 auto f = make_fixture();
1369 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
1370 f.cookie_psid,
1371 make_wkc("v=1,type=external"),
1372 wkc_v1_op_field());
1373
1374 pcookie_impl->pcfg_.tls_crypt_metadata_factory.reset();
1375 ASSERT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
1376
1377 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
1378 EXPECT_GT(session.recv(pkt), 0u);
1379 EXPECT_EQ(meta_factory->n_created, 0u);
1380}
1381
1382TEST_F(PsidCookieTlsCryptV2Test, TimestampMetadataIsNotUserMetadata)
1383{
1384 // stock tls-crypt-v2-genkey's default: a timestamp, type 0x01. The hook still runs, and
1385 // sees the type byte saying this is not a CSV record.
1386 auto f = make_fixture();
1387 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
1388 f.cookie_psid,
1389 make_wkc(std::string("\x68\x74\x9f\x00", 4), 0x01),
1390 wkc_v1_op_field());
1391
1392 ASSERT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
1393
1394 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
1395 ASSERT_GT(session.recv(pkt), 0u);
1396
1397 ASSERT_TRUE(meta_factory->last);
1398 EXPECT_EQ(meta_factory->last->n_calls, 1u);
1399 EXPECT_EQ(meta_factory->last->type_seen, 0x01);
1400}
1401
1402TEST_F(PsidCookieTlsCryptV2Test, WkcWithoutMetadataReportsNoMetadata)
1403{
1404 auto f = make_fixture();
1405 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
1406 f.cookie_psid,
1407 make_wkc(""),
1408 wkc_v1_op_field());
1409
1410 ASSERT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
1411
1412 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
1413 ASSERT_GT(session.recv(pkt), 0u);
1414
1415 ASSERT_TRUE(meta_factory->last);
1416 EXPECT_EQ(meta_factory->last->n_calls, 1u);
1417 EXPECT_EQ(meta_factory->last->type_seen, -1);
1418}
1419
1420// verify() belongs to an embedder, and a bad cookie is all it takes to aim a packet at this
1421// layer, so nothing a stranger sends may reach the hook. Since the cookie layer judges
1422// nothing at all, a dropped third packet cannot: it never becomes the session that would ask.
1423TEST_F(PsidCookieTlsCryptV2Test, DroppedThirdPacketRunsNoMetadataHook)
1424{
1425 auto f = make_fixture();
1426 ProtoSessionID bogus;
1427 bogus.randomize(*pcookie_impl->pcfg_.rng);
1428
1429 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
1430 bogus,
1431 make_wkc("v=1,type=external"),
1432 wkc_v1_op_field());
1433
1434 EXPECT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::DROP_2ND);
1435 // no handler was made for it, so nothing saw the record ...
1436 EXPECT_EQ(meta_factory->n_created, 0u);
1437 EXPECT_FALSE(meta_factory->last);
1438}
1439
1440// A failed verify() rejects the client, and the session is where that is decided: the packet
1441// is dropped and the rejection counted. PG's hook never does this -- its metadata is
1442// informational -- but the plumbing honours it.
1443TEST_F(PsidCookieTlsCryptV2Test, PacketRejectedByMetadataHookIsDroppedAndCounted)
1444{
1445 auto f = make_fixture();
1446 meta_factory->accept = false;
1447
1448 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
1449 f.cookie_psid,
1450 make_wkc("v=1,type=external"),
1451 wkc_v1_op_field());
1452
1453 // the cookie layer answers it either way; judging is not its business
1454 ASSERT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
1455
1456 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
1457 EXPECT_EQ(session.recv(pkt), 0u);
1458 EXPECT_EQ(session.rec->names, "TLS_CRYPT_META_FAIL");
1459}
static constexpr size_t slab_size_
ClientAddressMock(RandomAPI &prng)
union ClientAddressMock::@119 addrport_
const unsigned char * get_abstract_cli_addrport(size_t &slab_size) const override
virtual ~ClientAddressMock()=default
const void * get_impl_info() const override
The hook an embedder installs, standing in for PG's.
bool accept
verify() verdict handed to the recorders produced here
RCPtr< MetadataRecorder > last
TLSCryptMetadata::Ptr new_obj() override
Records what unwrap_tls_crypt_wkc() handed the hook, and answers with accept.
const bool accept_
what verify() returns
int type_seen
-1 would mean "WKc carried no metadata"
MetadataRecorder(bool accept)
bool verify(int type, Buffer &metadata) const override
A ProtoContext needs one of these; it counts what the session puts on the wire.
bool supports_epoch_data() override
void control_net_send(const Buffer &net_buf) override
void active(bool primary) override
Called when KeyContext transitions to ACTIVE state.
void control_recv(BufferPtr &&app_bp) 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)
A server with one tls-crypt-v2 key in its config for every client.
void SetUp() override
Time advance_clock(uint64_t binary_ms)
Time set_clock(Time setting)
ProtoContext::ProtoConfig::Ptr pcfg
ServerProto::Factory::Ptr spf
the server factory each session's ProtoConfig is cloned from
openvpn_io::io_context dummy_io_context
std::unique_ptr< PsidCookieImpl > pcookie_impl
void TearDown() override
A server holding a tls-crypt-v2 key and no tls-auth one.
Tests for the tls-crypt-v2 arm of intercept()
static unsigned char control_v1_op_field()
the opcode of the packets that follow, which carry no WKc to key a session from
BufferAllocated wrap_third_packet(const OpenVPNStaticKey &kc, const ProtoSessionID &cli_psid, const ProtoSessionID &cookie_psid, const BufferAllocated &wkc, unsigned char op_field, std::uint32_t own_pktid_be)
BufferAllocated make_wkc(const std::string &metadata, int metadata_type=0x00)
Build the WKc a client appends to its handshake packets.
OpenVPNStaticKey server_key_
Ka/Ke, used here to wrap the WKc.
static unsigned char wkc_v1_op_field()
the opcode of the third packet, the one whose WKc keys the session behind it
BufferAllocated build_third_packet_tls_crypt_v2(const ProtoSessionID &cli_psid, const ProtoSessionID &cookie_psid, const BufferAllocated &wkc, unsigned char op_field)
Build the tls-crypt-v2 third packet of the 3-way handshake: a CONTROL_WKC_V1 wrapped with Kc,...
RCPtr< MetadataRecorderFactory > meta_factory
OpenVPNStaticKey client_key_
Kc, as the client keys its tls-crypt instance.
BufferAllocated wrap_wkc(const unsigned char *kc, const std::string &metadata, int metadata_type, size_t kc_size=OpenVPNStaticKey::KEY_SIZE)
BufferAllocated build_first_packet_tls_crypt_v2(const ProtoSessionID &cli_psid, const BufferAllocated &wkc)
Build the tls-crypt-v2 first handshake packet: a CONTROL_HARD_RESET_CLIENT_V3 with the WKc behind the...
unsigned char client_key_raw_[OpenVPNStaticKey::KEY_SIZE]
BufferAllocated build_foreign_third_packet(const ProtoSessionID &cookie_psid, std::uint32_t own_pktid_be=0)
The handshake packet a client of this server can forge for another's address.
static constexpr std::uint32_t SERVER_KEY_ID
K_id of test/ssl/06/063FE634.key, the server key the WKc names.
Names every error the session reports, so a test can say why a packet was dropped.
void error(const size_t type, const std::string *text=nullptr) override
Stands in for the transport the cookie layer answers a first packet through.
bool psid_cookie_send_const(Buffer &send_buf, const PsidCookieAddrInfoBase &pcaib) override
A plain tls-auth server: no tls-crypt of either version.
A tls-crypt v1 server: one key shared with every client, no tls-auth, no WKc.
BufferAllocated v1_packet(const ProtoSessionID &cli_psid, const ProtoSessionID &srv_psid)
A CONTROL_V1 frame from cli_psid, wrapped with the shared key and echoing srv_psid.
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:1773
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:1597
void inc_size(const size_t delta)
Increment the size of the array (usually used in a similar context to set_size such as after mutable_...
Definition buffer.hpp:1390
size_t max_size() const
Return the maximum allowable size value in T objects given the current offset (without considering re...
Definition buffer.hpp:1375
void push_back(const T &value)
Append a T object to the end of the array, resizing the array if necessary.
Definition buffer.hpp:1480
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:1574
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:1448
bool empty() const
Returns true if the buffer is empty.
Definition buffer.hpp:1235
void write(const T *data, const size_t size)
Write data to the buffer.
Definition buffer.hpp:1561
void push_front(const T &value)
Append a T object to the array, with possible resize.
Definition buffer.hpp:1488
void set_size(const size_t size)
After an external method, operating on the array as a mutable unsigned char buffer,...
Definition buffer.hpp:1382
unsigned char * raw_alloc()
StaticKey slice(unsigned int key_specifier) const
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)
static bool strip_resent_wkc(Buffer &recv, const ProtoConfig &proto_config)
Virtually remove a resent WKc from the end of a CONTROL_WKC_V1 packet.
Definition proto.hpp:3108
static size_t tls_crypt_frame_size(const ProtoConfig &proto_config)
Smallest tls-crypt frame a WKc can be appended to.
Definition proto.hpp:2834
static Error::Type unwrap_tls_crypt_wkc(Buffer &recv, const ProtoConfig &proto_config, TLSCryptInstance &tls_crypt_server, UnwrappedWkc &unwrapped)
Extract and process the TLS crypt WKc information.
Definition proto.hpp:2926
OpenVPNStaticKey tls_auth_key
leave this undefined to disable tls_auth
Definition proto.hpp:425
StrongRandomAPI::Ptr rng
Definition proto.hpp:385
bool control_net_recv(const PacketType &type, BufferPtr &&net_bp)
Definition proto.hpp:4967
void flush(const bool control_channel)
Definition proto.hpp:4894
PacketType packet_type(const Buffer &buf)
Definition proto.hpp:4844
static unsigned char op_compose(const unsigned int opcode, const unsigned int key_id)
Definition proto.hpp:326
bool control_net_validate(const PacketType &type, const Buffer &net_buf)
Definition proto.hpp:4961
static constexpr PacketIDControl::id_t EARLY_NEG_START
Definition proto.hpp:308
void reset(const ProtoSessionID cookie_psid=ProtoSessionID())
Resets ProtoContext *this to it's initial state.
Definition proto.hpp:4733
void start(const ProtoSessionID cookie_psid=ProtoSessionID())
Initialize the state machine and start protocol negotiation.
Definition proto.hpp:4857
void write(Buffer &buf) const
Definition psid.hpp:65
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 provide access to the server's transport capability.
Interface to integrate this component into the server implementation.
Intercept
Values returned by the intercept() function.
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 Ptr Create(ArgsT &&...args)
Creates a new instance of RcEnable with the given arguments.
Definition make_rc.hpp:43
constexpr static const size_t hmac_offset
virtual bool hmac_gen(unsigned char *header, const size_t header_len, const unsigned char *payload, const size_t payload_len)=0
virtual size_t encrypt(const unsigned char *iv, unsigned char *out, const size_t olen, const unsigned char *in, const size_t ilen)=0
virtual void init(SSLLib::Ctx libctx, const StaticKey &key_hmac, const StaticKey &key_crypt)=0
void extract_key(OpenVPNStaticKey &tls_key)
void parse(const std::string &key_text)
static TimeType now()
Definition time.hpp:302
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)
constexpr BufferFlags GROW(1U<< 2)
if enabled, buffer will grow (otherwise buffer_full exception will be thrown)
constexpr BufferFlags CONSTRUCT_ZERO(1U<< 0)
if enabled, constructors/init will zero allocated space
Type lookup(const std::string &name)
const char * name(const size_t type)
Definition error.hpp:117
Frame::Ptr frame_init_simple(const size_t payload)
std::string read_text(const std::string &filename, const std::uint64_t max_size=0)
Definition file.hpp:127
BufferAllocatedType< unsigned char > BufferAllocated
Definition buffer.hpp:1897
std::string buf_to_string(const Buffer &buf)
Definition bufstr.hpp:22
The session kotun.hpp creates once intercept() has returned HANDLE_2ND.
CookieSession(const ProtoContext::ProtoConfig::Ptr &cfg, const ProtoSessionID &cookie_psid)
RCPtr< RecordingStats > rec
SessionStats::Ptr stats
size_t recv(const BufferAllocated &pkt)
NullProtoCallback cb
static constexpr size_t idsize
What a WKc yields, owned by whoever asked for the unwrap.
Definition proto.hpp:2911
OpenVPNStaticKey client_key
Kc, the client key the WKc wrapped.
Definition proto.hpp:2913
auto f(const Thing1 t)
void validate(const ValidateCreds::Type type, const bool expected_result, const std::string &cred, const bool strict)
uint64_t a6_64[2]
struct in6_addr a6
struct in_addr a4