/* The ciphersuites for AEAD QUIC-TLS have 16-bytes authentication tags */
#define QUIC_TLS_TAG_LEN 16 /* bytes */
+/* Returned by quic_aead_confidentiality_limit() for an AEAD whose limit is
+ * greater than the number of possible packets, and may be disregarded.
+ */
+#define QUIC_AEAD_NO_CONFIDENTIALITY_LIMIT UINT64_MAX
+
/* The TLS extensions for QUIC transport parameters */
#define TLS_EXTENSION_QUIC_TRANSPORT_PARAMETERS 0x0039
#define TLS_EXTENSION_QUIC_TRANSPORT_PARAMETERS_DRAFT 0xffa5
unsigned char flags;
};
-/* Key update phase bit */
+/* Key phase bit of the packets we send */
#define QUIC_FL_TLS_KP_BIT_SET (1 << 0)
+/* A key phase update initiated locally is in progress: the peer has not
+ * switched to the new key phase yet.
+ */
+#define QUIC_FL_TLS_KP_LOCAL_IN_PROGRESS (1 << 1)
struct quic_tls_secrets {
QUIC_AEAD_CTX *ctx;
size_t ivlen;
unsigned char *key;
size_t keylen;
- /* Used only on the RX part to store the largest received packet number */
+ /* Frontier between the current and the previous key phase. On RX, the
+ * lowest packet number received with the current keys: a packet with a
+ * lower number was protected with the previous ones. On TX, the lowest
+ * packet number sent with the current keys, compared against the largest
+ * acknowledged one to know whether a new key update may be initiated
+ * (RFC 9001 6.1).
+ */
int64_t pn;
+ /* Used only on the TX part to count the packets encrypted with these keys */
+ uint64_t count;
};
struct quic_tls_ctx {
int quic_tls_key_update(struct quic_conn *qc);
void quic_tls_rotate_keys(struct quic_conn *qc);
+int quic_tls_initiate_key_update(struct quic_conn *qc);
static inline const QUIC_AEAD *tls_aead(const SSL_CIPHER *cipher)
{
return 0;
}
+/* Return the number of packets which may be encrypted with the same keys with
+ * <aead> as AEAD algorithm, as stated by RFC 9001 6.6.
+ */
+static inline uint64_t quic_aead_confidentiality_limit(const QUIC_AEAD *aead)
+{
+ /* See quic_aead_integrity_limit() about QUIC_AEAD_API and the
+ * algorithms tls_aead() may return.
+ *
+ * The limit for ChaCha20-Poly1305 is greater than the number of
+ * possible packets, so it may be disregarded.
+ */
+#ifdef QUIC_AEAD_API
+ if (aead == EVP_aead_chacha20_poly1305())
+ return QUIC_AEAD_NO_CONFIDENTIALITY_LIMIT;
+ if (aead == EVP_aead_aes_128_gcm() ||
+ aead == EVP_aead_aes_256_gcm())
+ return 1ULL << 23;
+#else /* !QUIC_AEAD_API */
+ switch (EVP_CIPHER_nid(aead)) {
+#if !defined(USE_OPENSSL_WOLFSSL)
+ case NID_aes_128_ccm:
+ /* 2^21.5 */
+ return 2965820;
+#endif
+#if (!defined(LIBRESSL_VERSION_NUMBER) || LIBRESSL_VERSION_NUMBER >= 0x4000000fL)
+ case NID_chacha20_poly1305:
+ return QUIC_AEAD_NO_CONFIDENTIALITY_LIMIT;
+#endif
+ case NID_aes_128_gcm:
+ case NID_aes_256_gcm:
+ return 1ULL << 23;
+ }
+#endif
+ return 0;
+}
+
static inline const EVP_MD *tls_md(const SSL_CIPHER *cipher)
{
switch (SSL_CIPHER_get_id(cipher)) {
ctx->rx.key = NULL;
ctx->rx.keylen = 0;
ctx->rx.pn = 0;
+ /* Not used on the RX path. */
+ ctx->rx.count = 0;
ctx->tx.ctx = NULL;
ctx->tx.aead = NULL;
ctx->tx.ivlen = 0;
ctx->tx.key = NULL;
ctx->tx.keylen = 0;
- /* Not used on the TX path. */
ctx->tx.pn = 0;
+ ctx->tx.count = 0;
ctx->flags = 0;
}
static int qc_pkt_decrypt(struct quic_conn *qc, struct quic_enc_level *qel,
struct quic_rx_packet *pkt)
{
- int ret, kp_changed;
+ int ret, kp_changed, kp_prv;
unsigned char iv[QUIC_TLS_IV_LEN];
struct quic_tls_ctx *tls_ctx =
qc_select_tls_ctx(qc, qel, pkt->type, pkt->version);
ret = 0;
kp_changed = 0;
+ kp_prv = 0;
if (pkt->type == QUIC_PACKET_TYPE_SHORT) {
/* The two tested bits are not at the same position,
* this is why they are first both inversed.
*/
if (!(*pkt->data & QUIC_PACKET_KEY_PHASE_BIT) ^ !(tls_ctx->flags & QUIC_FL_TLS_KP_BIT_SET)) {
- if (pkt->pn < tls_ctx->rx.pn) {
+ if ((tls_ctx->flags & QUIC_FL_TLS_KP_LOCAL_IN_PROGRESS) ||
+ pkt->pn < tls_ctx->rx.pn) {
/* The previous key phase secrets are only
* available after a first key update.
*/
rx_ctx = qc->ku.prv_rx.ctx;
rx_iv = qc->ku.prv_rx.iv;
rx_key = qc->ku.prv_rx.key;
+ kp_prv = 1;
}
else if (pkt->pn > qel->pktns->rx.largest_pn) {
/* Next key phase */
if (!quic_tls_key_update(qc))
TRACE_ERROR("quic_tls_key_update() failed", QUIC_EV_CONN_RXPKT, qc);
}
+ else if (!kp_prv && (tls_ctx->flags & QUIC_FL_TLS_KP_LOCAL_IN_PROGRESS)) {
+ /* The peer has switched to the new key phase. */
+ tls_ctx->flags &= ~QUIC_FL_TLS_KP_LOCAL_IN_PROGRESS;
+ tls_ctx->rx.pn = pkt->pn;
+ }
/* Update the packet length (required to parse the frames). */
pkt->len -= QUIC_TLS_TAG_LEN;
qc->ku.nxt_tx.iv = curr_iv;
qc->ku.nxt_tx.key = curr_key;
+ /* The new keys have not encrypted any packet yet. */
+ tls_ctx->tx.count = 0;
+ /* Store the lowest packet number to be sent with the new keys. */
+ tls_ctx->tx.pn = qc->ael->pktns->tx.next_pn + 1;
+
TRACE_LEAVE(QUIC_EV_CONN_RXPKT, qc);
}
+/* Initiate a key update for <qc> connection. This is the local counterpart of
+ * the key update triggered by the peer.
+ * Return 1 if succeeded, 0 if not.
+ */
+int quic_tls_initiate_key_update(struct quic_conn *qc)
+{
+ struct quic_tls_ctx *tls_ctx = &qc->ael->tls_ctx;
+ int ret = 0;
+
+ TRACE_ENTER(QUIC_EV_CONN_KP, qc);
+
+ /* The next keys may be missing if a previous update failed. */
+ if ((!qc->ku.nxt_rx.ctx || !qc->ku.nxt_tx.ctx) &&
+ !quic_tls_key_update(qc)) {
+ TRACE_ERROR("quic_tls_key_update() failed", QUIC_EV_CONN_KP, qc);
+ goto leave;
+ }
+
+ quic_tls_rotate_keys(qc);
+ /* Toggle the Key Phase bit */
+ tls_ctx->flags ^= QUIC_FL_TLS_KP_BIT_SET;
+ /* The peer keeps sending with the previous key phase for at least one
+ * round trip. Its packets must be decrypted with the previous keys.
+ */
+ tls_ctx->flags |= QUIC_FL_TLS_KP_LOCAL_IN_PROGRESS;
+
+ /* Prepare the next key update. On failure, the keys are derived again
+ * at the next key phase change.
+ */
+ if (!quic_tls_key_update(qc))
+ TRACE_ERROR("quic_tls_key_update() failed", QUIC_EV_CONN_KP, qc);
+
+ ret = 1;
+ leave:
+ TRACE_LEAVE(QUIC_EV_CONN_KP, qc);
+ return ret;
+}
+
/* Release the memory allocated for the QUIC TLS context with <ctx> as address. */
void quic_tls_ctx_free(struct quic_tls_ctx **ctx)
{
*fail = 1;
enc_debug_info_init(&edi, payload, payload_len, aad, aad_len, pn);
}
+ else {
+ tls_ctx->tx.count++;
+ }
TRACE_LEAVE(QUIC_EV_CONN_ENCPKT, qc);
}
}
}
+/* Return 1 if a packet may be encrypted with the keys currently in use to
+ * protect <qel>, 0 if their confidentiality limit is reached. <cc> is set when
+ * this packet carries the CONNECTION_CLOSE frame.
+ *
+ * The number of packets encrypted with a given key must never exceed the
+ * confidentiality limit of its AEAD algorithm, the CONNECTION_CLOSE frame
+ * included. One packet of the budget is therefore reserved for that frame, so
+ * that the peer can always be notified before the keys are exhausted.
+ */
+static int quic_tls_tx_allowed(const struct quic_conn *qc,
+ const struct quic_enc_level *qel, int cc)
+{
+ uint64_t count, limit;
+
+ if (qel != qc->ael)
+ return 1;
+
+ if (!quic_tls_has_tx_sec(qel) || !qel->tls_ctx.tx.aead)
+ return 1;
+
+ count = qel->tls_ctx.tx.count;
+ limit = quic_aead_confidentiality_limit(qel->tls_ctx.tx.aead);
+ if (limit == QUIC_AEAD_NO_CONFIDENTIALITY_LIMIT)
+ return 1;
+
+ return cc ? count < limit : count + 1 < limit;
+}
+
/* Return the TX buffer dedicated to the "connection close" datagram to be built
* if an immediate close is required after having allocated it or directly
* allocate a TX buffer if an immediate close is not required.
* if there is nothing to send, i.e. if no CONNECTION_CLOSE or ACK are required
* and if there is no more packets to send upon PTO expiration
* and if there is no more ack-eliciting frames to send or in flight
- * congestion control limit is reached for prepared data
+ * congestion control limit is reached for prepared data.
+ *
+ * Also do not build any packet if the confidentiality limit of the keys
+ * in use is reached with no possible key update: no more packet may
+ * consume them, except the one carrying the CONNECTION_CLOSE frame.
*/
if (!quic_tls_has_tx_sec(qel) ||
(!cc && !probe && !*must_ack &&
- (LIST_ISEMPTY(frms) || qc->path->prep_in_flight >= qc->path->cwnd))) {
+ (LIST_ISEMPTY(frms) || qc->path->prep_in_flight >= qc->path->cwnd)) ||
+ !quic_tls_tx_allowed(qc, qel, cc)) {
return 0;
}
return -1;
}
+/* Check the AEAD confidentiality limit of RFC 9001 6.6 for <qc> connection.
+ * Initiate a key update before the limit is reached, and close the connection
+ * if it is reached without any possible key update.
+ *
+ * This must be called before preparing packets, to initiate a key update or
+ * arm an immediate close before selecting the TX buffer. As a single call may
+ * encrypt several packets, qc_may_build_pkt() also checks the remaining budget
+ * before building each packet.
+ */
+static void qc_check_aead_confidentiality_limit(struct quic_conn *qc)
+{
+ struct quic_tls_ctx *tls_ctx;
+ uint64_t count, limit;
+ int can_update;
+
+ TRACE_ENTER(QUIC_EV_CONN_KP, qc);
+
+ if (qc->flags & (QUIC_FL_CONN_IMMEDIATE_CLOSE|QUIC_FL_CONN_CLOSING|
+ QUIC_FL_CONN_DRAINING))
+ goto leave;
+
+ /* The 1-RTT encryption level may be allocated without usable secrets. */
+ if (!quic_tls_has_tx_sec(qc->ael) || !qc->ael->tls_ctx.tx.aead)
+ goto leave;
+
+ tls_ctx = &qc->ael->tls_ctx;
+ count = tls_ctx->tx.count;
+ limit = quic_aead_confidentiality_limit(tls_ctx->tx.aead);
+ if (!limit) {
+ TRACE_ERROR("unknown AEAD algorithm", QUIC_EV_CONN_KP, qc);
+ quic_set_connection_close(qc, quic_err_transport(QC_ERR_INTERNAL_ERROR));
+ goto leave;
+ }
+
+ /* RFC 9001 6.6: the limit of this AEAD is greater than the number of
+ * possible packets, so it may be disregarded.
+ */
+ if (limit == QUIC_AEAD_NO_CONFIDENTIALITY_LIMIT)
+ goto leave;
+
+ /* Start the key update before the limit is reached, to leave enough
+ * room for the acknowledgement required by the next one.
+ */
+ if (count < limit - (limit >> 3))
+ goto leave;
+
+ /* RFC 9001 6.1. Initiating a Key Update
+ *
+ * An endpoint MUST NOT initiate a key update prior to having confirmed
+ * the handshake (Section 4.1.2). An endpoint MUST NOT initiate a
+ * subsequent key update unless it has received an acknowledgment for a
+ * packet that was sent protected with keys from the current key phase.
+ */
+ can_update = count + 1 < limit &&
+ qc->state >= QUIC_HS_ST_CONFIRMED &&
+ !(tls_ctx->flags & QUIC_FL_TLS_KP_LOCAL_IN_PROGRESS) &&
+ tls_ctx->tx.pn <= qc->ael->pktns->rx.largest_acked_pn;
+ if (can_update && quic_tls_initiate_key_update(qc))
+ goto leave;
+
+ /* No key update is possible, and the keys may not encrypt any more
+ * regular packet: no more than the confidentiality limit of the AEAD
+ * algorithm may be consumed, the CONNECTION_CLOSE frame included. Close
+ * the connection now, while the packet reserved for that frame is still
+ * available.
+ */
+ if (!can_update && count + 1 >= limit) {
+ TRACE_ERROR("AEAD confidentiality limit reached", QUIC_EV_CONN_KP, qc);
+ quic_set_connection_close(qc, quic_err_transport(QC_ERR_AEAD_LIMIT_REACHED));
+ }
+
+ leave:
+ TRACE_LEAVE(QUIC_EV_CONN_KP, qc);
+}
+
/* Encode frames and send them as packets for <qc> connection. Input frames are
* specified via quic_enc_level <send_list> through their send_frms member. Set
* <old_data> when reemitted duplicated data.
TRACE_ENTER(QUIC_EV_CONN_TXPKT, qc);
+ /* The AEAD confidentiality limit is checked here, before anything is
+ * prepared: an immediate close armed by that check is sent by the caller
+ * or the connection layer, from its own dedicated buffer, so that no
+ * further packet may be built with exhausted keys.
+ */
+ qc_check_aead_confidentiality_limit(qc);
+
buf = qc_get_txb(qc);
if (!buf) {
TRACE_ERROR("buffer allocation failed", QUIC_EV_CONN_TXPKT, qc);