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 server_key_.parse(read_text(UNITTEST_SOURCE_DIR "/../ssl/06/063FE634.key"));
459
460 // The cookie code needs an ssl_factory for libctx() and, via mode(), for the
461 // client key's direction. No handshake happens here, but a server-mode context
462 // insists on a certificate.
463 SSLLib::SSLAPI::Config::Ptr sslcfg(new SSLLib::SSLAPI::Config());
464 sslcfg->set_mode(Mode(Mode::SERVER));
465 sslcfg->set_frame(pcfg.frame);
466 sslcfg->set_rng(pcfg.rng);
467 sslcfg->load_ca(read_text(UNITTEST_SOURCE_DIR "/../ssl/ca.crt"), true);
468 sslcfg->load_cert(read_text(UNITTEST_SOURCE_DIR "/../ssl/server.crt"));
469 sslcfg->load_private_key(read_text(UNITTEST_SOURCE_DIR "/../ssl/server.key"));
470 pcfg.ssl_factory = sslcfg->new_factory();
471
473 pcfg.tls_crypt_metadata_factory = meta_factory;
474
475 // only wanted by the tests below that drive a ProtoContext, which builds a
476 // KeyContext, which wants somewhere to derive key material from and a protocol
477 // to know whether it is reliable
479 pcfg.protocol = Protocol(Protocol::UDPv4);
480
481 // Kc, the client key the WKc wraps. Kept as raw bytes to write into the
482 // WKc plaintext, and mirrored into a static key to key the client-side
483 // tls-crypt instance with the same material.
484 pcfg.prng->rand_bytes(client_key_raw_, sizeof(client_key_raw_));
485 std::memcpy(client_key_.raw_alloc(), client_key_raw_, sizeof(client_key_raw_));
486 }
487
495 BufferAllocated make_wkc(const std::string &metadata, int metadata_type = 0x00)
496 {
497 return wrap_wkc(client_key_raw_, metadata, metadata_type);
498 }
499
503 BufferAllocated wrap_wkc(const unsigned char *kc,
504 const std::string &metadata,
505 int metadata_type,
506 size_t kc_size = OpenVPNStaticKey::KEY_SIZE)
507 {
509
510 // A WKc names its server key by K_id only where the server looks keys up that way.
511 const std::optional<std::uint32_t> k_id = pcfg.tls_crypt_v2_serverkey_id ? std::optional<std::uint32_t>(SERVER_KEY_ID)
512 : std::nullopt;
513
514 return TLSCryptV2ClientKey::wrap(*pcfg.tls_crypt_context,
516 kc,
517 kc_size,
518 metadata,
519 metadata_type,
520 k_id,
521 pcfg.ssl_factory->libctx());
522 }
523
530 const ProtoSessionID &cookie_psid,
531 const BufferAllocated &wkc,
532 unsigned char op_field)
533 {
534 return wrap_third_packet(client_key_, cli_psid, cookie_psid, wkc, op_field, 0);
535 }
536
540 const ProtoSessionID &cli_psid,
541 const ProtoSessionID &cookie_psid,
542 const BufferAllocated &wkc,
543 unsigned char op_field,
544 std::uint32_t own_pktid_be)
545 {
547 const size_t hmac_size = pcfg.tls_crypt_context->digest_size();
548
549 // ENCRYPT|INVERSE, as a client slices it: the server's DECRYPT|NORMAL key set
550 TLSCryptInstance::Ptr send = pcfg.tls_crypt_context->new_obj_send();
551 send->init(pcfg.ssl_factory->libctx(),
554
555 // the layout validate_3whs_ack_payload() walks
556 BufferAllocated payload;
557 pcfg.frame->prepare(Frame::WRITE_SSL_INIT, payload);
558 payload.push_back(1); // ACK count
559 const std::uint32_t acked_pktid_be = 0;
560 payload.write(&acked_pktid_be, sizeof(acked_pktid_be));
561 cookie_psid.write(payload);
562 payload.write(&own_pktid_be, sizeof(own_pktid_be));
563
564 // header fields, prepended in reverse on-the-wire order
566 pcfg.frame->prepare(Frame::ENCRYPT_WORK, work);
567 work.prepend_alloc(hmac_size);
569 pid.write_next(work, /*prepend=*/true, pcookie_impl->now_->seconds_since_epoch());
570 cli_psid.prepend(work);
571 work.push_front(op_field);
572
573 send->hmac_gen(work.data(), TLSCryptContext::hmac_offset, payload.c_data(), payload.size());
574
575 const size_t data_offset = TLSCryptContext::hmac_offset + hmac_size;
576 const size_t encrypt_bytes = send->encrypt(work.c_data() + TLSCryptContext::hmac_offset,
577 work.data() + data_offset,
578 work.max_size() - data_offset,
579 payload.c_data(),
580 payload.size());
581 work.inc_size(encrypt_bytes);
582
583 // the WKc rides at the very end of the packet, on the opcodes that carry one
584 if (!wkc.empty())
585 work.write(wkc.c_data(), wkc.size());
586
587 return work;
588 }
589
598 std::uint32_t own_pktid_be = 0)
599 {
600 unsigned char kc_raw[OpenVPNStaticKey::KEY_SIZE];
601 pcookie_impl->pcfg_.prng->rand_bytes(kc_raw, sizeof(kc_raw));
603 std::memcpy(kc.raw_alloc(), kc_raw, sizeof(kc_raw));
604
605 ProtoSessionID cli_psid;
606 cli_psid.randomize(*pcookie_impl->pcfg_.rng);
607
608 return wrap_third_packet(kc,
609 cli_psid,
610 cookie_psid,
611 wrap_wkc(kc_raw, "v=1,type=external", 0x00),
613 own_pktid_be);
614 }
615
625 const BufferAllocated &wkc)
626 {
627 const size_t frame_size = ProtoContext::KeyContext::tls_crypt_frame_size(pcookie_impl->pcfg_);
628
629 BufferAllocated pkt(frame_size + wkc.size(), BufAllocFlags::GROW);
631 cli_psid.write(pkt);
632
633 // the packet id; its id field is what supports_early_negotiation() weighs
634 const std::uint32_t early_neg_be = htonl(ProtoContext::EARLY_NEG_START);
635 pkt.write(&early_neg_be, sizeof(early_neg_be));
636 const std::uint32_t pid_time_be = 0;
637 pkt.write(&pid_time_be, sizeof(pid_time_be));
638
639 // the hmac and payload behind it, which this path does not look at
640 while (pkt.size() < frame_size)
641 pkt.push_back(0);
642
643 pkt.write(wkc.c_data(), wkc.size());
644 return pkt;
645 }
646
648 static unsigned char wkc_v1_op_field()
649 {
651 }
652
654 static unsigned char control_v1_op_field()
655 {
657 }
658
660 static constexpr std::uint32_t SERVER_KEY_ID = 0x063FE634;
661
666};
667
668// The session created for this client strips the WKc itself, uniformly for this copy and
669// the retransmissions that follow, so intercept() must hand the packet on as it arrived.
670TEST_F(PsidCookieTlsCryptV2Test, ThirdPacketIsForwardedIntact)
671{
672 auto f = make_fixture();
673 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
674 f.cookie_psid,
675 make_wkc("v=1,type=external"),
676 wkc_v1_op_field());
677
678 const size_t wire_size = pkt.size();
679 const std::string wire = buf_to_string(pkt);
680
681 ASSERT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
682
683 EXPECT_EQ(pkt.size(), wire_size);
684 EXPECT_EQ(buf_to_string(pkt), wire);
685 // ... and the session takes the WKc off it
686 EXPECT_TRUE(ProtoContext::KeyContext::strip_resent_wkc(pkt, pcookie_impl->pcfg_));
687 EXPECT_LT(pkt.size(), wire_size);
688}
689
690// Nothing travels: the cookie layer unwrapped a key to answer the packet with and threw
691// it away, so the config every session is cloned from is as it was before the client
692// showed up, and one client's handshake cannot furnish another's session.
693TEST_F(PsidCookieTlsCryptV2Test, NothingIsLeftOnTheSharedConfig)
694{
695 auto f = make_fixture();
696 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
697 f.cookie_psid,
698 make_wkc("v=1,type=external"),
699 wkc_v1_op_field());
700
701 ASSERT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
702
703 // the shared config holds what it was configured with: the server key ID to look
704 // WKc-wrapping keys up by, and no client's key
705 EXPECT_TRUE(pcookie_impl->pcfg_.tls_crypt_v2_serverkey_id);
706 EXPECT_FALSE(pcookie_impl->pcfg_.tls_crypt_key.defined());
707 EXPECT_FALSE(spf->clone_proto_config()->tls_crypt_key.defined());
708}
709
712{
713 public:
714 size_t n_sent = 0;
715
716 private:
717 bool psid_cookie_send_const(Buffer &send_buf, const PsidCookieAddrInfoBase &pcaib) override
718 {
719 ++n_sent;
720 return true;
721 }
722};
723
726{
727 public:
728 size_t n_sent = 0;
729
730 private:
731 void control_net_send(const Buffer &net_buf) override
732 {
733 ++n_sent;
734 }
735 void control_recv(BufferPtr &&app_bp) override
736 {
737 }
738 bool supports_epoch_data() override
739 {
740 return false;
741 }
742 void active(bool primary) override
743 {
744 }
745};
746
749{
750 public:
751 std::string names;
752
753 private:
754 void error(const size_t type, const std::string *text = nullptr) override
755 {
756 if (!names.empty())
757 names += " ";
758 names += Error::name(type);
759 }
760};
761
769{
774
776 : proto(&cb, cfg, stats)
777 {
778 proto.reset(cookie_psid);
779 proto.start(cookie_psid);
780 proto.flush(true);
781 cb.n_sent = 0; // count only what the packets below draw out
782 }
783
785 size_t recv(const BufferAllocated &pkt)
786 {
787 const size_t before = cb.n_sent;
788
791 if (pt.is_control())
792 proto.control_net_recv(pt, std::move(bp));
793 proto.flush(true);
794
795 return cb.n_sent - before;
796 }
797};
798
799// The point of the whole arrangement: the session keys its control channel off the WKc
800// still riding on the packet it is handed, having been given no key by anyone. It accepts
801// the packet, which it could only do with the right key, and answers it.
802TEST_F(PsidCookieTlsCryptV2Test, SessionDerivesItsKeyFromTheWkcOnItsFirstPacket)
803{
804 auto f = make_fixture();
805 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
806 f.cookie_psid,
807 make_wkc("v=1,type=external"),
808 wkc_v1_op_field());
809
810 ASSERT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
811 // the cookie layer unwrapped the WKc to answer the packet, and judged nothing
812 ASSERT_EQ(meta_factory->n_created, 0u);
813
814 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
815 EXPECT_GT(session.recv(pkt), 0u);
816
817 // One hook for the client, made where the record is judged and shown it once
818 EXPECT_EQ(meta_factory->n_created, 1u);
819 EXPECT_EQ(meta_factory->last->n_calls, 1u);
820}
821
822TEST_F(PsidCookieTlsCryptV2Test, ThirdPacketParsesWKcMetadata)
823{
824 auto f = make_fixture();
825 const std::string metadata = "v=1,type=external,sn=04:e3,time=1750000000,tenant=acme";
826
827 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
828 f.cookie_psid,
829 make_wkc(metadata),
830 wkc_v1_op_field());
831
832 ASSERT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
833 EXPECT_TRUE(pcookie_impl->get_cookie_psid().match(f.cookie_psid));
834
835 // the record reaches the hook from the session's own unwrap of the same WKc, once
836 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
837 ASSERT_GT(session.recv(pkt), 0u);
838
839 ASSERT_EQ(meta_factory->n_created, 1u);
840 ASSERT_TRUE(meta_factory->last);
841 EXPECT_EQ(meta_factory->last->n_calls, 1u);
842 EXPECT_EQ(meta_factory->last->type_seen, 0x00);
843 EXPECT_EQ(meta_factory->last->payload_seen, metadata);
844}
845
846// A WKc that does not unwrap keys nothing, so the packet it rode in on is dropped rather
847// than answered -- and the handshake is not left half-converted, see
848// ProtoContext::KeyContext::tls_crypt_v2_wanted().
849TEST_F(PsidCookieTlsCryptV2Test, SessionDropsAPacketWhoseWkcDoesNotUnwrap)
850{
851 auto f = make_fixture();
852 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
853 f.cookie_psid,
854 make_wkc("v=1,type=external"),
855 wkc_v1_op_field());
856
857 ASSERT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
858
859 // flip a bit in the WKc ciphertext, past the tls-crypt frame the session decapsulates
860 pkt.data()[pkt.size() - 32] ^= 0x01;
861
862 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
863 EXPECT_EQ(session.recv(pkt), 0u);
864}
865
872{
873 protected:
875 {
877 pcfg.tls_crypt_v2_serverkey_id = false;
878 pcfg.tls_crypt_v2_serverkey_dir.clear();
879 server_key_.extract_key(pcfg.tls_crypt_key);
880 }
881};
882
883// Keying the server context was the caller's job in this mode, and only
884// ProtoContext::reset_tls_crypt_server() did it. The cookie layer hands over a context
885// straight from new_obj_recv(), so the first client packet threw ovpn_tls_crypt_wrong_mode
886// out of intercept() and into the embedder's packet loop.
887TEST_F(PsidCookieSingleServerKeyTest, ThirdPacketUnwrapsWithNoServerKeyIdOnTheWire)
888{
889 auto f = make_fixture();
890 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
891 f.cookie_psid,
892 make_wkc("v=1,type=external"),
893 wkc_v1_op_field());
894
895 EXPECT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
896}
897
898// ... and the session behind it keys itself from the same WKc, with no key directory to
899// consult and no K_id to consult it with.
900TEST_F(PsidCookieSingleServerKeyTest, SessionDerivesItsKeyWithNoServerKeyId)
901{
902 auto f = make_fixture();
903 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
904 f.cookie_psid,
905 make_wkc("v=1,type=external"),
906 wkc_v1_op_field());
907
908 ASSERT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
909
910 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
911 EXPECT_GT(session.recv(pkt), 0u);
912 EXPECT_EQ(meta_factory->n_created, 1u);
913}
914
915// The client key is the one part of a WKc that has to be there, and the check for it counted
916// the length prefix and the K_id along with it -- neither of which is key material, and both
917// advanced past before the key is read. A WKc short by those six bytes passed the check,
918// passed the tag comparison behind it, and underflowed the read. Only a holder of the server
919// key can wrap one, so this is a key generator's mistake, but it arrives as an exception.
920TEST_F(PsidCookieTlsCryptV2Test, WkcWrappingTooLittleKeyIsRejectedRatherThanThrown)
921{
922 auto f = make_fixture();
923
924 // short by the 2-byte length prefix and the 4-byte K_id in front of the key
925 const size_t short_kc = OpenVPNStaticKey::KEY_SIZE - sizeof(std::uint16_t) - sizeof(std::uint32_t);
926 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
927 f.cookie_psid,
928 wrap_wkc(client_key_raw_, "", 0x00, short_kc),
929 wkc_v1_op_field());
930
931 PsidCookie::Intercept ret = PsidCookie::Intercept::HANDLE_2ND;
932 ASSERT_NO_THROW(ret = pcookie_impl->intercept(pkt, f.cli_addr));
933 EXPECT_NE(ret, PsidCookie::Intercept::HANDLE_2ND);
934
935 // and the unwrap leaves its out parameter untouched, as it promises: raw_alloc() defines
936 // the key before the read that fills it, so a throw there left behind a client key that
937 // had never been unwrapped
938 BufferAllocated again = build_third_packet_tls_crypt_v2(f.cli_psid,
939 f.cookie_psid,
940 wrap_wkc(client_key_raw_, "", 0x00, short_kc),
941 wkc_v1_op_field());
942 TLSCryptInstance::Ptr server = pcookie_impl->pcfg_.tls_crypt_context->new_obj_recv();
944 EXPECT_NE(ProtoContext::KeyContext::unwrap_tls_crypt_wkc(again, pcookie_impl->pcfg_, *server, unwrapped),
946 EXPECT_FALSE(unwrapped.client_key.defined());
947}
948
949// Answering the first packet means unwrapping the WKc on it, and the unwrap trims the WKc off
950// the buffer it is handed -- so this returned the caller a packet shorter than the one it
951// passed in. Nothing forwards this packet today; anything that did would hand on a packet
952// with no WKc, and a session keyed from that can never key itself.
953TEST_F(PsidCookieTlsCryptV2Test, FirstPacketIsLeftAsItArrived)
954{
956 pcookie_impl->provide_psid_cookie_transport(transport);
957
958 auto f = make_fixture();
959 BufferAllocated pkt = build_first_packet_tls_crypt_v2(f.cli_psid, make_wkc("v=1,type=external"));
960
961 const size_t wire_size = pkt.size();
962 const std::string wire = buf_to_string(pkt);
963
964 EXPECT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_1ST);
965 EXPECT_EQ(transport->n_sent, 1u);
966
967 EXPECT_EQ(pkt.size(), wire_size);
968 EXPECT_EQ(buf_to_string(pkt), wire);
969}
970
978{
979 protected:
981 {
982 pcookie_impl->pcfg_.tls_auth_key.erase();
983 }
984};
985
986// The first packet a pre-filtering embedder sees is the one the receive context is keyed
987// from, so there is no key to check it with. Only CONTROL_HARD_RESET_CLIENT_V3 was exempt,
988// not the CONTROL_WKC_V1 a psid cookie layer leaves the session to key itself from.
989TEST_F(PsidCookieTlsCryptV2OnlyTest, ControlNetValidateAcceptsTheWkcBearingPacket)
990{
991 auto f = make_fixture();
992 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
993 f.cookie_psid,
994 make_wkc("v=1,type=external"),
995 wkc_v1_op_field());
996
997 ASSERT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
998
999 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
1000
1002 const ProtoContext::PacketType pt = session.proto.packet_type(*bp);
1003 ASSERT_TRUE(pt.is_control());
1004 EXPECT_TRUE(session.proto.control_net_validate(pt, *bp));
1005
1006 // the pre-filter and the path it filters for have to agree
1007 EXPECT_GT(session.recv(pkt), 0u);
1008}
1009
1010// The same packet on a server holding a tls-auth key as well -- what PG deploys. Such a
1011// session is still TLS_AUTH when the pre-filter runs, since only decapsulate() converts it,
1012// so the exemption has to be recognised in that arm too.
1013TEST_F(PsidCookieTlsCryptV2Test, ControlNetValidateAcceptsTheWkcBearingPacketOnATlsAuthServer)
1014{
1015 auto f = make_fixture();
1016 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
1017 f.cookie_psid,
1018 make_wkc("v=1,type=external"),
1019 wkc_v1_op_field());
1020
1021 ASSERT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
1022
1023 // the premise: this fixture keeps the tls-auth key
1024 ASSERT_TRUE(pcookie_impl->pcfg_.tls_auth_enabled());
1025
1026 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
1027
1029 const ProtoContext::PacketType pt = session.proto.packet_type(*bp);
1030 ASSERT_TRUE(pt.is_control());
1031 EXPECT_TRUE(session.proto.control_net_validate(pt, *bp));
1032
1033 // the pre-filter and the path it filters for have to agree
1034 EXPECT_GT(session.recv(pkt), 0u);
1035}
1036
1037// The exemption follows the opcode, not the mode: one carrying no WKc keys nothing and stays
1038// judged with the tls-auth key. Same packet as above but for its op field.
1039TEST_F(PsidCookieTlsCryptV2Test, ControlNetValidateStillJudgesNonWkcOpcodesOnATlsAuthServer)
1040{
1041 auto f = make_fixture();
1042 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
1043 f.cookie_psid,
1044 make_wkc("v=1,type=external"),
1045 control_v1_op_field());
1046
1047 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
1048
1050 const ProtoContext::PacketType pt = session.proto.packet_type(*bp);
1051 ASSERT_TRUE(pt.is_control());
1052
1053 // tls-crypt wrapped, so the tls-auth key cannot vouch for it -- and nothing exempts it
1054 EXPECT_FALSE(session.proto.control_net_validate(pt, *bp));
1055}
1056
1063{
1064 protected:
1066 {
1068 pcfg.tls_auth_key.erase();
1069 pcfg.tls_crypt_ = ProtoContext::ProtoConfig::TLSCrypt::V1;
1070 pcfg.tls_crypt_v2_serverkey_id = false;
1071 pcfg.tls_crypt_v2_serverkey_dir.clear();
1073 pcfg.tls_crypt_key = shared_key_;
1074 }
1075
1077 BufferAllocated v1_packet(const ProtoSessionID &cli_psid, const ProtoSessionID &srv_psid)
1078 {
1079 return wrap_third_packet(shared_key_, cli_psid, srv_psid, BufferAllocated(), control_v1_op_field(), 0);
1080 }
1081
1084};
1085
1086// A pre-filter's verdict must not depend on what it was handed before. It pinned the peer
1087// psid from whatever packet reached it first, outside accept_peer(), so a holder of the
1088// shared key could pin itself from a spoofed datagram and lock the real client out.
1089TEST_F(TlsCryptV1SessionTest, ControlNetValidateDoesNotPinThePeer)
1090{
1091 auto f = make_fixture();
1092 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
1093
1094 ProtoSessionID other_psid;
1095 other_psid.randomize(*pcookie_impl->pcfg_.rng);
1096
1097 auto validate = [&](const BufferAllocated &pkt)
1098 {
1099 BufferPtr bp = BufferAllocatedRc::Create(pkt.c_data(), pkt.size(), BufAllocFlags::GROW);
1100 const ProtoContext::PacketType pt = session.proto.packet_type(*bp);
1101 return pt.is_control() && session.proto.control_net_validate(pt, *bp);
1102 };
1103
1104 // whichever arrives first, both are packets the pre-filter has no peer to judge against
1105 EXPECT_TRUE(validate(v1_packet(other_psid, f.cookie_psid)));
1106 EXPECT_TRUE(validate(v1_packet(f.cli_psid, f.cookie_psid)));
1107}
1108
1115{
1116 protected:
1118 {
1120 pcfg.tls_crypt_ = ProtoContext::ProtoConfig::TLSCrypt::None;
1121 pcfg.tls_crypt_key.erase();
1122 pcfg.tls_crypt_v2_serverkey_id = false;
1123 pcfg.tls_crypt_v2_serverkey_dir.clear();
1124 }
1125};
1126
1127// match() is false against an undefined psid, so the pre-filter turned away every packet
1128// until a peer was pinned -- including the one that would have pinned it.
1129TEST_F(TlsAuthSessionTest, ControlNetValidateAcceptsAPacketBeforeAPeerIsPinned)
1130{
1131 auto f = make_fixture();
1132 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
1133
1134 BufferAllocated pkt = build_third_packet_tls_auth(f.cli_psid,
1135 f.cookie_psid,
1136 /*acked_pktid_be=*/0,
1137 /*own_pktid_be=*/0,
1138 /*ack_count=*/1,
1140
1142 const ProtoContext::PacketType pt = session.proto.packet_type(*bp);
1143 ASSERT_TRUE(pt.is_control());
1144 EXPECT_TRUE(session.proto.control_net_validate(pt, *bp));
1145}
1146
1147// intercept() turns away only an empty datagram, so every one whose first byte carries a
1148// CONTROL_HARD_RESET_CLIENT_V3 opcode reaches the tls-crypt arm -- which read a psid and a
1149// packet id off it before establishing there was that much packet. Two bytes from anywhere
1150// threw buffer_underflow out of intercept(). The tls-auth sibling has always checked.
1151TEST_F(PsidCookieTlsCryptV2Test, ShortInitialResetIsDroppedRatherThanThrown)
1152{
1153 auto f = make_fixture();
1154
1155 for (size_t size = 1; size <= 96; ++size)
1156 {
1158 pkt.set_size(size);
1160
1161 // where there is room, claim early-negotiation support so the arm carries on into the
1162 // WKc unwrap instead of declining at the flag; both outcomes are wanted
1163 if (size >= 1 + ProtoSessionID::SIZE + sizeof(std::uint32_t))
1164 {
1165 const std::uint32_t early_neg_be = htonl(ProtoContext::EARLY_NEG_START);
1166 std::memcpy(pkt.data() + 1 + ProtoSessionID::SIZE, &early_neg_be, sizeof(early_neg_be));
1167 }
1168
1169 PsidCookie::Intercept ret = PsidCookie::Intercept::HANDLE_2ND;
1170 ASSERT_NO_THROW(ret = pcookie_impl->intercept(pkt, f.cli_addr)) << "size " << size;
1171 EXPECT_NE(ret, PsidCookie::Intercept::HANDLE_2ND) << "size " << size;
1172 }
1173}
1174
1175// A WKc that unwraps says only that this server issued the key inside it, which is true of
1176// every client's WKc -- so a packet that unwraps one and then fails to authenticate under
1177// it must leave no trace. Were its key kept, the genuine packet arriving next would find
1178// the session already keyed and be decrypted with a stranger's key.
1179TEST_F(PsidCookieTlsCryptV2Test, SessionKeepsNoKeyFromAPacketThatFailedToAuthenticate)
1180{
1181 auto f = make_fixture();
1182 BufferAllocated good = build_third_packet_tls_crypt_v2(f.cli_psid,
1183 f.cookie_psid,
1184 make_wkc("v=1,type=external"),
1185 wkc_v1_op_field());
1186
1187 ASSERT_EQ(pcookie_impl->intercept(good, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
1188
1189 // What an off-path attacker spoofing this client's address can put together: a WKc of
1190 // its own, which unwraps under the server key like any other, on a frame it cannot
1191 // wrap with the key that WKc carries.
1192 unsigned char foreign_key_raw[OpenVPNStaticKey::KEY_SIZE];
1193 pcookie_impl->pcfg_.prng->rand_bytes(foreign_key_raw, sizeof(foreign_key_raw));
1194 BufferAllocated spoofed = build_third_packet_tls_crypt_v2(f.cli_psid,
1195 f.cookie_psid,
1196 wrap_wkc(foreign_key_raw, "v=1,type=external", 0x00),
1197 wkc_v1_op_field());
1198
1199 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
1200 EXPECT_EQ(session.recv(spoofed), 0u);
1201
1202 // one datagram, and the client can still connect
1203 EXPECT_GT(session.recv(good), 0u);
1204}
1205
1206// Authenticating at the tls-crypt layer only says the sender holds the Kc it supplied its
1207// own WKc for, which every client of this server can do. So a packet rejected after that --
1208// here on the psid the cookie layer issued, which the sender cannot know -- must leave the
1209// peer unpinned. Pinned, it closes tls_crypt_v2_wanted()'s conversion window for good and
1210// the real client's packets are checked as tls-auth from then on.
1211TEST_F(PsidCookieTlsCryptV2Test, SessionKeepsNoPeerPsidFromAPacketThatFailedToAuthenticate)
1212{
1213 auto f = make_fixture();
1214 BufferAllocated good = build_third_packet_tls_crypt_v2(f.cli_psid,
1215 f.cookie_psid,
1216 make_wkc("v=1,type=external"),
1217 wkc_v1_op_field());
1218
1219 ASSERT_EQ(pcookie_impl->intercept(good, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
1220
1221 ProtoSessionID bogus_cookie_psid;
1222 bogus_cookie_psid.randomize(*pcookie_impl->pcfg_.rng);
1223
1224 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
1225 EXPECT_EQ(session.recv(build_foreign_third_packet(bogus_cookie_psid)), 0u);
1226
1227 // one datagram, and the client can still connect
1228 EXPECT_GT(session.recv(good), 0u);
1229}
1230
1231// The other side of that coin: a packet of the peer's own that yields no message must keep
1232// the key it unwrapped, because the packet after it need not carry a WKc to re-key from.
1233// Here the first packet is refused by the reliable receive window, and the CONTROL_V1 behind
1234// it has no WKc of its own -- it can only be read with the key the refused packet left.
1235TEST_F(PsidCookieTlsCryptV2Test, SessionKeepsTheKeyOfItsPeersPacketThatYieldedNoMessage)
1236{
1237 auto f = make_fixture();
1238 BufferAllocated good = build_third_packet_tls_crypt_v2(f.cli_psid,
1239 f.cookie_psid,
1240 make_wkc("v=1,type=external"),
1241 wkc_v1_op_field());
1242
1243 ASSERT_EQ(pcookie_impl->intercept(good, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
1244
1245 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
1246
1247 // the client's own third packet, numbered far outside the reliable receive window
1248 session.recv(wrap_third_packet(client_key_,
1249 f.cli_psid,
1250 f.cookie_psid,
1251 make_wkc("v=1,type=external"),
1252 wkc_v1_op_field(),
1253 htonl(5000)));
1254
1255 BufferAllocated no_wkc = wrap_third_packet(client_key_,
1256 f.cli_psid,
1257 f.cookie_psid,
1259 control_v1_op_field(),
1260 0);
1261 session.rec->names.clear();
1262 session.recv(no_wkc);
1263
1264 // CC_ERROR here would be the session finding no key at all, HMAC_ERROR a key that is
1265 // not this client's
1266 EXPECT_EQ(session.rec->names, "");
1267}
1268
1269// The session's hook is its own: it judges the record whether or not a cookie layer ever
1270// saw the client, which is how a session reached over TCP -- kotcp.hpp creates those with
1271// no cookie layer at all -- comes by its verdict.
1272TEST_F(PsidCookieTlsCryptV2Test, SessionWithoutACookieLayerJudgesTheRecordItself)
1273{
1274 auto f = make_fixture();
1275 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
1276 f.cookie_psid,
1277 make_wkc("v=1,type=external"),
1278 wkc_v1_op_field());
1279
1280 // the packet reaches the session without passing intercept() first
1281 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
1282 EXPECT_GT(session.recv(pkt), 0u);
1283
1284 EXPECT_EQ(meta_factory->n_created, 1u);
1285 EXPECT_EQ(meta_factory->last->n_calls, 1u);
1286}
1287
1288// Unwrapping a WKc says this server issued it, not that the sender holds the Kc inside: a
1289// captured one unwraps just the same. The hook ran anyway, and since a failed packet leaves
1290// the session un-keyed, an unauthenticated sender could drive it again and again.
1291TEST_F(PsidCookieTlsCryptV2Test, UnauthenticatedWkcRunsNoMetadataHook)
1292{
1293 auto f = make_fixture();
1294
1295 // a WKc this server unwraps, on a frame not wrapped with the Kc it carries
1296 unsigned char foreign_key_raw[OpenVPNStaticKey::KEY_SIZE];
1297 pcookie_impl->pcfg_.prng->rand_bytes(foreign_key_raw, sizeof(foreign_key_raw));
1298 BufferAllocated spoofed = build_third_packet_tls_crypt_v2(f.cli_psid,
1299 f.cookie_psid,
1300 wrap_wkc(foreign_key_raw, "v=1,type=ATTACKER", 0x00),
1301 wkc_v1_op_field());
1302
1303 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
1304
1305 EXPECT_EQ(session.recv(spoofed), 0u);
1306 EXPECT_EQ(meta_factory->n_created, 0u);
1307
1308 // un-keyed again, so the second attempt judges no more than the first
1309 EXPECT_EQ(session.recv(spoofed), 0u);
1310 EXPECT_EQ(meta_factory->n_created, 0u);
1311 EXPECT_FALSE(meta_factory->last);
1312
1313 // the real client still connects, its record seen once
1314 BufferAllocated good = build_third_packet_tls_crypt_v2(f.cli_psid,
1315 f.cookie_psid,
1316 make_wkc("v=1,type=external"),
1317 wkc_v1_op_field());
1318 EXPECT_GT(session.recv(good), 0u);
1319 EXPECT_EQ(meta_factory->n_created, 1u);
1320 EXPECT_EQ(meta_factory->last->payload_seen, "v=1,type=external");
1321}
1322
1323// The factory is optional: TLSCryptMetadata::verify() accepts by default, so an embedder
1324// with nothing to check has no reason to configure one. A session without it makes no
1325// handler, runs no hook, and comes up all the same.
1326TEST_F(PsidCookieTlsCryptV2Test, SessionWithoutMetadataFactoryMakesNoHandler)
1327{
1328 auto f = make_fixture();
1329 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
1330 f.cookie_psid,
1331 make_wkc("v=1,type=external"),
1332 wkc_v1_op_field());
1333
1334 pcookie_impl->pcfg_.tls_crypt_metadata_factory.reset();
1335 ASSERT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
1336
1337 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
1338 EXPECT_GT(session.recv(pkt), 0u);
1339 EXPECT_EQ(meta_factory->n_created, 0u);
1340}
1341
1342TEST_F(PsidCookieTlsCryptV2Test, TimestampMetadataIsNotUserMetadata)
1343{
1344 // stock tls-crypt-v2-genkey's default: a timestamp, type 0x01. The hook still runs, and
1345 // sees the type byte saying this is not a CSV record.
1346 auto f = make_fixture();
1347 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
1348 f.cookie_psid,
1349 make_wkc(std::string("\x68\x74\x9f\x00", 4), 0x01),
1350 wkc_v1_op_field());
1351
1352 ASSERT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
1353
1354 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
1355 ASSERT_GT(session.recv(pkt), 0u);
1356
1357 ASSERT_TRUE(meta_factory->last);
1358 EXPECT_EQ(meta_factory->last->n_calls, 1u);
1359 EXPECT_EQ(meta_factory->last->type_seen, 0x01);
1360}
1361
1362TEST_F(PsidCookieTlsCryptV2Test, WkcWithoutMetadataReportsNoMetadata)
1363{
1364 auto f = make_fixture();
1365 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
1366 f.cookie_psid,
1367 make_wkc(""),
1368 wkc_v1_op_field());
1369
1370 ASSERT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
1371
1372 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
1373 ASSERT_GT(session.recv(pkt), 0u);
1374
1375 ASSERT_TRUE(meta_factory->last);
1376 EXPECT_EQ(meta_factory->last->n_calls, 1u);
1377 EXPECT_EQ(meta_factory->last->type_seen, -1);
1378}
1379
1380// verify() belongs to an embedder, and a bad cookie is all it takes to aim a packet at this
1381// layer, so nothing a stranger sends may reach the hook. Since the cookie layer judges
1382// nothing at all, a dropped third packet cannot: it never becomes the session that would ask.
1383TEST_F(PsidCookieTlsCryptV2Test, DroppedThirdPacketRunsNoMetadataHook)
1384{
1385 auto f = make_fixture();
1386 ProtoSessionID bogus;
1387 bogus.randomize(*pcookie_impl->pcfg_.rng);
1388
1389 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
1390 bogus,
1391 make_wkc("v=1,type=external"),
1392 wkc_v1_op_field());
1393
1394 EXPECT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::DROP_2ND);
1395 // no handler was made for it, so nothing saw the record ...
1396 EXPECT_EQ(meta_factory->n_created, 0u);
1397 EXPECT_FALSE(meta_factory->last);
1398}
1399
1400// A failed verify() rejects the client, and the session is where that is decided: the packet
1401// is dropped and the rejection counted. PG's hook never does this -- its metadata is
1402// informational -- but the plumbing honours it.
1403TEST_F(PsidCookieTlsCryptV2Test, PacketRejectedByMetadataHookIsDroppedAndCounted)
1404{
1405 auto f = make_fixture();
1406 meta_factory->accept = false;
1407
1408 BufferAllocated pkt = build_third_packet_tls_crypt_v2(f.cli_psid,
1409 f.cookie_psid,
1410 make_wkc("v=1,type=external"),
1411 wkc_v1_op_field());
1412
1413 // the cookie layer answers it either way; judging is not its business
1414 ASSERT_EQ(pcookie_impl->intercept(pkt, f.cli_addr), PsidCookie::Intercept::HANDLE_2ND);
1415
1416 CookieSession session(spf->clone_proto_config(), f.cookie_psid);
1417 EXPECT_EQ(session.recv(pkt), 0u);
1418 EXPECT_EQ(session.rec->names, "TLS_CRYPT_META_FAIL");
1419}
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.
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.
TLSCryptV2ServerKey server_key_
Ka/Ke, used here to wrap the WKc.
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.
OpenVPNStaticKey shared_key_
The one key a v1 server shares with every client, in the form both sides slice.
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 push_back(const T &value)
Append a T object to the end of the array, resizing the array if necessary.
Definition buffer.hpp:1480
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
static BufferAllocated wrap(TLSCryptContext &context, const TLSCryptV2ServerKey &server_key, const unsigned char *kc, const size_t kc_size, const std::string &metadata, const int metadata_type, const std::optional< std::uint32_t > key_id, SSLLib::Ctx libctx=nullptr)
Wrap client key material and metadata into a WKc.
void parse(const std::string &key_text)
void extract_key(OpenVPNStaticKey &tls_key) const
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