OpenVPN 3 Core Library
Loading...
Searching...
No Matches
websocket.hpp
Go to the documentation of this file.
1// OpenVPN -- An application to securely tunnel IP networks
2// over a single port, with support for SSL/TLS-based
3// session authentication and key exchange,
4// packet encryption, packet authentication, and
5// packet compression.
6//
7// Copyright (C) 2012- OpenVPN Inc.
8//
9// SPDX-License-Identifier: MPL-2.0 OR AGPL-3.0-only WITH openvpn3-openssl-exception
10//
11
12#pragma once
13
14#include <string>
15#include <cstdint>
16#include <ostream>
17#include <tuple>
18#include <utility>
19
21#include <openvpn/common/rc.hpp>
28
30
31OPENVPN_EXCEPTION(websocket_error);
32
33class Receiver;
34
35inline std::string accept_confirmation(DigestFactory &digest_factory,
36 const std::string &websocket_key)
37{
38 static const char guid[] = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
39 HashString h(digest_factory, CryptoAlgs::SHA1);
40 h.update(websocket_key + guid);
41 return h.final_base64();
42}
43
45{
46 public:
47 static constexpr size_t MAX_HEAD = 16;
48
49 enum Opcode
50 {
51 Text = 0x1,
52 Binary = 0x2,
53 Close = 0x8,
54 Ping = 0x9,
55 Pong = 0xA,
56 };
57
58 static std::string opcode_to_string(const unsigned int opcode)
59 {
60 switch (opcode)
61 {
62 case Text:
63 return "Text";
64 case Binary:
65 return "Binary";
66 case Close:
67 return "Close";
68 case Ping:
69 return "Ping";
70 case Pong:
71 return "Pong";
72 default:
73 return "WS-OPCODE-" + std::to_string(opcode);
74 }
75 }
76
77 union MaskingKey {
78 public:
79 MaskingKey(std::uint32_t mask)
80 : mask32(std::move(mask))
81 {
82 }
83
84 void xor_buf(Buffer &buf) const
85 {
86 const size_t size = buf.size();
87 std::uint8_t *data = buf.data();
88 for (size_t i = 0; i < size; ++i)
89 data[i] ^= mask8[i & 0x3];
90 }
91
92 void prepend_mask(Buffer &buf) const
93 {
94 buf.prepend(&mask32, sizeof(mask32));
95 }
96
97 private:
98 std::uint32_t mask32;
99 std::uint8_t mask8[4];
100 };
101};
102
104{
105 public:
107 : opcode_(0),
108 fin_(false),
110 {
111 }
112
113 Status(unsigned int opcode,
114 bool fin = true,
115 uint16_t close_status_code = 0)
116 : opcode_(std::move(opcode)),
117 fin_(std::move(fin)),
119 {
120 }
121
122 Status(const Status &ref,
123 const unsigned int opcode)
124 : opcode_(opcode),
125 fin_(ref.fin_),
127 {
128 }
129
130 bool defined() const
131 {
132 return opcode_ != 0;
133 }
134
135 unsigned int opcode() const
136 {
137 return opcode_;
138 }
139
140 bool fin() const
141 {
142 return fin_;
143 }
144
145 auto close_status_code() const
146 {
147 return close_status_code_;
148 }
149
150 bool operator==(const Status &rhs) const
151 {
152 return std::tie(opcode_, fin_, close_status_code_) == std::tie(rhs.opcode_, fin_, rhs.close_status_code_);
153 }
154
155 bool operator!=(const Status &rhs) const
156 {
157 return std::tie(opcode_, fin_, close_status_code_) != std::tie(rhs.opcode_, fin_, rhs.close_status_code_);
158 }
159
160 std::string to_string() const
161 {
162 std::string ret;
163
164 ret.reserve(64);
165 ret += "[op=";
167 ret += " fin=";
168 ret += std::to_string(fin_);
170 {
171 ret += " status=";
172 ret += std::to_string(close_status_code_);
173 }
174 ret += ']';
175 return ret;
176 }
177
178 private:
179 friend class Receiver;
180
181 unsigned int opcode_;
182 bool fin_;
184};
185
187{
188 public:
189 Sender(StrongRandomAPI::Ptr cli_rng_arg) // only provide rng on client side
190 : cli_rng(std::move(cli_rng_arg))
191 {
192 }
193
194 void frame(Buffer &buf, const Status &s) const
195 {
196 if (s.opcode() == Protocol::Close)
197 {
198 const std::uint16_t cs = htons(s.close_status_code());
199 buf.prepend(&cs, sizeof(cs));
200 }
201
202 const size_t payload_len = buf.size();
203 if (cli_rng)
204 {
205 const Protocol::MaskingKey mk(cli_rng->rand_get<std::uint32_t>());
206 mk.xor_buf(buf);
207 mk.prepend_mask(buf);
208 }
209 prepend_payload_length(buf, payload_len);
210
211 std::uint8_t head = s.opcode() & 0xF;
212 if (s.fin())
213 head |= 0x80;
214 buf.prepend(&head, sizeof(head));
215
216 // OPENVPN_LOG("WS SEND HEAD\n" << dump_hex(buf));
217 }
218
219 private:
220 void prepend_payload_length(Buffer &buf, const size_t len) const
221 {
222 std::uint8_t len8;
223
224 if (len <= 125)
225 len8 = static_cast<std::uint8_t>(len);
226 else if (len <= 65535)
227 {
228 len8 = 126;
229 const std::uint16_t len16 = htons(static_cast<std::uint16_t>(len));
230 buf.prepend(&len16, sizeof(len16));
231 }
232 else
233 {
234 len8 = 127;
235 const std::uint64_t len64 = Endian::rev64(len);
236 buf.prepend(&len64, sizeof(len64));
237 }
238
239 if (cli_rng)
240 len8 |= 0x80;
241 buf.prepend(&len8, sizeof(len8));
242 }
243
245};
246
248{
249 public:
250 Receiver(const bool is_client_arg)
251 : is_client(is_client_arg)
252 {
253 reset_pod();
254 }
255
257 {
259 if (size > buf.size())
260 throw websocket_error("Receiver::buf_unframed: internal error");
261 return Buffer(buf.data(), static_cast<size_t>(size), true);
262 }
263
264 // return true if message is complete
265 bool complete()
266 {
267 // already complete?
268 if (header_complete)
269 return complete_();
270
271 // we need at least 2 bytes before we can do anything
272 if (buf.size() < 2)
273 return false;
274
275 // get first 2 bytes of header
276 Buffer b(buf.data(), buf.size(), true);
277 const std::uint8_t *head = b.read_alloc(2);
278 s.opcode_ = head[0] & 0xF;
279 s.fin_ = bool(head[0] & 0x80);
280 if (head[0] & 0x70)
281 throw websocket_error("Receiver: reserved bits are set");
282 if (bool(head[1] & 0x80) == is_client)
283 throw websocket_error("Receiver: bad masking direction");
284
285 // process payload length
286 const std::uint8_t pl = head[1] & 0x7f;
287 if (pl <= 125)
288 {
289 size = pl;
290 }
291 else if (pl == 126)
292 {
293 std::uint16_t len16;
294 if (b.size() < sizeof(len16))
295 return false;
296 b.read(&len16, sizeof(len16));
297 size = ntohs(len16);
298 }
299 else // pl == 127
300 {
301 std::uint64_t len64;
302 if (b.size() < sizeof(len64))
303 return false;
304 b.read(&len64, sizeof(len64));
305 size = Endian::rev64(len64);
306 }
307
308 // read mask (server side only)
309 if (!is_client)
310 {
311 if (b.size() < sizeof(mask))
312 return false;
313 b.read(&mask, sizeof(mask));
314 }
315
316 buf.advance(b.offset());
317 header_complete = true;
318 return complete_();
319 }
320
322 {
323 if (!buf.allocated())
324 {
325 buf = std::move(inbuf);
327 }
328 else
329 buf.append(inbuf);
330 }
331
332 void reset()
333 {
335 s = Status();
336 reset_buf();
337 reset_pod();
338 }
339
341 {
343 return s;
344 }
345
346 private:
348 {
349 if (buf.allocated())
350 {
351 if (size < buf.size())
352 {
353 buf.advance(static_cast<size_t>(size));
354 buf.realign(0);
355 }
356 else if (size == buf.size())
357 buf.clear();
358 else
359 throw websocket_error("Receiver::reset_buf: bad size");
360 }
361 }
362
364 {
365 header_complete = false;
366 message_complete = false;
367 mask = 0;
368 size = 0;
369 }
370
372 {
373 if (!message_complete)
374 throw websocket_error("Receiver: message incomplete");
375 }
376
378 {
380 return true;
381
382 if (header_complete && size <= buf.size())
383 {
384 // un-xor the data on the server side only
385 if (!is_client)
386 {
387 Buffer b(buf.data(), static_cast<size_t>(size), true);
388 const Protocol::MaskingKey mk(mask);
389 mk.xor_buf(b);
390 }
391
392 // get close status code
393 if (s.opcode_ == Protocol::Close && size >= 2)
394 {
395 std::uint16_t cs;
396 buf.read(&cs, sizeof(cs));
397 size -= sizeof(cs);
398 s.close_status_code_ = ntohs(cs);
399 }
400
401 message_complete = true;
402 return true;
403 }
404 return false;
405 }
406
407 const bool is_client;
410 std::uint32_t mask;
411 std::uint64_t size;
414};
415
416namespace Client {
417
418struct Config : public RC<thread_unsafe_refcount>
419{
421
422 std::string origin;
423 std::string protocol;
426
427 // compression
428 bool compress = false;
429 size_t compress_threshold = 256;
430};
431
432class PerRequest : public RC<thread_unsafe_refcount>
433{
434 private:
436
437 public:
439
441 : conf(validate_conf(std::move(conf_arg))),
442 sender(conf->rng),
443 receiver(true)
444 {
445 }
446
447 void client_headers(std::ostream &os)
448 {
450 os << "Sec-WebSocket-Key: " << websocket_key << "\r\n";
451 os << "Sec-WebSocket-Version: 13\r\n";
452 if (!conf->protocol.empty())
453 os << "Sec-WebSocket-Protocol: " << conf->protocol << "\r\n";
454 os << "Connection: Upgrade\r\n";
455 os << "Upgrade: websocket\r\n";
456 if (!conf->origin.empty())
457 os << "Origin: " << conf->origin << "\r\n";
458 }
459
460 bool confirm_websocket_key(const std::string &ws_accept) const
461 {
462 return ws_accept == accept_confirmation(*conf->digest_factory, websocket_key);
463 }
464
467
468 private:
470 {
471 if (!conf)
472 throw websocket_error("no config");
473 if (!conf->digest_factory)
474 throw websocket_error("no digest factory in config");
475 return conf;
476 }
477
479 {
480 std::uint8_t data[16];
481 conf->rng->rand_bytes(data, sizeof(data));
482 websocket_key = base64->encode(data, sizeof(data));
483 }
484
485 std::string websocket_key;
486};
487
488} // namespace Client
489
490namespace Server {
491
492struct Config : public RC<thread_unsafe_refcount>
493{
495
496 std::string protocol;
498};
499
500class PerRequest : public RC<thread_unsafe_refcount>
501{
502 private:
504
505 public:
507
509 : conf(validate_conf(std::move(conf_arg))),
511 receiver(false)
512 {
513 }
514
515 void set_websocket_key(const std::string &websocket_key)
516 {
517 websocket_accept = accept_confirmation(*conf->digest_factory, websocket_key);
518 }
519
520 void server_headers(std::ostream &os)
521 {
522 os << "Upgrade: websocket\r\n";
523 os << "Connection: Upgrade\r\n";
524 if (!websocket_accept.empty())
525 os << "Sec-WebSocket-Accept: " << websocket_accept << "\r\n";
526 if (!conf->protocol.empty())
527 os << "Sec-WebSocket-Protocol: " << conf->protocol << "\r\n";
528 }
529
532
533 private:
535 {
536 if (!conf)
537 throw websocket_error("no config");
538 if (!conf->digest_factory)
539 throw websocket_error("no digest factory in config");
540 return conf;
541 }
542
543 std::string websocket_accept;
544};
545
546} // namespace Server
547
548} // namespace openvpn::WebSocket
std::string encode(const V &data) const
Definition base64.hpp:139
void clear()
Clears the contents of the buffer.
Definition buffer.hpp:1824
void add_flags(const BufferFlags flags)
Sets the specified flags for the buffer.
Definition buffer.hpp:1837
BufferAllocatedType & realign(const size_t headroom)
Realign the buffer with the specified headroom.
Definition buffer.hpp:1760
void append(const B &other)
Append data from another buffer to this buffer.
Definition buffer.hpp:1626
void prepend(const T *data, const size_t size)
Prepend data to the buffer.
Definition buffer.hpp:1575
bool allocated() const
Returns true if the data memory is defined (allocated).
Definition buffer.hpp:1230
size_t size() const
Returns the size of the buffer in T objects.
Definition buffer.hpp:1242
T * data()
Get a mutable pointer to the start of the array.
Definition buffer.hpp:1450
void advance(const size_t delta)
Advances the buffer by the specified delta.
Definition buffer.hpp:1277
auto * read_alloc(const size_t size)
Allocate memory and read data from the buffer into the allocated memory.
Definition buffer.hpp:1343
size_t offset() const
Returns the current offset (headroom) into the buffer.
Definition buffer.hpp:1218
void read(NCT *data, const size_t size)
Read data from the buffer into the specified memory location.
Definition buffer.hpp:1331
std::string final_base64()
Definition hashstr.hpp:74
void update(const std::string &str)
Definition hashstr.hpp:32
Reference count base class for objects tracked by RCPtr. Disallows copying and assignment.
Definition rc.hpp:912
T rand_get()
Create a data object filled with random bytes.
Definition randapi.hpp:86
Abstract base class for cryptographically strong random number generators.
Definition randapi.hpp:228
bool confirm_websocket_key(const std::string &ws_accept) const
static Config::Ptr validate_conf(Config::Ptr conf)
void client_headers(std::ostream &os)
static std::string opcode_to_string(const unsigned int opcode)
Definition websocket.hpp:58
static constexpr size_t MAX_HEAD
Definition websocket.hpp:47
void verify_message_complete() const
void add_buf(BufferAllocated &&inbuf)
Receiver(const bool is_client_arg)
void prepend_payload_length(Buffer &buf, const size_t len) const
void frame(Buffer &buf, const Status &s) const
Sender(StrongRandomAPI::Ptr cli_rng_arg)
StrongRandomAPI::Ptr cli_rng
void set_websocket_key(const std::string &websocket_key)
void server_headers(std::ostream &os)
static Config::Ptr validate_conf(Config::Ptr conf)
unsigned int opcode() const
Status(unsigned int opcode, bool fin=true, uint16_t close_status_code=0)
std::string to_string() const
bool operator!=(const Status &rhs) const
bool operator==(const Status &rhs) const
Status(const Status &ref, const unsigned int opcode)
#define OPENVPN_EXCEPTION(C)
constexpr BufferFlags GROW(1u<< 2)
if enabled, buffer will grow (otherwise buffer_full exception will be thrown)
std::uint64_t rev64(const std::uint64_t value)
Definition endian64.hpp:32
std::string accept_confirmation(DigestFactory &digest_factory, const std::string &websocket_key)
Definition websocket.hpp:35
BufferType< unsigned char > Buffer
Definition buffer.hpp:1895
const Base64 * base64
Definition base64.hpp:299
Implementation of the base classes for random number generators.
std::string ret
std::ostringstream os
void prepend_mask(Buffer &buf) const
Definition websocket.hpp:92