Skip to content
This repository was archived by the owner on Aug 11, 2020. It is now read-only.

Commit 089e9dc

Browse files
committed
quic: stateless reset generate strategy
Generate stateless reset token cryptographically Fixes: #62
1 parent 08bb8c9 commit 089e9dc

File tree

13 files changed

+239
-36
lines changed

13 files changed

+239
-36
lines changed

doc/api/errors.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1671,6 +1671,10 @@ TBD
16711671

16721672
TBD
16731673

1674+
<a id="ERR_QUICSOCKET_INVALID_STATELESS_RESET_SECRET_LENGTH"></a>
1675+
### ERR_QUICSOCKET_INVALID_STATELESS_RESET_SECRET_LENGTH
1676+
TBD
1677+
16741678
<a id="ERR_QUICSOCKET_LISTENING"></a>
16751679
### ERR_QUICSOCKET_LISTENING
16761680

doc/api/quic.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1596,6 +1596,15 @@ Changing TTL values is typically done for network probes or when multicasting.
15961596
The argument to `socket.setTTL()` is a number of hops between `1` and `255`.
15971597
The default on most systems is `64` but can vary.
15981598

1599+
#### quicsocket.statelessResetCount
1600+
<!-- YAML
1601+
added: REPLACEME
1602+
-->
1603+
1604+
* Type: {BigInt}
1605+
1606+
A `BigInt` that represents the number of stateless resets that have been sent.
1607+
15991608
#### quicsocket.unref();
16001609
<!-- YAML
16011610
added: REPLACEME

lib/internal/errors.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1128,6 +1128,9 @@ E('ERR_QUICSOCKET_CLOSING',
11281128
'Cannot call %s while a QuicSocket is closing', Error);
11291129
E('ERR_QUICSOCKET_DESTROYED',
11301130
'Cannot call %s after a QuicSocket has been destroyed', Error);
1131+
E('ERR_QUICSOCKET_INVALID_STATELESS_RESET_SECRET_LENGTH',
1132+
'The stateResetToken must be exactly 16-bytes in length',
1133+
Error);
11311134
E('ERR_QUICSOCKET_LISTENING',
11321135
'This QuicSocket is already listening', Error);
11331136
E('ERR_QUICSOCKET_UNBOUND',

lib/internal/quic/core.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@ const {
168168
IDX_QUIC_SOCKET_STATS_PACKETS_SENT,
169169
IDX_QUIC_SOCKET_STATS_SERVER_SESSIONS,
170170
IDX_QUIC_SOCKET_STATS_CLIENT_SESSIONS,
171+
IDX_QUIC_SOCKET_STATS_STATELESS_RESET_COUNT,
171172
ERR_INVALID_REMOTE_TRANSPORT_PARAMS,
172173
ERR_INVALID_TLS_SESSION_TICKET,
173174
NGTCP2_PATH_VALIDATION_RESULT_FAILURE,
@@ -809,6 +810,9 @@ class QuicSocket extends EventEmitter {
809810

810811
// Whether qlog should be enabled for sessions
811812
qlog,
813+
814+
// Stateless reset token secret (16 byte buffer)
815+
statelessResetSecret
812816
} = validateQuicSocketOptions(options);
813817
super();
814818
const socketOptions =
@@ -827,7 +831,8 @@ class QuicSocket extends EventEmitter {
827831
socketOptions,
828832
retryTokenTimeout,
829833
maxConnectionsPerHost,
830-
qlog);
834+
qlog,
835+
statelessResetSecret);
831836
udpHandle.quicSocket = handle;
832837
handle[owner_symbol] = this;
833838
this[async_id_symbol] = handle.getAsyncId();
@@ -1397,6 +1402,11 @@ class QuicSocket extends EventEmitter {
13971402
return stats[IDX_QUIC_SOCKET_STATS_CLIENT_SESSIONS];
13981403
}
13991404

1405+
get statelessResetCount() {
1406+
const stats = this.#stats || this[kHandle].stats;
1407+
return stats[IDX_QUIC_SOCKET_STATS_STATELESS_RESET_COUNT];
1408+
}
1409+
14001410
setDiagnosticPacketLoss(options) {
14011411
if (this.#state === kSocketDestroyed)
14021412
throw new ERR_QUICSOCKET_DESTROYED('setDiagnosticPacketLoss');

lib/internal/quic/util.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const {
66
ERR_INVALID_ARG_VALUE,
77
ERR_OUT_OF_RANGE,
88
ERR_QUICSESSION_INVALID_DCID,
9+
ERR_QUICSOCKET_INVALID_STATELESS_RESET_SECRET_LENGTH,
910
},
1011
} = require('internal/errors');
1112

@@ -369,6 +370,7 @@ function validateQuicSocketOptions(options) {
369370
validateAddressLRU = false,
370371
retryTokenTimeout = DEFAULT_RETRYTOKEN_EXPIRATION,
371372
qlog = false,
373+
statelessResetSecret,
372374
} = { ...options };
373375
validateBindOptions(port, address);
374376
if (typeof type !== 'string')
@@ -411,6 +413,17 @@ function validateQuicSocketOptions(options) {
411413
throw new ERR_INVALID_ARG_TYPE('options.qlog', 'boolean', qlog);
412414
}
413415

416+
if (statelessResetSecret !== undefined) {
417+
if (!isArrayBufferView(statelessResetSecret)) {
418+
throw new ERR_INVALID_ARG_TYPE(
419+
'options.statelessResetSecret',
420+
['string', 'Buffer', 'TypedArray', 'DataView'],
421+
statelessResetSecret);
422+
}
423+
if (statelessResetSecret.length !== 16)
424+
throw new ERR_QUICSOCKET_INVALID_STATELESS_RESET_SECRET_LENGTH();
425+
}
426+
414427
return {
415428
address,
416429
autoClose,
@@ -426,6 +439,7 @@ function validateQuicSocketOptions(options) {
426439
validateAddress: validateAddress || validateAddressLRU,
427440
validateAddressLRU,
428441
qlog,
442+
statelessResetSecret,
429443
};
430444
}
431445

src/node_quic.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,7 @@ void Initialize(Local<Object> target,
233233
NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SOCKET_STATS_PACKETS_SENT);
234234
NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SOCKET_STATS_SERVER_SESSIONS);
235235
NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SOCKET_STATS_CLIENT_SESSIONS);
236+
NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SOCKET_STATS_STATELESS_RESET_COUNT);
236237

