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

Commit 15ed75a

Browse files
committed
quic: stateless reset generate strategy
Generate stateless reset token cryptographically Fixes: #62
1 parent 4e9a96e commit 15ed75a

File tree

13 files changed

+246
-36
lines changed

13 files changed

+246
-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
@@ -125,6 +125,23 @@ bool GenerateRandData(uint8_t* buf, size_t len) {
125125
return true;
126126
}
127127

128+
bool GenerateResetToken(
129+
uint8_t* token,
130+
uint8_t* secret,
131+
size_t secretlen,
132+
const ngtcp2_cid* cid) {
133+
ngtcp2_crypto_ctx ctx;
134+
ngtcp2_crypto_ctx_initial(&ctx);
135+
return NGTCP2_OK(ngtcp2_crypto_hkdf_expand(
136+
token,
137+
NGTCP2_STATELESS_RESET_TOKENLEN,
138+
&ctx.md,
139+
secret,
140+
secretlen,
141+
cid->data,
142+
cid->datalen));
143+
}
144+
128145
// The Retry Token is an encrypted token that is sent to the client
129146
// by the server as part of the path validation flow. The plaintext
130147
// 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: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -678,20 +678,37 @@ 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+
// Generates a new stateless reset token randomly
696+
void RandomStatelessResetTokenStrategy::GetNewStatelessToken(
697+
QuicSession* session,
698+
ngtcp2_cid* cid,
699+
uint8_t* token,
700+
size_t tokenlen) {
701+
EntropySource(token, tokenlen);
702+
}
703+
704+
void CryptoStatelessResetTokenStrategy::GetNewStatelessToken(
705+
QuicSession* session,
706+
ngtcp2_cid* cid,
707+
uint8_t* token,
708+
size_t tokenlen) {
709+
std::array<uint8_t, NGTCP2_STATELESS_RESET_TOKENLEN>* secret =
710+
session->Socket()->GetSessionResetSecret();
711+
CHECK(GenerateResetToken(token, secret->data(), secret->size(), cid));
695712
}
696713

697714
// Check required capabilities were not excluded from the OpenSSL build:
@@ -1260,6 +1277,7 @@ QuicSession::QuicSession(
12601277
reinterpret_cast<double*>(&recovery_stats_)) {
12611278
PushListener(&default_listener_);
12621279
SetConnectionIDStrategory(&default_connection_id_strategy_);
1280+
SetStatelessResetTokenStrategy(&default_stateless_reset_strategy_);
12631281
crypto_context_.reset(new QuicCryptoContext(this, ctx, side, options));
12641282
application_.reset(SelectApplication(this));
12651283
if (rcid != nullptr)
@@ -1634,6 +1652,12 @@ void QuicSession::SetConnectionIDStrategory(ConnectionIDStrategy* strategy) {
16341652
connection_id_strategy_ = strategy;
16351653
}
16361654

1655+
void QuicSession::SetStatelessResetTokenStrategy(
1656+
StatelessResetTokenStrategy* strategy) {
1657+
CHECK_NOT_NULL(strategy);
1658+
stateless_reset_strategy_ = strategy;
1659+
}
1660+
16371661
// Generates and associates a new connection ID for this QuicSession.
16381662
// ngtcp2 will call this multiple times at the start of a new connection
16391663
// in order to build a pool of available CIDs.
@@ -1643,7 +1667,16 @@ int QuicSession::GetNewConnectionID(
16431667
size_t cidlen) {
16441668
DCHECK(!IsFlagSet(QUICSESSION_FLAG_DESTROYED));
16451669
CHECK_NOT_NULL(connection_id_strategy_);
1646-
connection_id_strategy_->GetNewConnectionID(this, cid, token, cidlen);
1670+
connection_id_strategy_->GetNewConnectionID(
1671+
this,
1672+
cid,
1673+
cidlen);
1674+
stateless_reset_strategy_->GetNewStatelessToken(
1675+
this,
1676+
cid,
1677+
token,
1678+
NGTCP2_STATELESS_RESET_TOKENLEN);
1679+
16471680
AssociateCID(cid);
16481681
return 0;
16491682
}

src/node_quic_session.h

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -292,7 +292,6 @@ class ConnectionIDStrategy {
292292
virtual void GetNewConnectionID(
293293
QuicSession* session,
294294
ngtcp2_cid* cid,
295-
uint8_t* token,
296295
size_t cidlen) = 0;
297296
};
298297

@@ -301,10 +300,27 @@ class RandomConnectionIDStrategy : public ConnectionIDStrategy {
301300
void GetNewConnectionID(
302301
QuicSession* session,
303302
ngtcp2_cid* cid,
304-
uint8_t* token,
305303
size_t cidlen) override;
306304
};
307305

306+
class StatelessResetTokenStrategy {
307+
public:
308+
virtual void GetNewStatelessToken(
309+
QuicSession* session,
310+
ngtcp2_cid* cid,
311+
uint8_t* token,
312+
size_t tokenlen) = 0;
313+
};
314+
315+
class CryptoStatelessResetTokenStrategy : public StatelessResetTokenStrategy {
316+
public:
317+
void GetNewStatelessToken(
318+
QuicSession* session,
319+
ngtcp2_cid* cid,
320+
uint8_t* token,
321+
size_t tokenlen) override;
322+
};
323+
308324
// The QuicCryptoContext class encapsulates all of the crypto/TLS
309325
// handshake details on behalf of a QuicSession.
310326
class QuicCryptoContext : public MemoryRetainer {
@@ -933,6 +949,7 @@ class QuicSession : public AsyncWrap,
933949
void RemoveListener(QuicSessionListener* listener);
934950

935951
void SetConnectionIDStrategory(ConnectionIDStrategy* strategy);
952+
void SetStatelessResetTokenStrategy(StatelessResetTokenStrategy* strategy);
936953

937954
// Report that the stream data is flow control blocked
938955
void StreamDataBlocked(int64_t stream_id);
@@ -1296,6 +1313,9 @@ class QuicSession : public AsyncWrap,
12961313
ConnectionIDStrategy* connection_id_strategy_ = nullptr;
12971314
RandomConnectionIDStrategy default_connection_id_strategy_;
12981315

1316+
StatelessResetTokenStrategy* stateless_reset_strategy_ = nullptr;
1317+
CryptoStatelessResetTokenStrategy default_stateless_reset_strategy_;
1318+
12991319
QuicSessionListener* listener_ = nullptr;
13001320
JSQuicSessionListener default_listener_;
13011321

0 commit comments

Comments
 (0)