Coverage Report

Created: 2025-08-28 09:57

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