237238
NODE_DEFINE_CONSTANT(constants, IDX_HTTP3_QPACK_MAX_TABLE_CAPACITY);
238239
NODE_DEFINE_CONSTANT(constants, IDX_HTTP3_QPACK_BLOCKED_STREAMS);

src/node_quic_crypto.cc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,23 @@ void GenerateRandData(uint8_t* buf, size_t len) {
123123
}
124124
} // namespace
125125

126+
bool GenerateResetToken(
127+
uint8_t* token,
128+
uint8_t* secret,
129+
size_t secretlen,
130+
const ngtcp2_cid* cid) {
131+
ngtcp2_crypto_ctx ctx;
132+
ngtcp2_crypto_ctx_initial(&ctx);
133+
return NGTCP2_OK(ngtcp2_crypto_hkdf_expand(
134+
token,
135+
NGTCP2_STATELESS_RESET_TOKENLEN,
136+
&ctx.md,
137+
secret,
138+
secretlen,
139+
cid->data,
140+
cid->datalen));
141+
}
142+
126143
// The Retry Token is an encrypted token that is sent to the client
127144
// by the server as part of the path validation flow. The plaintext
128145
// format within the token is opaque and only meaningful the server.

src/node_quic_crypto.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ bool UpdateKey(
6363
std::vector<uint8_t>* current_rx_secret,
6464
std::vector<uint8_t>* current_tx_secret);
6565

66+
bool GenerateResetToken(
67+
uint8_t* token,
68+
uint8_t* secret,
69+
size_t secretlen,
70+
const ngtcp2_cid* cid);
71+
6672
bool GenerateRetryToken(
6773
uint8_t* token,
6874
size_t* tokenlen,

src/node_quic_session.cc

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -678,20 +678,28 @@ void JSQuicSessionListener::OnQLog(const uint8_t* data, size_t len) {
678678
&str);
679679
}
680680

681-
// Generates and associates a new connection ID for this QuicSession.
681+
// Generates a new connection ID for this QuicSession.
682682
// ngtcp2 will call this multiple times at the start of a new connection
683683
// in order to build a pool of available CIDs.
684684
void RandomConnectionIDStrategy::GetNewConnectionID(
685685
QuicSession* session,
686686
ngtcp2_cid* cid,
687-
uint8_t* token,
688687
size_t cidlen) {
689688
cid->datalen = cidlen;
690689
// cidlen shouldn't ever be zero here but just in case that
691690
// behavior changes in ngtcp2 in the future...
692691
if (cidlen > 0)
693692
EntropySource(cid->data, cidlen);
694-
EntropySource(token, NGTCP2_STATELESS_RESET_TOKENLEN);
693+
}
694+
695+
void CryptoStatelessResetTokenStrategy::GetNewStatelessToken(
696+
QuicSession* session,
697+
ngtcp2_cid* cid,
698+
uint8_t* token,
699+
size_t tokenlen) {
700+
std::array<uint8_t, NGTCP2_STATELESS_RESET_TOKENLEN>* secret =
701+
session->Socket()->GetSessionResetSecret();
702+
CHECK(GenerateResetToken(token, secret->data(), secret->size(), cid));
695703
}
696704

