-/* $OpenBSD: packet.c,v 1.327 2025/12/05 06:16:27 dtucker Exp $ */
+/* $OpenBSD: packet.c,v 1.328 2025/12/30 00:22:58 djm Exp $ */
/*
* Author: Tatu Ylonen <ylo@cs.hut.fi>
* Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland
struct packet_state p_read, p_send;
/* Volume-based rekeying */
+ u_int64_t hard_max_blocks_in, hard_max_blocks_out;
u_int64_t max_blocks_in, max_blocks_out, rekey_limit;
/* Time-based rekeying */
struct sshcomp *comp;
struct sshcipher_ctx **ccp;
struct packet_state *ps;
- u_int64_t *max_blocks;
+ u_int64_t *max_blocks, *hard_max_blocks;
const char *wmsg;
int r, crypt_type;
const char *dir = mode == MODE_OUT ? "out" : "in";
ccp = &state->send_context;
crypt_type = CIPHER_ENCRYPT;
ps = &state->p_send;
+ hard_max_blocks = &state->hard_max_blocks_out;
max_blocks = &state->max_blocks_out;
} else {
ccp = &state->receive_context;
crypt_type = CIPHER_DECRYPT;
ps = &state->p_read;
+ hard_max_blocks = &state->hard_max_blocks_in;
max_blocks = &state->max_blocks_in;
}
if (state->newkeys[mode] != NULL) {
* See RFC4344 section 3.2.
*/
if (enc->block_size >= 16)
- *max_blocks = (u_int64_t)1 << (enc->block_size*2);
+ *hard_max_blocks = (u_int64_t)1 << (enc->block_size*2);
else
- *max_blocks = ((u_int64_t)1 << 30) / enc->block_size;
- if (state->rekey_limit)
+ *hard_max_blocks = ((u_int64_t)1 << 30) / enc->block_size;
+ *max_blocks = *hard_max_blocks;
+ if (state->rekey_limit) {
*max_blocks = MINIMUM(*max_blocks,
state->rekey_limit / enc->block_size);
+ }
debug("rekey %s after %llu blocks", dir,
(unsigned long long)*max_blocks);
return 0;
}
#define MAX_PACKETS (1U<<31)
+/*
+ * Checks whether the packet- or block- based rekeying limits have been
+ * exceeded. If the 'hard' flag is set, the checks are performed against the
+ * absolute maximum we're willing to accept for the given cipher. Otherwise
+ * the checks are performed against the RekeyLimit volume, which may be lower.
+ */
+static inline int
+ssh_packet_check_rekey_blocklimit(struct ssh *ssh, u_int packet_len, int hard)
+{
+ struct session_state *state = ssh->state;
+ u_int32_t out_blocks;
+ const u_int64_t max_blocks_in = hard ?
+ state->hard_max_blocks_in : state->max_blocks_in;
+ const u_int64_t max_blocks_out = hard ?
+ state->hard_max_blocks_out : state->max_blocks_out;
+
+ /*
+ * Always rekey when MAX_PACKETS sent in either direction
+ * As per RFC4344 section 3.1 we do this after 2^31 packets.
+ */
+ if (state->p_send.packets > MAX_PACKETS ||
+ state->p_read.packets > MAX_PACKETS)
+ return 1;
+
+ /* Rekey after (cipher-specific) maximum blocks */
+ out_blocks = ROUNDUP(packet_len,
+ state->newkeys[MODE_OUT]->enc.block_size);
+ return (max_blocks_out &&
+ (state->p_send.blocks + out_blocks > max_blocks_out)) ||
+ (max_blocks_in &&
+ (state->p_read.blocks > max_blocks_in));
+}
+
static int
ssh_packet_need_rekeying(struct ssh *ssh, u_int outbound_packet_len)
{
struct session_state *state = ssh->state;
- u_int32_t out_blocks;
- /* XXX client can't cope with rekeying pre-auth */
+ /* Don't attempt rekeying during pre-auth */
if (!state->after_authentication)
return 0;
(int64_t)state->rekey_time + state->rekey_interval <= monotime())
return 1;
- /*
- * Always rekey when MAX_PACKETS sent in either direction
- * As per RFC4344 section 3.1 we do this after 2^31 packets.
- */
- if (state->p_send.packets > MAX_PACKETS ||
- state->p_read.packets > MAX_PACKETS)
- return 1;
+ return ssh_packet_check_rekey_blocklimit(ssh, outbound_packet_len, 0);
+}
- /* Rekey after (cipher-specific) maximum blocks */
- out_blocks = ROUNDUP(outbound_packet_len,
- state->newkeys[MODE_OUT]->enc.block_size);
- return (state->max_blocks_out &&
- (state->p_send.blocks + out_blocks > state->max_blocks_out)) ||
- (state->max_blocks_in &&
- (state->p_read.blocks > state->max_blocks_in));
+/* Checks that the hard rekey limits have not been exceeded during preauth */
+static int
+ssh_packet_check_rekey_preauth(struct ssh *ssh, u_int outgoing_packet_len)
+{
+ if (ssh->state->after_authentication)
+ return 0;
+
+ if (ssh_packet_check_rekey_blocklimit(ssh, 0, 1)) {
+ error("RekeyLimit exceeded before authentication completed");
+ return SSH_ERR_NEED_REKEY;
+ }
+ return 0;
}
int
ssh_packet_check_rekey(struct ssh *ssh)
{
+ int r;
+
+ if ((r = ssh_packet_check_rekey_preauth(ssh, 0)) != 0)
+ return r;
if (!ssh_packet_need_rekeying(ssh, 0))
return 0;
debug3_f("rekex triggered");
need_rekey = !ssh_packet_type_is_kex(type) &&
ssh_packet_need_rekeying(ssh, sshbuf_len(state->outgoing_packet));
+ /* Enforce hard rekey limit during pre-auth */
+ if (!state->rekeying && !ssh_packet_type_is_kex(type) &&
+ (r = ssh_packet_check_rekey_preauth(ssh, 0)) != 0)
+ return r;
+
/*
* During rekeying we can only send key exchange messages.
* Queue everything else.