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

quic: stateless reset generate strategy #215

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions doc/api/errors.md
Original file line number Diff line number Diff line change
Expand Up @@ -1671,6 +1671,10 @@ TBD

TBD

<a id="ERR_QUICSOCKET_INVALID_STATELESS_RESET_SECRET_LENGTH"></a>
### ERR_QUICSOCKET_INVALID_STATELESS_RESET_SECRET_LENGTH
TBD

<a id="ERR_QUICSOCKET_LISTENING"></a>
### ERR_QUICSOCKET_LISTENING

Expand Down
9 changes: 9 additions & 0 deletions doc/api/quic.md
Original file line number Diff line number Diff line change
Expand Up @@ -1596,6 +1596,15 @@ Changing TTL values is typically done for network probes or when multicasting.
The argument to `socket.setTTL()` is a number of hops between `1` and `255`.
The default on most systems is `64` but can vary.

#### quicsocket.statelessResetCount
<!-- YAML
added: REPLACEME
-->

* Type: {BigInt}

A `BigInt` that represents the number of stateless resets that have been sent.

#### quicsocket.unref();
<!-- YAML
added: REPLACEME
Expand Down
3 changes: 3 additions & 0 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1128,6 +1128,9 @@ E('ERR_QUICSOCKET_CLOSING',
'Cannot call %s while a QuicSocket is closing', Error);
E('ERR_QUICSOCKET_DESTROYED',
'Cannot call %s after a QuicSocket has been destroyed', Error);
E('ERR_QUICSOCKET_INVALID_STATELESS_RESET_SECRET_LENGTH',
'The stateResetToken must be exactly 16-bytes in length',
Error);
E('ERR_QUICSOCKET_LISTENING',
'This QuicSocket is already listening', Error);
E('ERR_QUICSOCKET_UNBOUND',
Expand Down
12 changes: 11 additions & 1 deletion lib/internal/quic/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ const {
IDX_QUIC_SOCKET_STATS_PACKETS_SENT,
IDX_QUIC_SOCKET_STATS_SERVER_SESSIONS,
IDX_QUIC_SOCKET_STATS_CLIENT_SESSIONS,
IDX_QUIC_SOCKET_STATS_STATELESS_RESET_COUNT,
ERR_INVALID_REMOTE_TRANSPORT_PARAMS,
ERR_INVALID_TLS_SESSION_TICKET,
NGTCP2_PATH_VALIDATION_RESULT_FAILURE,
Expand Down Expand Up @@ -809,6 +810,9 @@ class QuicSocket extends EventEmitter {

// Whether qlog should be enabled for sessions
qlog,

// Stateless reset token secret (16 byte buffer)
statelessResetSecret
} = validateQuicSocketOptions(options);
super();
const socketOptions =
Expand All @@ -827,7 +831,8 @@ class QuicSocket extends EventEmitter {
socketOptions,
retryTokenTimeout,
maxConnectionsPerHost,
qlog);
qlog,
statelessResetSecret);
udpHandle.quicSocket = handle;
handle[owner_symbol] = this;
this[async_id_symbol] = handle.getAsyncId();
Expand Down Expand Up @@ -1397,6 +1402,11 @@ class QuicSocket extends EventEmitter {
return stats[IDX_QUIC_SOCKET_STATS_CLIENT_SESSIONS];
}

get statelessResetCount() {
const stats = this.#stats || this[kHandle].stats;
return stats[IDX_QUIC_SOCKET_STATS_STATELESS_RESET_COUNT];
}

setDiagnosticPacketLoss(options) {
if (this.#state === kSocketDestroyed)
throw new ERR_QUICSOCKET_DESTROYED('setDiagnosticPacketLoss');
Expand Down
14 changes: 14 additions & 0 deletions lib/internal/quic/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const {
ERR_INVALID_ARG_VALUE,
ERR_OUT_OF_RANGE,
ERR_QUICSESSION_INVALID_DCID,
ERR_QUICSOCKET_INVALID_STATELESS_RESET_SECRET_LENGTH,
},
} = require('internal/errors');

Expand Down Expand Up @@ -369,6 +370,7 @@ function validateQuicSocketOptions(options) {
validateAddressLRU = false,
retryTokenTimeout = DEFAULT_RETRYTOKEN_EXPIRATION,
qlog = false,
statelessResetSecret,
} = { ...options };
validateBindOptions(port, address);
if (typeof type !== 'string')
Expand Down Expand Up @@ -411,6 +413,17 @@ function validateQuicSocketOptions(options) {
throw new ERR_INVALID_ARG_TYPE('options.qlog', 'boolean', qlog);
}

if (statelessResetSecret !== undefined) {
if (!isArrayBufferView(statelessResetSecret)) {
throw new ERR_INVALID_ARG_TYPE(
'options.statelessResetSecret',
['string', 'Buffer', 'TypedArray', 'DataView'],
statelessResetSecret);
}
if (statelessResetSecret.length !== 16)
throw new ERR_QUICSOCKET_INVALID_STATELESS_RESET_SECRET_LENGTH();
}

return {
address,
autoClose,
Expand All @@ -426,6 +439,7 @@ function validateQuicSocketOptions(options) {
validateAddress: validateAddress || validateAddressLRU,
validateAddressLRU,
qlog,
statelessResetSecret,
};
}

Expand Down
1 change: 1 addition & 0 deletions src/node_quic.cc
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ void Initialize(Local<Object> target,
NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SOCKET_STATS_PACKETS_SENT);
NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SOCKET_STATS_SERVER_SESSIONS);
NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SOCKET_STATS_CLIENT_SESSIONS);
NODE_DEFINE_CONSTANT(constants, IDX_QUIC_SOCKET_STATS_STATELESS_RESET_COUNT);