697705
// Check required capabilities were not excluded from the OpenSSL build:
@@ -1260,6 +1268,7 @@ QuicSession::QuicSession(
12601268
reinterpret_cast<double*>(&recovery_stats_)) {
12611269
PushListener(&default_listener_);
12621270
SetConnectionIDStrategory(&default_connection_id_strategy_);
1271+
SetStatelessResetTokenStrategy(&default_stateless_reset_strategy_);
12631272
crypto_context_.reset(new QuicCryptoContext(this, ctx, side, options));
12641273
application_.reset(SelectApplication(this));
12651274
if (rcid != nullptr)
@@ -1634,6 +1643,12 @@ void QuicSession::SetConnectionIDStrategory(ConnectionIDStrategy* strategy) {
16341643
connection_id_strategy_ = strategy;
16351644
}
16361645

1646+
void QuicSession::SetStatelessResetTokenStrategy(
1647+
StatelessResetTokenStrategy* strategy) {
1648+
CHECK_NOT_NULL(strategy);
1649+
stateless_reset_strategy_ = strategy;
1650+
}
1651+
16371652
// Generates and associates a new connection ID for this QuicSession.
16381653
// ngtcp2 will call this multiple times at the start of a new connection
16391654
// in order to build a pool of available CIDs.
@@ -1643,7 +1658,16 @@ int QuicSession::GetNewConnectionID(
16431658
size_t cidlen) {
16441659
DCHECK(!IsFlagSet(QUICSESSION_FLAG_DESTROYED));
16451660
CHECK_NOT_NULL(connection_id_strategy_);
1646-
connection_id_strategy_->GetNewConnectionID(this, cid, token, cidlen);
1661+
connection_id_strategy_->GetNewConnectionID(
1662+
this,
1663+
cid,
1664+
cidlen);
1665+
stateless_reset_strategy_->GetNewStatelessToken(
1666+
this,
1667+
cid,
1668+
token,
1669+
NGTCP2_STATELESS_RESET_TOKENLEN);
1670+
16471671
AssociateCID(cid);
16481672
return 0;
16491673
}

src/node_quic_session.h

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,6 @@ class ConnectionIDStrategy {
295295
virtual void GetNewConnectionID(
296296
QuicSession* session,
297297
ngtcp2_cid* cid,
298-
uint8_t* token,
299298
size_t cidlen) = 0;
300299
};
301300

@@ -304,10 +303,27 @@ class RandomConnectionIDStrategy : public ConnectionIDStrategy {
304303
void GetNewConnectionID(
305304
QuicSession* session,
306305
ngtcp2_cid* cid,
307-
uint8_t* token,
308306
size_t cidlen) override;
309307
};
310308

309+
class StatelessResetTokenStrategy {
310+
public:
311+
virtual void GetNewStatelessToken(
312+
QuicSession* session,
313+
ngtcp2_cid* cid,
314+
uint8_t* token,
315+
size_t tokenlen) = 0;
316+
};
317+
318+
class CryptoStatelessResetTokenStrategy : public StatelessResetTokenStrategy {
319+
public:
320+
void GetNewStatelessToken(
321+
QuicSession* session,
322+
ngtcp2_cid* cid,
323+
uint8_t* token,
324+
size_t tokenlen) override;
325+
};
326+
311327
// The QuicCryptoContext class encapsulates all of the crypto/TLS
312328
// handshake details on behalf of a QuicSession.
313329
class QuicCryptoContext : public MemoryRetainer {
@@ -935,6 +951,7 @@ class QuicSession : public AsyncWrap,
935951
void RemoveListener(QuicSessionListener* listener);
936952

937953
void SetConnectionIDStrategory(ConnectionIDStrategy* strategy);
954+
void SetStatelessResetTokenStrategy(StatelessResetTokenStrategy* strategy);
938955

939956
// Report that the stream data is flow control blocked
940957
void StreamDataBlocked(int64_t stream_id);
@@ -1298,6 +1315,9 @@ class QuicSession : public AsyncWrap,
12981315
ConnectionIDStrategy* connection_id_strategy_ = nullptr;
12991316
RandomConnectionIDStrategy default_connection_id_strategy_;
13001317

1318+
StatelessResetTokenStrategy* stateless_reset_strategy_ = nullptr;
1319+
CryptoStatelessResetTokenStrategy default_stateless_reset_strategy_;
1320+
13011321
QuicSessionListener* listener_ = nullptr;
13021322
JSQuicSessionListener default_listener_;
13031323

0 commit comments

Comments
 (0)