/src/node/src/quic/tokens.cc
Line | Count | Source |
1 | | #if HAVE_OPENSSL |
2 | | #include "guard.h" |
3 | | #ifndef OPENSSL_NO_QUIC |
4 | | #include "tokens.h" |
5 | | #include <crypto/crypto_util.h> |
6 | | #include <ngtcp2/ngtcp2_crypto.h> |
7 | | #include <node_sockaddr-inl.h> |
8 | | #include <string_bytes.h> |
9 | | #include <util-inl.h> |
10 | | #include <algorithm> |
11 | | #include "nbytes.h" |
12 | | #include "ncrypto.h" |
13 | | |
14 | | namespace node::quic { |
15 | | |
16 | | // ============================================================================ |
17 | | // TokenSecret |
18 | | |
19 | 0 | TokenSecret::TokenSecret() : buf_() { |
20 | | // As a performance optimization later, we could consider creating an entropy |
21 | | // cache here similar to what we use for random CIDs so that we do not have |
22 | | // to engage CSPRNG on every call. That, however, is suboptimal for secrets. |
23 | | // If someone manages to get visibility into that cache then they would know |
24 | | // the secrets for a larger number of tokens, which could be bad. For now, |
25 | | // generating on each call is safer, even if less performant. |
26 | 0 | CHECK(ncrypto::CSPRNG(buf_, QUIC_TOKENSECRET_LEN)); |
27 | 0 | } |
28 | | |
29 | 0 | TokenSecret::TokenSecret(const uint8_t* secret) : buf_() { |
30 | 0 | CHECK_NOT_NULL(secret); |
31 | 0 | memcpy(buf_, secret, QUIC_TOKENSECRET_LEN); |
32 | 0 | } |
33 | | |
34 | 0 | TokenSecret::~TokenSecret() { |
35 | 0 | memset(buf_, 0, QUIC_TOKENSECRET_LEN); |
36 | 0 | } |
37 | | |
38 | 0 | TokenSecret::operator const uint8_t*() const { |
39 | 0 | return buf_; |
40 | 0 | } |
41 | | |
42 | 0 | uint8_t TokenSecret::operator[](int pos) const { |
43 | 0 | CHECK_GE(pos, 0); |
44 | 0 | CHECK_LT(pos, QUIC_TOKENSECRET_LEN); |
45 | 0 | return buf_[pos]; |
46 | 0 | } |
47 | | |
48 | 0 | TokenSecret::operator const char*() const { |
49 | 0 | return reinterpret_cast<const char*>(buf_); |
50 | 0 | } |
51 | | |
52 | 0 | std::string TokenSecret::ToString() const { |
53 | 0 | char dest[QUIC_TOKENSECRET_LEN * 2]; |
54 | 0 | size_t written = |
55 | 0 | nbytes::HexEncode(*this, QUIC_TOKENSECRET_LEN, dest, arraysize(dest)); |
56 | 0 | DCHECK_EQ(written, arraysize(dest)); |
57 | 0 | return std::string(dest, written); |
58 | 0 | } |
59 | | |
60 | | // ============================================================================ |
61 | | // StatelessResetToken |
62 | | |
63 | 72 | StatelessResetToken::StatelessResetToken() : ptr_(nullptr), buf_() {} |
64 | | |
65 | 0 | StatelessResetToken::StatelessResetToken(const uint8_t* token) : ptr_(token) {} |
66 | | |
67 | | StatelessResetToken::StatelessResetToken(const TokenSecret& secret, |
68 | | const CID& cid) |
69 | 0 | : ptr_(buf_) { |
70 | 0 | CHECK_EQ(ngtcp2_crypto_generate_stateless_reset_token( |
71 | 0 | buf_, secret, kStatelessTokenLen, cid), |
72 | 0 | 0); |
73 | 0 | } |
74 | | |
75 | | StatelessResetToken::StatelessResetToken(uint8_t* token, |
76 | | const TokenSecret& secret, |
77 | | const CID& cid) |
78 | 0 | : ptr_(token) { |
79 | 0 | CHECK_EQ(ngtcp2_crypto_generate_stateless_reset_token( |
80 | 0 | token, secret, kStatelessTokenLen, cid), |
81 | 0 | 0); |
82 | 0 | } |
83 | | |
84 | | StatelessResetToken::StatelessResetToken(const StatelessResetToken& other) |
85 | 0 | : ptr_(buf_) { |
86 | 0 | if (other) { |
87 | 0 | memcpy(buf_, other.ptr_, kStatelessTokenLen); |
88 | 0 | } else { |
89 | 0 | ptr_ = nullptr; |
90 | 0 | } |
91 | 0 | } |
92 | | |
93 | 0 | StatelessResetToken::operator const uint8_t*() const { |
94 | 0 | return ptr_ != nullptr ? ptr_ : buf_; |
95 | 0 | } |
96 | | |
97 | 0 | StatelessResetToken::operator const char*() const { |
98 | 0 | return reinterpret_cast<const char*>(ptr_ != nullptr ? ptr_ : buf_); |
99 | 0 | } |
100 | | |
101 | 0 | StatelessResetToken::operator bool() const { |
102 | 0 | return ptr_ != nullptr; |
103 | 0 | } |
104 | | |
105 | 0 | bool StatelessResetToken::operator==(const StatelessResetToken& other) const { |
106 | 0 | if (ptr_ == other.ptr_) return true; |
107 | 0 | if ((ptr_ == nullptr && other.ptr_ != nullptr) || |
108 | 0 | (ptr_ != nullptr && other.ptr_ == nullptr)) { |
109 | 0 | return false; |
110 | 0 | } |
111 | 0 | return memcmp(ptr_, other.ptr_, kStatelessTokenLen) == 0; |
112 | 0 | } |
113 | | |
114 | 0 | bool StatelessResetToken::operator!=(const StatelessResetToken& other) const { |
115 | 0 | return !(*this == other); |
116 | 0 | } |
117 | | |
118 | 0 | std::string StatelessResetToken::ToString() const { |
119 | 0 | if (ptr_ == nullptr) return std::string(); |
120 | 0 | char dest[kStatelessTokenLen * 2]; |
121 | 0 | size_t written = |
122 | 0 | nbytes::HexEncode(*this, kStatelessTokenLen, dest, arraysize(dest)); |
123 | 0 | DCHECK_EQ(written, arraysize(dest)); |
124 | 0 | return std::string(dest, written); |
125 | 0 | } |
126 | | |
127 | | size_t StatelessResetToken::Hash::operator()( |
128 | 0 | const StatelessResetToken& token) const { |
129 | 0 | size_t hash = 0; |
130 | 0 | if (token.ptr_ == nullptr) return hash; |
131 | 0 | for (size_t n = 0; n < kStatelessTokenLen; n++) |
132 | 0 | hash ^= std::hash<uint8_t>{}(token.ptr_[n]) + 0x9e3779b9 + (hash << 6) + |
133 | 0 | (hash >> 2); |
134 | 0 | return hash; |
135 | 0 | } |
136 | | |
137 | | StatelessResetToken StatelessResetToken::kInvalid; |
138 | | |
139 | | // ============================================================================ |
140 | | // RetryToken and RegularToken |
141 | | namespace { |
142 | | ngtcp2_vec GenerateRetryToken(uint8_t* buffer, |
143 | | uint32_t version, |
144 | | const SocketAddress& address, |
145 | | const CID& retry_cid, |
146 | | const CID& odcid, |
147 | 0 | const TokenSecret& token_secret) { |
148 | 0 | ssize_t ret = |
149 | 0 | ngtcp2_crypto_generate_retry_token(buffer, |
150 | 0 | token_secret, |
151 | 0 | TokenSecret::QUIC_TOKENSECRET_LEN, |
152 | 0 | version, |
153 | 0 | address.data(), |
154 | 0 | address.length(), |
155 | 0 | retry_cid, |
156 | 0 | odcid, |
157 | 0 | uv_hrtime()); |
158 | 0 | DCHECK_GE(ret, 0); |
159 | 0 | DCHECK_LE(ret, RetryToken::kRetryTokenLen); |
160 | 0 | DCHECK_EQ(buffer[0], RetryToken::kTokenMagic); |
161 | | // This shouldn't be possible but we handle it anyway just to be safe. |
162 | 0 | if (ret == 0) return {nullptr, 0}; |
163 | 0 | return {buffer, static_cast<size_t>(ret)}; |
164 | 0 | } |
165 | | |
166 | | ngtcp2_vec GenerateRegularToken(uint8_t* buffer, |
167 | | uint32_t version, |
168 | | const SocketAddress& address, |
169 | 0 | const TokenSecret& token_secret) { |
170 | 0 | ssize_t ret = |
171 | 0 | ngtcp2_crypto_generate_regular_token(buffer, |
172 | 0 | token_secret, |
173 | 0 | TokenSecret::QUIC_TOKENSECRET_LEN, |
174 | 0 | address.data(), |
175 | 0 | address.length(), |
176 | 0 | uv_hrtime()); |
177 | 0 | DCHECK_GE(ret, 0); |
178 | 0 | DCHECK_LE(ret, RegularToken::kRegularTokenLen); |
179 | 0 | DCHECK_EQ(buffer[0], RegularToken::kTokenMagic); |
180 | | // This shouldn't be possible but we handle it anyway just to be safe. |
181 | 0 | if (ret == 0) return {nullptr, 0}; |
182 | 0 | return {buffer, static_cast<size_t>(ret)}; |
183 | 0 | } |
184 | | } // namespace |
185 | | |
186 | | RetryToken::RetryToken(uint32_t version, |
187 | | const SocketAddress& address, |
188 | | const CID& retry_cid, |
189 | | const CID& odcid, |
190 | | const TokenSecret& token_secret) |
191 | | : buf_(), |
192 | 0 | ptr_(GenerateRetryToken( |
193 | 0 | buf_, version, address, retry_cid, odcid, token_secret)) {} |
194 | | |
195 | | RetryToken::RetryToken(const uint8_t* token, size_t size) |
196 | 0 | : ptr_(ngtcp2_vec{const_cast<uint8_t*>(token), size}) { |
197 | 0 | DCHECK_LE(size, RetryToken::kRetryTokenLen); |
198 | 0 | DCHECK_IMPLIES(token == nullptr, size = 0); |
199 | 0 | } |
200 | | |
201 | | std::optional<CID> RetryToken::Validate(uint32_t version, |
202 | | const SocketAddress& addr, |
203 | | const CID& dcid, |
204 | | const TokenSecret& token_secret, |
205 | 0 | uint64_t verification_expiration) { |
206 | 0 | if (ptr_.base == nullptr || ptr_.len == 0) return std::nullopt; |
207 | 0 | ngtcp2_cid ocid; |
208 | 0 | int ret = ngtcp2_crypto_verify_retry_token( |
209 | 0 | &ocid, |
210 | 0 | ptr_.base, |
211 | 0 | ptr_.len, |
212 | 0 | token_secret, |
213 | 0 | TokenSecret::QUIC_TOKENSECRET_LEN, |
214 | 0 | version, |
215 | 0 | addr.data(), |
216 | 0 | addr.length(), |
217 | 0 | dcid, |
218 | 0 | std::min(verification_expiration, QUIC_MIN_RETRYTOKEN_EXPIRATION), |
219 | 0 | uv_hrtime()); |
220 | 0 | if (ret != 0) return std::nullopt; |
221 | 0 | return std::optional<CID>(ocid); |
222 | 0 | } |
223 | | |
224 | 0 | RetryToken::operator const ngtcp2_vec&() const { |
225 | 0 | return ptr_; |
226 | 0 | } |
227 | 0 | RetryToken::operator const ngtcp2_vec*() const { |
228 | 0 | return &ptr_; |
229 | 0 | } |
230 | | |
231 | 0 | std::string RetryToken::ToString() const { |
232 | 0 | if (ptr_.base == nullptr) return std::string(); |
233 | 0 | MaybeStackBuffer<char, 32> dest(ptr_.len * 2); |
234 | 0 | size_t written = |
235 | 0 | nbytes::HexEncode(*this, ptr_.len, dest.out(), dest.length()); |
236 | 0 | DCHECK_EQ(written, dest.length()); |
237 | 0 | return std::string(dest.out(), written); |
238 | 0 | } |
239 | | |
240 | 0 | RetryToken::operator const char*() const { |
241 | 0 | return reinterpret_cast<const char*>(ptr_.base); |
242 | 0 | } |
243 | | |
244 | 0 | RetryToken::operator bool() const { |
245 | 0 | return ptr_.base != nullptr && ptr_.len > 0; |
246 | 0 | } |
247 | | |
248 | 0 | RegularToken::RegularToken() : buf_(), ptr_(ngtcp2_vec{nullptr, 0}) {} |
249 | | |
250 | | RegularToken::RegularToken(uint32_t version, |
251 | | const SocketAddress& address, |
252 | | const TokenSecret& token_secret) |
253 | | : buf_(), |
254 | 0 | ptr_(GenerateRegularToken(buf_, version, address, token_secret)) {} |
255 | | |
256 | | RegularToken::RegularToken(const uint8_t* token, size_t size) |
257 | 0 | : ptr_(ngtcp2_vec{const_cast<uint8_t*>(token), size}) { |
258 | 0 | DCHECK_LE(size, RegularToken::kRegularTokenLen); |
259 | 0 | DCHECK_IMPLIES(token == nullptr, size = 0); |
260 | 0 | } |
261 | | |
262 | 0 | RegularToken::operator bool() const { |
263 | 0 | return ptr_.base != nullptr && ptr_.len > 0; |
264 | 0 | } |
265 | | |
266 | | bool RegularToken::Validate(uint32_t version, |
267 | | const SocketAddress& addr, |
268 | | const TokenSecret& token_secret, |
269 | 0 | uint64_t verification_expiration) { |
270 | 0 | if (ptr_.base == nullptr || ptr_.len == 0) return false; |
271 | 0 | return ngtcp2_crypto_verify_regular_token( |
272 | 0 | ptr_.base, |
273 | 0 | ptr_.len, |
274 | 0 | token_secret, |
275 | 0 | TokenSecret::QUIC_TOKENSECRET_LEN, |
276 | 0 | addr.data(), |
277 | 0 | addr.length(), |
278 | 0 | std::min(verification_expiration, |
279 | 0 | QUIC_MIN_REGULARTOKEN_EXPIRATION), |
280 | 0 | uv_hrtime()) == 0; |
281 | 0 | } |
282 | | |
283 | 0 | RegularToken::operator const ngtcp2_vec&() const { |
284 | 0 | return ptr_; |
285 | 0 | } |
286 | 0 | RegularToken::operator const ngtcp2_vec*() const { |
287 | 0 | return &ptr_; |
288 | 0 | } |
289 | | |
290 | 0 | std::string RegularToken::ToString() const { |
291 | 0 | if (ptr_.base == nullptr) return std::string(); |
292 | 0 | MaybeStackBuffer<char, 32> dest(ptr_.len * 2); |
293 | 0 | size_t written = |
294 | 0 | nbytes::HexEncode(*this, ptr_.len, dest.out(), dest.length()); |
295 | 0 | DCHECK_EQ(written, dest.length()); |
296 | 0 | return std::string(dest.out(), written); |
297 | 0 | } |
298 | | |
299 | 0 | RegularToken::operator const char*() const { |
300 | 0 | return reinterpret_cast<const char*>(ptr_.base); |
301 | 0 | } |
302 | | |
303 | | } // namespace node::quic |
304 | | |
305 | | #endif // OPENSSL_NO_QUIC |
306 | | #endif // HAVE_OPENSSL |