NODE_DEFINE_CONSTANT(constants, IDX_HTTP3_QPACK_MAX_TABLE_CAPACITY);
NODE_DEFINE_CONSTANT(constants, IDX_HTTP3_QPACK_BLOCKED_STREAMS);
Expand Down
23 changes: 20 additions & 3 deletions src/node_quic_crypto.cc
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ bool DeriveTokenKey(
const uint8_t* rand_data,
size_t rand_datalen,
const ngtcp2_crypto_ctx& ctx,
const std::array<uint8_t, TOKEN_SECRETLEN>& token_secret) {
const RetryTokenSecret& token_secret) {
TokenSecret secret;

return
Expand Down Expand Up @@ -123,6 +123,23 @@ void GenerateRandData(uint8_t* buf, size_t len) {
}
} // namespace

bool GenerateResetToken(
uint8_t* token,
uint8_t* secret,
size_t secretlen,
const ngtcp2_cid* cid) {
ngtcp2_crypto_ctx ctx;
ngtcp2_crypto_ctx_initial(&ctx);
return NGTCP2_OK(ngtcp2_crypto_hkdf_expand(
token,
NGTCP2_STATELESS_RESET_TOKENLEN,
&ctx.md,
secret,
secretlen,
cid->data,
cid->datalen));
}

// The Retry Token is an encrypted token that is sent to the client
// by the server as part of the path validation flow. The plaintext
// format within the token is opaque and only meaningful the server.
Expand All @@ -137,7 +154,7 @@ bool GenerateRetryToken(
size_t* tokenlen,
const sockaddr* addr,
const ngtcp2_cid* ocid,
const std::array<uint8_t, TOKEN_SECRETLEN>& token_secret) {
const RetryTokenSecret& token_secret) {
std::array<uint8_t, 4096> plaintext;

ngtcp2_crypto_ctx ctx;
Expand Down Expand Up @@ -195,7 +212,7 @@ bool InvalidRetryToken(
ngtcp2_cid* ocid,
const ngtcp2_pkt_hd* hd,
const sockaddr* addr,
const std::array<uint8_t, TOKEN_SECRETLEN>& token_secret,
const RetryTokenSecret& token_secret,
uint64_t verification_expiration) {

ngtcp2_crypto_ctx ctx;
Expand Down
10 changes: 8 additions & 2 deletions src/node_quic_crypto.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,25 @@ bool UpdateKey(
std::vector<uint8_t>* current_rx_secret,
std::vector<uint8_t>* current_tx_secret);

bool GenerateResetToken(
uint8_t* token,
uint8_t* secret,
size_t secretlen,
const ngtcp2_cid* cid);

bool GenerateRetryToken(
uint8_t* token,
size_t* tokenlen,
const sockaddr* addr,
const ngtcp2_cid* ocid,
const std::array<uint8_t, TOKEN_SECRETLEN>& token_secret);
const RetryTokenSecret& token_secret);

bool InvalidRetryToken(
Environment* env,
ngtcp2_cid* ocid,
const ngtcp2_pkt_hd* hd,
const sockaddr* addr,
const std::array<uint8_t, TOKEN_SECRETLEN>& token_secret,
const RetryTokenSecret& token_secret,
uint64_t verification_expiration);

int VerifyHostnameIdentity(SSL* ssl, const char* hostname);
Expand Down
31 changes: 27 additions & 4 deletions src/node_quic_session.cc
Original file line number Diff line number Diff line change
Expand Up @@ -678,20 +678,27 @@ void JSQuicSessionListener::OnQLog(const uint8_t* data, size_t len) {
&str);
}

