/src/node/src/quic/tokens.h
Line | Count | Source (jump to first uncovered line) |
1 | | #pragma once |
2 | | |
3 | | #if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS |
4 | | #if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC |
5 | | |
6 | | #include <memory_tracker.h> |
7 | | #include <ngtcp2/ngtcp2_crypto.h> |
8 | | #include <node_internals.h> |
9 | | #include <node_sockaddr.h> |
10 | | #include "cid.h" |
11 | | |
12 | | namespace node { |
13 | | namespace quic { |
14 | | |
15 | | // TokenSecrets are used to generate things like stateless reset tokens, |
16 | | // retry tokens, and token packets. They are always QUIC_TOKENSECRET_LEN |
17 | | // bytes in length. |
18 | | // |
19 | | // In the default case, token secrets will always be generated randomly. |
20 | | // User code will be given the option to provide a secret directly |
21 | | // however. |
22 | | class TokenSecret final : public MemoryRetainer { |
23 | | public: |
24 | | static constexpr int QUIC_TOKENSECRET_LEN = 16; |
25 | | |
26 | | // Generate a random secret. |
27 | | TokenSecret(); |
28 | | |
29 | | // Copy the given secret. The uint8_t* is assumed |
30 | | // to be QUIC_TOKENSECRET_LEN in length. Note that |
31 | | // the length is not verified so care must be taken |
32 | | // when this constructor is used. |
33 | | explicit TokenSecret(const uint8_t* secret); |
34 | | ~TokenSecret(); |
35 | | |
36 | 0 | TokenSecret(const TokenSecret&) = default; |
37 | 0 | TokenSecret& operator=(const TokenSecret&) = default; |
38 | | TokenSecret(TokenSecret&&) = delete; |
39 | | TokenSecret& operator=(TokenSecret&&) = delete; |
40 | | |
41 | | operator const uint8_t*() const; |
42 | | uint8_t operator[](int pos) const; |
43 | | |
44 | | std::string ToString() const; |
45 | | |
46 | | SET_NO_MEMORY_INFO() |
47 | | SET_MEMORY_INFO_NAME(TokenSecret) |
48 | | SET_SELF_SIZE(TokenSecret) |
49 | | |
50 | | private: |
51 | | operator const char*() const; |
52 | | uint8_t buf_[QUIC_TOKENSECRET_LEN]; |
53 | | }; |
54 | | |
55 | | // A stateless reset token is used when a QUIC endpoint receives a QUIC packet |
56 | | // with a short header but the associated connection ID cannot be matched to any |
57 | | // known Session. In such cases, the receiver may choose to send a subtle opaque |
58 | | // indication to the sending peer that state for the Session has apparently been |
59 | | // lost. For any on- or off- path attacker, a stateless reset packet resembles |
60 | | // any other QUIC packet with a short header. In order to be successfully |
61 | | // handled as a stateless reset, the peer must have already seen a reset token |
62 | | // issued to it associated with the given CID. The token itself is opaque to the |
63 | | // peer that receives is but must be possible to statelessly recreate by the |
64 | | // peer that originally created it. The actual implementation is Node.js |
65 | | // specific but we currently defer to a utility function provided by ngtcp2. |
66 | | // |
67 | | // QUIC leaves the generation of stateless session tokens up to the |
68 | | // implementation to figure out. The idea, however, is that it ought to be |
69 | | // possible to generate a stateless reset token reliably even when all state |
70 | | // for a connection has been lost. We use the cid as it is the only reliably |
71 | | // consistent bit of data we have when a session is destroyed. |
72 | | // |
73 | | // StatlessResetTokens are always kStatelessTokenLen bytes, |
74 | | // as are the secrets used to generate the token. |
75 | | class StatelessResetToken final : public MemoryRetainer { |
76 | | public: |
77 | | static constexpr int kStatelessTokenLen = NGTCP2_STATELESS_RESET_TOKENLEN; |
78 | | |
79 | | StatelessResetToken(); |
80 | | |
81 | | // Generates a stateless reset token using HKDF with the cid and token secret |
82 | | // as input. The token secret is either provided by user code when an Endpoint |
83 | | // is created or is generated randomly. |
84 | | StatelessResetToken(const TokenSecret& secret, const CID& cid); |
85 | | |
86 | | // Generates a stateless reset token using the given token storage. |
87 | | // The StatelessResetToken wraps the token and does not take ownership. |
88 | | // The token storage must be at least kStatelessTokenLen bytes in length. |
89 | | // The length is not verified so care must be taken when using this |
90 | | // constructor. |
91 | | StatelessResetToken(uint8_t* token, |
92 | | const TokenSecret& secret, |
93 | | const CID& cid); |
94 | | |
95 | | // Wraps the given token. Does not take over ownership of the token storage. |
96 | | // The token must be at least kStatelessTokenLen bytes in length. |
97 | | // The length is not verified so care must be taken when using this |
98 | | // constructor. |
99 | | explicit StatelessResetToken(const uint8_t* token); |
100 | | |
101 | | StatelessResetToken(const StatelessResetToken& other); |
102 | | StatelessResetToken(StatelessResetToken&&) = delete; |
103 | | |
104 | | std::string ToString() const; |
105 | | |
106 | | operator const uint8_t*() const; |
107 | | operator bool() const; |
108 | | |
109 | | bool operator==(const StatelessResetToken& other) const; |
110 | | bool operator!=(const StatelessResetToken& other) const; |
111 | | |
112 | | struct Hash { |
113 | | size_t operator()(const StatelessResetToken& token) const; |
114 | | }; |
115 | | |
116 | | SET_NO_MEMORY_INFO() |
117 | | SET_MEMORY_INFO_NAME(StatelessResetToken) |
118 | | SET_SELF_SIZE(StatelessResetToken) |
119 | | |
120 | | template <typename T> |
121 | | using Map = |
122 | | std::unordered_map<StatelessResetToken, T, StatelessResetToken::Hash>; |
123 | | |
124 | | static StatelessResetToken kInvalid; |
125 | | |
126 | | private: |
127 | | operator const char*() const; |
128 | | |
129 | | const uint8_t* ptr_; |
130 | | uint8_t buf_[NGTCP2_STATELESS_RESET_TOKENLEN]; |
131 | | }; |
132 | | |
133 | | // A RETRY packet communicates a retry token to the client. Retry tokens are |
134 | | // generated only by QUIC servers for the purpose of validating the network path |
135 | | // between a client and server. The content payload of the RETRY packet is |
136 | | // opaque to the clientand must not be guessable by on- or off-path attackers. |
137 | | // |
138 | | // A QUIC server sends a RETRY token as a way of initiating explicit path |
139 | | // validation in response to an initial QUIC packet. The client, upon receiving |
140 | | // a RETRY, must abandon the initial connection attempt and try again with the |
141 | | // received retry token included with the new initial packet sent to the server. |
142 | | // If the server is performing explicit validation, it will look for the |
143 | | // presence of the retry token and attempt to validate it if found. The internal |
144 | | // structure of the retry token must be meaningful to the server, and the server |
145 | | // must be able to validate that the token is correct without relying on any |
146 | | // state left over from the previous connection attempt. We use an |
147 | | // implementation that is provided by ngtcp2. |
148 | | // |
149 | | // The token secret must be kept private on the QUIC server that generated the |
150 | | // retry. When multiple QUIC servers are used in a cluster, it cannot be |
151 | | // guaranteed that the same QUIC server instance will receive the subsequent new |
152 | | // Initial packet. Therefore, all QUIC servers in the cluster should either |
153 | | // share or be aware of the same token secret or a mechanism needs to be |
154 | | // implemented to ensure that subsequent packets are routed to the same QUIC |
155 | | // server instance. |
156 | | class RetryToken final : public MemoryRetainer { |
157 | | public: |
158 | | // The token prefix that is used to differentiate between a retry token |
159 | | // and a regular token. |
160 | | static constexpr uint8_t kTokenMagic = NGTCP2_CRYPTO_TOKEN_MAGIC_RETRY; |
161 | | static constexpr int kRetryTokenLen = NGTCP2_CRYPTO_MAX_RETRY_TOKENLEN; |
162 | | |
163 | | static constexpr uint64_t QUIC_DEFAULT_RETRYTOKEN_EXPIRATION = |
164 | | 10 * NGTCP2_SECONDS; |
165 | | static constexpr uint64_t QUIC_MIN_RETRYTOKEN_EXPIRATION = 1 * NGTCP2_SECONDS; |
166 | | |
167 | | // Generates a new retry token. |
168 | | RetryToken(uint32_t version, |
169 | | const SocketAddress& address, |
170 | | const CID& retry_cid, |
171 | | const CID& odcid, |
172 | | const TokenSecret& token_secret); |
173 | | |
174 | | // Wraps the given retry token |
175 | | RetryToken(const uint8_t* token, size_t length); |
176 | | |
177 | | // Validates the retry token given the input. If the token is valid, |
178 | | // the embedded original CID will be extracted from the token an |
179 | | // returned. If the token is invalid, std::nullopt will be returned. |
180 | | std::optional<CID> Validate( |
181 | | uint32_t version, |
182 | | const SocketAddress& address, |
183 | | const CID& cid, |
184 | | const TokenSecret& token_secret, |
185 | | uint64_t verification_expiration = QUIC_DEFAULT_RETRYTOKEN_EXPIRATION); |
186 | | |
187 | | operator const ngtcp2_vec&() const; |
188 | | operator const ngtcp2_vec*() const; |
189 | | operator bool() const; |
190 | | |
191 | | std::string ToString() const; |
192 | | |
193 | | SET_NO_MEMORY_INFO() |
194 | | SET_MEMORY_INFO_NAME(RetryToken) |
195 | | SET_SELF_SIZE(RetryToken) |
196 | | |
197 | | private: |
198 | | operator const char*() const; |
199 | | uint8_t buf_[kRetryTokenLen]; |
200 | | const ngtcp2_vec ptr_; |
201 | | }; |
202 | | |
203 | | // A NEW_TOKEN packet communicates a regular token to a client that the server |
204 | | // would like the client to send in the header of an initial packet for a |
205 | | // future connection. It is similar to RETRY and used for the same purpose, |
206 | | // except a NEW_TOKEN is used in advance of the client establishing a new |
207 | | // connection and a RETRY is sent in response to the client trying to open |
208 | | // a new connection. |
209 | | class RegularToken final : public MemoryRetainer { |
210 | | public: |
211 | | // The token prefix that is used to differentiate between a retry token |
212 | | // and a regular token. |
213 | | static constexpr uint8_t kTokenMagic = NGTCP2_CRYPTO_TOKEN_MAGIC_REGULAR; |
214 | | static constexpr int kRegularTokenLen = NGTCP2_CRYPTO_MAX_REGULAR_TOKENLEN; |
215 | | static constexpr uint64_t QUIC_DEFAULT_REGULARTOKEN_EXPIRATION = |
216 | | 10 * NGTCP2_SECONDS; |
217 | | static constexpr uint64_t QUIC_MIN_REGULARTOKEN_EXPIRATION = |
218 | | 1 * NGTCP2_SECONDS; |
219 | | |
220 | | RegularToken(); |
221 | | |
222 | | // Generates a new retry token. |
223 | | RegularToken(uint32_t version, |
224 | | const SocketAddress& address, |
225 | | const TokenSecret& token_secret); |
226 | | |
227 | | // Wraps the given retry token |
228 | | RegularToken(const uint8_t* token, size_t length); |
229 | | |
230 | | // Validates the retry token given the input. |
231 | | bool Validate( |
232 | | uint32_t version, |
233 | | const SocketAddress& address, |
234 | | const TokenSecret& token_secret, |
235 | | uint64_t verification_expiration = QUIC_DEFAULT_REGULARTOKEN_EXPIRATION); |
236 | | |
237 | | operator const ngtcp2_vec&() const; |
238 | | operator const ngtcp2_vec*() const; |
239 | | |
240 | | operator bool() const; |
241 | | |
242 | | std::string ToString() const; |
243 | | |
244 | | SET_NO_MEMORY_INFO() |
245 | | SET_MEMORY_INFO_NAME(RetryToken) |
246 | | SET_SELF_SIZE(RetryToken) |
247 | | |
248 | | private: |
249 | | operator const char*() const; |
250 | | uint8_t buf_[kRegularTokenLen]; |
251 | | const ngtcp2_vec ptr_; |
252 | | }; |
253 | | |
254 | | } // namespace quic |
255 | | } // namespace node |
256 | | |
257 | | #endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC |
258 | | #endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS |