Coverage Report

Created: 2025-12-10 07:58

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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