// Generates and associates a new connection ID for this QuicSession.
// Generates a new connection ID for this QuicSession.
// ngtcp2 will call this multiple times at the start of a new connection
// in order to build a pool of available CIDs.
void RandomConnectionIDStrategy::GetNewConnectionID(
QuicSession* session,
ngtcp2_cid* cid,
uint8_t* token,
size_t cidlen) {
cid->datalen = cidlen;
// cidlen shouldn't ever be zero here but just in case that
// behavior changes in ngtcp2 in the future...
if (cidlen > 0)
EntropySource(cid->data, cidlen);
EntropySource(token, NGTCP2_STATELESS_RESET_TOKENLEN);
}

void CryptoStatelessResetTokenStrategy::GetNewStatelessToken(
QuicSession* session,
ngtcp2_cid* cid,
uint8_t* token,
size_t tokenlen) {
ResetTokenSecret* secret = session->Socket()->GetSessionResetSecret();
CHECK(GenerateResetToken(token, secret->data(), secret->size(), cid));
}

// Check required capabilities were not excluded from the OpenSSL build:
Expand Down Expand Up @@ -1260,6 +1267,7 @@ QuicSession::QuicSession(
reinterpret_cast<double*>(&recovery_stats_)) {
PushListener(&default_listener_);
SetConnectionIDStrategory(&default_connection_id_strategy_);
SetStatelessResetTokenStrategy(&default_stateless_reset_strategy_);
crypto_context_.reset(new QuicCryptoContext(this, ctx, side, options));
application_.reset(SelectApplication(this));
if (rcid != nullptr)
Expand Down Expand Up @@ -1634,6 +1642,12 @@ void QuicSession::SetConnectionIDStrategory(ConnectionIDStrategy* strategy) {
connection_id_strategy_ = strategy;
}

void QuicSession::SetStatelessResetTokenStrategy(
StatelessResetTokenStrategy* strategy) {
CHECK_NOT_NULL(strategy);
stateless_reset_strategy_ = strategy;
}

// Generates and associates a new connection ID for this QuicSession.
// ngtcp2 will call this multiple times at the start of a new connection
// in order to build a pool of available CIDs.
Expand All @@ -1643,7 +1657,16 @@ int QuicSession::GetNewConnectionID(
size_t cidlen) {
DCHECK(!IsFlagSet(QUICSESSION_FLAG_DESTROYED));
CHECK_NOT_NULL(connection_id_strategy_);
connection_id_strategy_->GetNewConnectionID(this, cid, token, cidlen);
connection_id_strategy_->GetNewConnectionID(
this,
cid,
cidlen);
stateless_reset_strategy_->GetNewStatelessToken(
this,
cid,
token,
NGTCP2_STATELESS_RESET_TOKENLEN);

AssociateCID(cid);
return 0;
}
Expand Down
24 changes: 22 additions & 2 deletions src/node_quic_session.h
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,6 @@ class ConnectionIDStrategy {
virtual void GetNewConnectionID(
QuicSession* session,
ngtcp2_cid* cid,
uint8_t* token,
size_t cidlen) = 0;
};

Expand All @@ -304,10 +303,27 @@ class RandomConnectionIDStrategy : public ConnectionIDStrategy {
void GetNewConnectionID(
QuicSession* session,
ngtcp2_cid* cid,
uint8_t* token,
size_t cidlen) override;
};

class StatelessResetTokenStrategy {
public:
virtual void GetNewStatelessToken(
QuicSession* session,
ngtcp2_cid* cid,
uint8_t* token,
size_t tokenlen) = 0;
};

class CryptoStatelessResetTokenStrategy : public StatelessResetTokenStrategy {
public:
void GetNewStatelessToken(
QuicSession* session,
ngtcp2_cid* cid,
uint8_t* token,
size_t tokenlen) override;
};

// The QuicCryptoContext class encapsulates all of the crypto/TLS
// handshake details on behalf of a QuicSession.
class QuicCryptoContext : public MemoryRetainer {
Expand Down Expand Up @@ -935,6 +951,7 @@ class QuicSession : public AsyncWrap,
void RemoveListener(QuicSessionListener* listener);

void SetConnectionIDStrategory(ConnectionIDStrategy* strategy);
void SetStatelessResetTokenStrategy(StatelessResetTokenStrategy* strategy);

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

StatelessResetTokenStrategy* stateless_reset_strategy_ = nullptr;
CryptoStatelessResetTokenStrategy default_stateless_reset_strategy_;

QuicSessionListener* listener_ = nullptr;
JSQuicSessionListener default_listener_;

Expand Down
Loading