From 435bd3cd29eeed361a8df44b3b17ec562e364090 Mon Sep 17 00:00:00 2001 From: Volodymyr Snitko Date: Wed, 24 Jun 2026 10:21:03 +0200 Subject: [PATCH 1/6] RFC 4771 ROC in-band support --- include/srtp.h | 54 +++ include/srtp_priv.h | 8 + srtp/srtp.c | 1021 ++++++++++++++++++++++++++++++++++++++----- srtp/srtp_policy.c | 12 + test/rcc_test.c | 550 +++++++++++++++++++++++ 5 files changed, 1529 insertions(+), 116 deletions(-) create mode 100644 test/rcc_test.c diff --git a/include/srtp.h b/include/srtp.h index 8e0ab1fca..3ae4a90dc 100644 --- a/include/srtp.h +++ b/include/srtp.h @@ -385,6 +385,37 @@ srtp_err_status_t srtp_policy_get_profile(srtp_policy_t policy, * - srtp_err_status_ok if flags were applied. * - srtp_err_status_bad_param if policy is NULL or profile is unset. */ + + /** + * @brief srtp_rcc_mode_t selects the RFC 4771 Roll-over Counter Carrying + * (RCC) integrity transform mode for an SRTP stream. + * + * RFC 4771 allows the sender's ROC to be carried inside the SRTP + * authentication tag of selected packets (those whose RTP sequence number + * is congruent to 0 modulo the transmission rate @c roc_tx_rate, "R"). + * For a packet that carries the ROC the tag is built as + * @c TAG @c = @c ROC(4 @c octets) @c || @c MAC_tr, where @c MAC_tr is the + * @c (auth_tag_len @c - @c 4) most significant octets of the HMAC. + * + * Modes 1 and 2 use the HMAC-SHA1 integrity transform and are defined only + * for the AES-CM ciphers. Mode 3 is the RFC 4771 NULL-MAC variant: it + * carries only the 4-octet ROC with no MAC of its own. Mode 3 is supported + * here on top of AES-GCM (RFC 7714); the AEAD tag authenticates the packet + * and the ROC is appended immediately after the GCM tag. + */ +typedef enum { + srtp_rcc_mode_none = 0, /**< RCC disabled (default RFC 3711 transform). */ + srtp_rcc_mode_1 = 1, /**< RFC 4771 mode 1: only ROC-carrying packets */ + /**< are integrity protected; other packets */ + /**< carry no authentication tag. */ + srtp_rcc_mode_2 = 2, /**< RFC 4771 mode 2: ROC-carrying packets use */ + /**< the RCC tag, all other packets use the */ + /**< default integrity transform. */ + srtp_rcc_mode_3 = 3 /**< RFC 4771 mode 3: NULL-MAC, ROC only. */ + /**< Supported with AES-GCM, where the ROC is */ + /**< appended after the GCM tag. */ +} srtp_rcc_mode_t; + srtp_err_status_t srtp_policy_set_sec_serv(srtp_policy_t policy, srtp_sec_serv_t rtp_sec_serv, srtp_sec_serv_t rtcp_sec_serv); @@ -418,6 +449,29 @@ srtp_err_status_t srtp_policy_use_mki(srtp_policy_t policy, size_t mki_len); srtp_err_status_t srtp_policy_get_mki_length(srtp_policy_t policy, size_t *mki_len); +/** + * @brief Enable RFC 4771 Roll-over Counter Carrying (RCC) for this policy. + * + * @param policy policy handle. + * @param rcc_mode RCC integrity-transform mode (see srtp_rcc_mode_t). Pass + * srtp_rcc_mode_none to disable RCC and use the default RFC 3711 + * transform. + * @param roc_tx_rate ROC transmission rate R: the sender embeds its ROC in + * every packet whose RTP sequence number is congruent to 0 modulo R + * (R == 1 carries the ROC in every packet). Ignored when rcc_mode is + * srtp_rcc_mode_none; must be >= 1 otherwise. + * + * Modes 1 and 2 are defined only for the AES-CM ciphers; mode 3 (NULL-MAC) is + * supported only with AES-GCM. The mode must be consistent with the policy's + * profile or srtp_create()/srtp_policy_validate() rejects it. Both peers must + * derive the same mode and rate so they agree on the packet layout. + * + * @return + * - srtp_err_status_ok if the RCC settings were applied. + * - srtp_err_status_bad_param if policy is NULL or the rate is invalid. + */ +srtp_err_status_t srtp_policy_set_rcc_mode_tx_rate(srtp_policy_t policy, srtp_rcc_mode_t rcc_mode, uint16_t roc_tx_rate); + /** * @brief Add a master key and salt to a policy handle. * diff --git a/include/srtp_priv.h b/include/srtp_priv.h index 575c0f41e..fdfd4717c 100644 --- a/include/srtp_priv.h +++ b/include/srtp_priv.h @@ -113,6 +113,12 @@ typedef struct srtp_policy_ctx_t_ { /**< ids. */ bool use_cryptex; /**< Encrypt header block and CSRCs with */ /**< cryptex, RFC 9335. */ + srtp_rcc_mode_t rcc_mode; /**< RFC 4771 RCC integrity transform */ + /**< mode for SRTP (default none). */ + uint16_t roc_tx_rate; /**< RFC 4771 ROC transmission rate R: */ + /**< the ROC is carried in packets whose */ + /**< sequence number is 0 modulo R. A */ + /**< value of 0 is treated as 1. */ } srtp_policy_ctx_t_; static inline bool srtp_policy_is_null_cipher_null_auth( @@ -188,6 +194,8 @@ typedef struct srtp_stream_ctx_t_ { size_t enc_xtn_hdr_count; uint32_t pending_roc; bool use_cryptex; + srtp_rcc_mode_t rcc_mode; /* RFC 4771 RCC integrity transform mode */ + uint16_t roc_tx_rate; /* RFC 4771 ROC transmission rate R (>= 1) */ } strp_stream_ctx_t_; /* diff --git a/srtp/srtp.c b/srtp/srtp.c index 3d14d3364..43b7dd490 100644 --- a/srtp/srtp.c +++ b/srtp/srtp.c @@ -819,6 +819,10 @@ static srtp_err_status_t srtp_stream_clone( str->enc_xtn_hdr = stream_template->enc_xtn_hdr; str->enc_xtn_hdr_count = stream_template->enc_xtn_hdr_count; str->use_cryptex = stream_template->use_cryptex; + /* copy RFC 4771 RCC configuration */ + str->rcc_mode = stream_template->rcc_mode; + str->roc_tx_rate = stream_template->roc_tx_rate; + return srtp_err_status_ok; } @@ -1642,6 +1646,92 @@ static srtp_err_status_t srtp_stream_init(srtp_stream_ctx_t *srtp, return srtp_err_status_bad_param; } + /* + * RFC 4771 RCC: validate the mode and transmission rate. When RCC is + * enabled the four-octet ROC is carried inside the authentication tag, + * so the tag must be able to hold the ROC plus at least one MAC octet. + */ + if (p->rcc_mode != srtp_rcc_mode_none) { + bool is_gcm = (p->rtp.cipher_type == SRTP_AES_GCM_128 || + p->rtp.cipher_type == SRTP_AES_GCM_256); + switch (p->rcc_mode) { + case srtp_rcc_mode_1: + case srtp_rcc_mode_2: + /* + * Modes 1 and 2 carry the ROC inside a truncated HMAC-SHA1 tag + * (TAG = ROC || MAC_tr), so the tag must hold the 4-octet ROC plus + * at least one MAC octet. They are defined only for the AES-CM + * ciphers with HMAC-SHA1, not for AEAD/GCM. + */ + if (p->rtp.auth_tag_len < 5) { + return srtp_err_status_bad_param; + } + if (is_gcm) { + return srtp_err_status_bad_param; + } + break; + case srtp_rcc_mode_3: + /* + * Mode 3 (RFC 4771 NULL-MAC) carries only the 4-octet ROC with no + * MAC of its own. It is supported here on top of AES-GCM: the + * AEAD tag authenticates the packet (RFC 7714) and the ROC is + * appended immediately after the GCM tag, in the SRTP + * authentication tag field retained by RFC 7714 section 7.1. + * Because the carried ROC also feeds the GCM IV, any tampering + * with it is detected by GCM tag verification. + */ + if (!is_gcm) { + return srtp_err_status_bad_param; + } + break; + default: + return srtp_err_status_bad_param; + } + } + + /* + * RFC 4771 RCC: validate the mode and transmission rate. When RCC is + * enabled the four-octet ROC is carried inside the authentication tag, + * so the tag must be able to hold the ROC plus at least one MAC octet. + */ + if (p->rcc_mode != srtp_rcc_mode_none) { + bool is_gcm = (p->rtp.cipher_type == SRTP_AES_GCM_128 || + p->rtp.cipher_type == SRTP_AES_GCM_256); + switch (p->rcc_mode) { + case srtp_rcc_mode_1: + case srtp_rcc_mode_2: + /* + * Modes 1 and 2 carry the ROC inside a truncated HMAC-SHA1 tag + * (TAG = ROC || MAC_tr), so the tag must hold the 4-octet ROC plus + * at least one MAC octet. They are defined only for the AES-CM + * ciphers with HMAC-SHA1, not for AEAD/GCM. + */ + if (p->rtp.auth_tag_len < 5) { + return srtp_err_status_bad_param; + } + if (is_gcm) { + return srtp_err_status_bad_param; + } + break; + case srtp_rcc_mode_3: + /* + * Mode 3 (RFC 4771 NULL-MAC) carries only the 4-octet ROC with no + * MAC of its own. It is supported here on top of AES-GCM: the + * AEAD tag authenticates the packet (RFC 7714) and the ROC is + * appended immediately after the GCM tag, in the SRTP + * authentication tag field retained by RFC 7714 section 7.1. + * Because the carried ROC also feeds the GCM IV, any tampering + * with it is detected by GCM tag verification. + */ + if (!is_gcm) { + return srtp_err_status_bad_param; + } + break; + default: + return srtp_err_status_bad_param; + } + } + if (p->window_size != 0) { err = srtp_rdbx_init(&srtp->rtp_rdbx, p->window_size); } else { @@ -1674,6 +1764,10 @@ static srtp_err_status_t srtp_stream_init(srtp_stream_ctx_t *srtp, /* initialize allow_repeat_tx */ srtp->allow_repeat_tx = p->allow_repeat_tx; + /* RFC 4771 RCC configuration (rate 0 means the default rate of 1) */ + srtp->rcc_mode = p->rcc_mode; + srtp->roc_tx_rate = (p->roc_tx_rate != 0) ? p->roc_tx_rate : 1; + /* DAM - no RTCP key limit at present */ /* initialize keys */ @@ -1951,116 +2045,676 @@ static srtp_err_status_t srtp_get_session_keys_for_packet( return srtp_err_status_bad_mki; } - mki_start_location -= tag_len; + mki_start_location -= tag_len; + + if (stream->mki_size > mki_start_location) { + return srtp_err_status_bad_mki; + } + + mki_start_location -= stream->mki_size; + + for (size_t i = 0; i < stream->num_master_keys; i++) { + if (memcmp(hdr + mki_start_location, stream->session_keys[i].mki_id, + stream->mki_size) == 0) { + *session_keys = &stream->session_keys[i]; + return srtp_err_status_ok; + } + } + + return srtp_err_status_bad_mki; +} + +static srtp_err_status_t srtp_get_session_keys_for_rtp_packet( + srtp_stream_ctx_t *stream, + const uint8_t *hdr, + size_t pkt_octet_len, + srtp_session_keys_t **session_keys) +{ + size_t tag_len = 0; + + if (stream->num_master_keys == 0 || stream->session_keys == NULL) { + return srtp_err_status_no_ctx; + } + + // Determine the authentication tag size + if (stream->session_keys[0].rtp_cipher->algorithm == SRTP_AES_GCM_128 || + stream->session_keys[0].rtp_cipher->algorithm == SRTP_AES_GCM_256) { + tag_len = 0; + } else { + tag_len = srtp_auth_get_tag_length(stream->session_keys[0].rtp_auth); + } + + return srtp_get_session_keys_for_packet(stream, hdr, pkt_octet_len, tag_len, + session_keys); +} + +static srtp_err_status_t srtp_get_session_keys_for_rtcp_packet( + srtp_stream_ctx_t *stream, + const uint8_t *hdr, + size_t pkt_octet_len, + srtp_session_keys_t **session_keys) +{ + size_t tag_len = 0; + + if (stream->num_master_keys == 0 || stream->session_keys == NULL) { + return srtp_err_status_no_ctx; + } + + // Determine the authentication tag size + if (stream->session_keys[0].rtcp_cipher->algorithm == SRTP_AES_GCM_128 || + stream->session_keys[0].rtcp_cipher->algorithm == SRTP_AES_GCM_256) { + tag_len = 0; + } else { + tag_len = srtp_auth_get_tag_length(stream->session_keys[0].rtcp_auth); + } + + return srtp_get_session_keys_for_packet(stream, hdr, pkt_octet_len, tag_len, + session_keys); +} + +static srtp_err_status_t srtp_estimate_index(srtp_rdbx_t *rdbx, + uint32_t roc, + srtp_xtd_seq_num_t *est, + srtp_sequence_number_t seq, + ssize_t *delta) +{ + *est = (srtp_xtd_seq_num_t)(((uint64_t)roc) << 16) | seq; + *delta = *est - rdbx->index; + + if (*est > rdbx->index) { + if (*est - rdbx->index > seq_num_median) { + *delta = 0; + return srtp_err_status_pkt_idx_adv; + } + } else if (*est < rdbx->index) { + if (rdbx->index - *est > seq_num_median) { + *delta = 0; + return srtp_err_status_pkt_idx_old; + } + } + + return srtp_err_status_ok; +} + +static srtp_err_status_t srtp_get_est_pkt_index(const srtp_hdr_t *hdr, + srtp_stream_ctx_t *stream, + srtp_xtd_seq_num_t *est, + ssize_t *delta) +{ + srtp_err_status_t result = srtp_err_status_ok; + + if (stream->pending_roc) { + result = srtp_estimate_index(&stream->rtp_rdbx, stream->pending_roc, + est, ntohs(hdr->seq), delta); + } else { + /* estimate packet index from seq. num. in header */ + *delta = + srtp_rdbx_estimate_index(&stream->rtp_rdbx, est, ntohs(hdr->seq)); + } + + debug_print(mod_srtp, "estimated u_packet index: %016" PRIx64, *est); + + return result; +} + +/* + * srtp_protect_rcc() implements the RFC 4771 Roll-over Counter Carrying (RCC) + * integrity transform (modes 1 and 2) for sending. It is dispatched to from + * srtp_protect() when stream->rcc_mode is not srtp_rcc_mode_none. + * + * For a packet whose RTP sequence number is congruent to 0 modulo the + * transmission rate R (a "ROC-carrying" packet) the authentication tag is + * built as TAG = ROC(4 octets, network order) || MAC_tr, where MAC_tr is the + * (tag_len - 4) most significant octets of HMAC-SHA1(auth_key, + * authenticated_portion || ROC). For other packets: + * - mode 1: no MAC is computed and no tag is appended; + * - mode 2: the default integrity transform is applied (full tag_len MAC). + * + * RCC is only defined here for the AES-CM ciphers with HMAC-SHA1; GCM streams + * are rejected at stream initialization time. + */ +static srtp_err_status_t srtp_protect_rcc(srtp_t ctx, + srtp_stream_ctx_t *stream, + const uint8_t *rtp, + size_t rtp_len, + uint8_t *srtp, + size_t *srtp_len, + srtp_session_keys_t *session_keys) +{ + const srtp_hdr_t *hdr = (const srtp_hdr_t *)rtp; + size_t enc_start; /* offset to start of encrypted portion */ + uint8_t *auth_start; /* pointer to start of auth. portion */ + size_t enc_octet_len = 0; /* number of octets in encrypted portion */ + srtp_xtd_seq_num_t est; /* estimated xtd_seq_num_t of *hdr */ + ssize_t delta; /* delta of local pkt idx and that in hdr */ + uint8_t *auth_tag = NULL; /* location of auth_tag within packet */ + srtp_err_status_t status; + size_t tag_len; + size_t prefix_len; + bool rcc_carry; /* whether this packet carries the ROC */ + size_t rcc_tag_len; /* number of tag octets actually appended */ + + debug_print0(mod_srtp, "function srtp_protect_rcc"); + + /* + * This handler implements only the AES-CM/HMAC RCC modes (1 and 2). + * Mode 3 (AEAD) is handled by srtp_protect_aead, and srtp_rcc_mode_none + * streams never dispatch here. Reject anything else instead of silently + * applying the wrong transform. + */ + if (stream->rcc_mode != srtp_rcc_mode_1 && + stream->rcc_mode != srtp_rcc_mode_2) { + return srtp_err_status_bad_param; + } + + /* RFC 4771: a packet carries the ROC when seq is 0 modulo R */ + rcc_carry = (ntohs(hdr->seq) % stream->roc_tx_rate) == 0; + + /* + * update the key usage limit, and check it to make sure that we + * didn't just hit either the soft limit or the hard limit + */ + switch (srtp_key_limit_update(session_keys->limit)) { + case srtp_key_event_normal: + break; + case srtp_key_event_soft_limit: + srtp_handle_event(ctx, stream, event_key_soft_limit); + break; + case srtp_key_event_hard_limit: + srtp_handle_event(ctx, stream, event_key_hard_limit); + return srtp_err_status_key_expired; + default: + break; + } + + /* get tag length from stream */ + tag_len = srtp_auth_get_tag_length(session_keys->rtp_auth); + + /* + * in mode 1, packets that do not carry the ROC are not integrity + * protected and carry no authentication tag at all + */ + if (stream->rcc_mode == srtp_rcc_mode_1 && !rcc_carry) { + rcc_tag_len = 0; + } else { + rcc_tag_len = tag_len; + } + + /* + * find starting point for encryption and length of data to be + * encrypted - the encrypted portion starts after the rtp header + * extension, if present; otherwise, it starts after the last csrc, + * if any are present + */ + enc_start = srtp_get_rtp_hdr_len(hdr); + if (hdr->x == 1) { + enc_start += srtp_get_rtp_hdr_xtnd_len(hdr, rtp); + } + + bool cryptex_inuse, cryptex_inplace; + status = srtp_cryptex_protect_init(stream, hdr, rtp, srtp, &cryptex_inuse, + &cryptex_inplace, &enc_start); + if (status) { + return status; + } + + if (enc_start > rtp_len) { + return srtp_err_status_parse_err; + } + enc_octet_len = rtp_len - enc_start; + + /* check output length */ + if (*srtp_len < rtp_len + stream->mki_size + rcc_tag_len) { + return srtp_err_status_buffer_small; + } + + /* if not-inplace then need to copy full rtp header */ + if (rtp != srtp) { + memcpy(srtp, rtp, enc_start); + } + + if (stream->use_mki) { + srtp_inject_mki(srtp + rtp_len, session_keys, stream->mki_size); + } + + /* + * if we're providing authentication, set the auth_start and auth_tag + * pointers to the proper locations; otherwise, set auth_start to NULL + */ + if ((stream->rtp_services & sec_serv_auth) && rcc_tag_len > 0) { + auth_start = srtp; + auth_tag = srtp + rtp_len + stream->mki_size; + } else { + auth_start = NULL; + auth_tag = NULL; + } + + /* + * estimate the packet index using the start of the replay window + * and the sequence number from the header + */ + status = srtp_get_est_pkt_index(hdr, stream, &est, &delta); + + if (status && (status != srtp_err_status_pkt_idx_adv)) { + return status; + } + + if (status == srtp_err_status_pkt_idx_adv) { + srtp_rdbx_set_roc_seq(&stream->rtp_rdbx, (uint32_t)(est >> 16), + (uint16_t)(est & 0xFFFF)); + stream->pending_roc = 0; + srtp_rdbx_add_index(&stream->rtp_rdbx, 0); + } else { + status = srtp_rdbx_check(&stream->rtp_rdbx, delta); + if (status) { + if (status != srtp_err_status_replay_fail || + !stream->allow_repeat_tx) + return status; /* we've been asked to reuse an index */ + } + srtp_rdbx_add_index(&stream->rtp_rdbx, delta); + } + + debug_print(mod_srtp, "estimated packet index: %016" PRIx64, est); + + /* set the AES counter-mode nonce and sequence */ + { + v128_t iv; + + iv.v32[0] = 0; + iv.v32[1] = hdr->ssrc; + iv.v64[1] = be64_to_cpu(est << 16); + status = srtp_cipher_set_iv(session_keys->rtp_cipher, (uint8_t *)&iv, + srtp_direction_encrypt); + if (!status && session_keys->rtp_xtn_hdr_cipher) { + status = srtp_cipher_set_iv(session_keys->rtp_xtn_hdr_cipher, + (uint8_t *)&iv, srtp_direction_encrypt); + } + if (status) { + return srtp_err_status_cipher_fail; + } + } + + /* shift est, put into network byte order */ + est = be64_to_cpu(est << 16); + + /* + * if we're authenticating using a universal hash, put the keystream + * prefix into the authentication tag + */ + if (auth_start) { + prefix_len = srtp_auth_get_prefix_length(session_keys->rtp_auth); + if (prefix_len) { + status = srtp_cipher_output(session_keys->rtp_cipher, auth_tag, + &prefix_len); + if (status) { + return srtp_err_status_cipher_fail; + } + } + } + + if (hdr->x == 1 && session_keys->rtp_xtn_hdr_cipher) { + /* extensions header encryption RFC 6904 */ + status = srtp_process_header_encryption( + stream, srtp_get_rtp_xtn_hdr(hdr, srtp), session_keys); + if (status) { + return status; + } + } + + if (cryptex_inuse) { + status = srtp_cryptex_protect(cryptex_inplace, hdr, srtp, + session_keys->rtp_cipher); + if (status) { + return status; + } + } + + /* if we're encrypting, exor keystream into the message */ + if (stream->rtp_services & sec_serv_conf) { + status = srtp_cipher_encrypt(session_keys->rtp_cipher, rtp + enc_start, + enc_octet_len, srtp + enc_start, + &enc_octet_len); + if (status) { + return srtp_err_status_cipher_fail; + } + } else if (rtp != srtp) { + /* if no encryption and not-inplace then need to copy rest of packet */ + memcpy(srtp + enc_start, rtp + enc_start, enc_octet_len); + } + + if (cryptex_inuse) { + srtp_cryptex_protect_cleanup(cryptex_inplace, hdr, srtp); + } + + /* + * if we're authenticating, run the authentication function and build + * the RFC 4771 tag + */ + if (auth_start) { + status = srtp_auth_start(session_keys->rtp_auth); + if (status) { + return status; + } + + status = srtp_auth_update(session_keys->rtp_auth, auth_start, rtp_len); + if (status) { + return status; + } + + if (rcc_carry) { + /* TAG = ROC (4 octets, network order) || MAC_tr */ + uint8_t mac[SRTP_MAX_TAG_LEN]; + status = srtp_auth_compute(session_keys->rtp_auth, (uint8_t *)&est, + 4, mac); + if (status) { + return status; + } + memcpy(auth_tag, (uint8_t *)&est, 4); + memcpy(auth_tag + 4, mac, tag_len - 4); + } else { + /* default integrity transform (mode 2, non-ROC packets) */ + status = srtp_auth_compute(session_keys->rtp_auth, (uint8_t *)&est, + 4, auth_tag); + if (status) { + return status; + } + } + } + + *srtp_len = enc_start + enc_octet_len; + + /* increase the packet length by the length of the auth tag (if any) */ + *srtp_len += rcc_tag_len; + + /* increase the packet length by the mki size if used */ + *srtp_len += stream->mki_size; + + return srtp_err_status_ok; +} + +/* + * srtp_unprotect_rcc() implements the RFC 4771 Roll-over Counter Carrying + * (RCC) integrity transform (modes 1 and 2) for receiving. It is dispatched + * to from srtp_unprotect() when stream->rcc_mode is not srtp_rcc_mode_none. + * + * For a ROC-carrying packet (seq congruent to 0 modulo R) the sender's ROC is + * read from the first four octets of the tag and used both to build the + * decryption IV and to compute the MAC; the remaining (tag_len - 4) octets of + * the tag are the truncated MAC and are verified. On success the receiver + * adopts the sender's ROC, providing fast and robust resynchronization. + * + * For packets that do not carry the ROC, mode 1 performs no authentication + * (no tag is present), while mode 2 applies the default integrity transform + * using the locally maintained ROC. + */ +static srtp_err_status_t srtp_unprotect_rcc(srtp_t ctx, + srtp_stream_ctx_t *stream, + const uint8_t *srtp, + size_t srtp_len, + uint8_t *rtp, + size_t *rtp_len, + srtp_session_keys_t *session_keys) +{ + const srtp_hdr_t *hdr = (const srtp_hdr_t *)srtp; + size_t enc_start; + const uint8_t *auth_start; + size_t enc_octet_len = 0; + const uint8_t *auth_tag = NULL; + srtp_xtd_seq_num_t est; + ssize_t delta = 0; + v128_t iv; + srtp_err_status_t status; + uint8_t tmp_tag[SRTP_MAX_TAG_LEN]; + size_t tag_len, prefix_len; + uint16_t seq; + bool rcc_carry; + size_t rcc_tag_len; + uint32_t roc_sender = 0; + bool advance_packet_index = false; + uint32_t roc_to_set = 0; + uint16_t seq_to_set = 0; + + debug_print0(mod_srtp, "function srtp_unprotect_rcc"); + + /* + * This handler implements only the AES-CM/HMAC RCC modes (1 and 2). + * Mode 3 (AEAD) is handled by srtp_unprotect_aead, and srtp_rcc_mode_none + * streams never dispatch here. Reject anything else instead of silently + * applying the wrong transform. + */ + if (stream->rcc_mode != srtp_rcc_mode_1 && + stream->rcc_mode != srtp_rcc_mode_2) { + return srtp_err_status_bad_param; + } + + seq = ntohs(hdr->seq); + rcc_carry = (seq % stream->roc_tx_rate) == 0; + + /* get tag length from stream */ + tag_len = srtp_auth_get_tag_length(session_keys->rtp_auth); + + /* + * in mode 1 packets that do not carry the ROC have no tag at all; in + * every other case the full tag_len octets are present + */ + if (stream->rcc_mode == srtp_rcc_mode_1 && !rcc_carry) { + rcc_tag_len = 0; + } else { + rcc_tag_len = tag_len; + } + + /* + * determine the packet index. For ROC-carrying packets the sender's ROC + * is taken directly from the tag (RFC 4771); otherwise it is estimated + * from the local replay database as in the default transform. + */ + if (rcc_carry) { + if (srtp_len < octets_in_rtp_header + stream->mki_size + tag_len) { + return srtp_err_status_bad_param; + } + /* the ROC is the first four octets of the tag, in network order */ + memcpy(&roc_sender, srtp + srtp_len - tag_len, 4); + roc_sender = ntohl(roc_sender); + est = (((srtp_xtd_seq_num_t)roc_sender) << 16) | seq; + } else { + status = srtp_get_est_pkt_index(hdr, stream, &est, &delta); + if (status && (status != srtp_err_status_pkt_idx_adv)) { + return status; + } + if (status == srtp_err_status_pkt_idx_adv) { + advance_packet_index = true; + roc_to_set = (uint32_t)(est >> 16); + seq_to_set = (uint16_t)(est & 0xFFFF); + } else { + status = srtp_rdbx_check(&stream->rtp_rdbx, delta); + if (status) { + return status; + } + } + } + + debug_print(mod_srtp, "estimated u_packet index: %016" PRIx64, est); + + /* set the AES counter-mode IV from the (possibly sender-supplied) index */ + iv.v32[0] = 0; + iv.v32[1] = hdr->ssrc; /* still in network order */ + iv.v64[1] = be64_to_cpu(est << 16); + status = srtp_cipher_set_iv(session_keys->rtp_cipher, (uint8_t *)&iv, + srtp_direction_decrypt); + if (!status && session_keys->rtp_xtn_hdr_cipher) { + status = srtp_cipher_set_iv(session_keys->rtp_xtn_hdr_cipher, + (uint8_t *)&iv, srtp_direction_decrypt); + } + if (status) { + return srtp_err_status_cipher_fail; + } + + /* shift est, put into network byte order */ + est = be64_to_cpu(est << 16); + + enc_start = srtp_get_rtp_hdr_len(hdr); + if (hdr->x == 1) { + enc_start += srtp_get_rtp_hdr_xtnd_len(hdr, srtp); + } + + bool cryptex_inuse, cryptex_inplace; + status = srtp_cryptex_unprotect_init(stream, hdr, srtp, rtp, &cryptex_inuse, + &cryptex_inplace, &enc_start); + if (status) { + return status; + } + + if (enc_start > srtp_len - rcc_tag_len - stream->mki_size) { + return srtp_err_status_parse_err; + } + enc_octet_len = srtp_len - enc_start - stream->mki_size - rcc_tag_len; + + /* check output length */ + if (*rtp_len < srtp_len - stream->mki_size - rcc_tag_len) { + return srtp_err_status_buffer_small; + } + + /* if not-inplace then need to copy full rtp header */ + if (srtp != rtp) { + memcpy(rtp, srtp, enc_start); + } - if (stream->mki_size > mki_start_location) { - return srtp_err_status_bad_mki; + if ((stream->rtp_services & sec_serv_auth) && rcc_tag_len > 0) { + auth_start = srtp; + /* the tag (ROC and/or MAC) is located at the end of the packet */ + auth_tag = srtp + srtp_len - tag_len; + } else { + auth_start = NULL; + auth_tag = NULL; } - mki_start_location -= stream->mki_size; + /* + * if we expect message authentication, run the authentication function + * and compare the result with the value of the tag + */ + if (auth_start) { + if (session_keys->rtp_auth->prefix_len != 0) { + prefix_len = srtp_auth_get_prefix_length(session_keys->rtp_auth); + status = srtp_cipher_output(session_keys->rtp_cipher, tmp_tag, + &prefix_len); + if (status) { + return srtp_err_status_cipher_fail; + } + } - for (size_t i = 0; i < stream->num_master_keys; i++) { - if (memcmp(hdr + mki_start_location, stream->session_keys[i].mki_id, - stream->mki_size) == 0) { - *session_keys = &stream->session_keys[i]; - return srtp_err_status_ok; + status = srtp_auth_start(session_keys->rtp_auth); + if (status) { + return status; } - } - return srtp_err_status_bad_mki; -} + /* the MAC covers the packet up to the start of the tag */ + status = srtp_auth_update(session_keys->rtp_auth, auth_start, + srtp_len - tag_len - stream->mki_size); + if (status) { + return status; + } -static srtp_err_status_t srtp_get_session_keys_for_rtp_packet( - srtp_stream_ctx_t *stream, - const uint8_t *hdr, - size_t pkt_octet_len, - srtp_session_keys_t **session_keys) -{ - size_t tag_len = 0; + status = srtp_auth_compute(session_keys->rtp_auth, (uint8_t *)&est, 4, + tmp_tag); + if (status) { + return srtp_err_status_auth_fail; + } - if (stream->num_master_keys == 0 || stream->session_keys == NULL) { - return srtp_err_status_no_ctx; + if (rcc_carry) { + /* MAC_tr occupies the (tag_len - 4) octets after the ROC */ + if (!srtp_octet_string_equal(tmp_tag, auth_tag + 4, tag_len - 4)) { + return srtp_err_status_auth_fail; + } + } else { + if (!srtp_octet_string_equal(tmp_tag, auth_tag, tag_len)) { + return srtp_err_status_auth_fail; + } + } } - // Determine the authentication tag size - if (stream->session_keys[0].rtp_cipher->algorithm == SRTP_AES_GCM_128 || - stream->session_keys[0].rtp_cipher->algorithm == SRTP_AES_GCM_256) { - tag_len = 0; - } else { - tag_len = srtp_auth_get_tag_length(stream->session_keys[0].rtp_auth); + /* + * update the key usage limit, and check it to make sure that we + * didn't just hit either the soft limit or the hard limit + */ + switch (srtp_key_limit_update(session_keys->limit)) { + case srtp_key_event_normal: + break; + case srtp_key_event_soft_limit: + srtp_handle_event(ctx, stream, event_key_soft_limit); + break; + case srtp_key_event_hard_limit: + srtp_handle_event(ctx, stream, event_key_hard_limit); + return srtp_err_status_key_expired; + default: + break; } - return srtp_get_session_keys_for_packet(stream, hdr, pkt_octet_len, tag_len, - session_keys); -} - -static srtp_err_status_t srtp_get_session_keys_for_rtcp_packet( - srtp_stream_ctx_t *stream, - const uint8_t *hdr, - size_t pkt_octet_len, - srtp_session_keys_t **session_keys) -{ - size_t tag_len = 0; - - if (stream->num_master_keys == 0 || stream->session_keys == NULL) { - return srtp_err_status_no_ctx; + if (hdr->x == 1 && session_keys->rtp_xtn_hdr_cipher) { + /* extensions header encryption RFC 6904 */ + status = srtp_process_header_encryption( + stream, srtp_get_rtp_xtn_hdr(hdr, rtp), session_keys); + if (status) { + return status; + } } - // Determine the authentication tag size - if (stream->session_keys[0].rtcp_cipher->algorithm == SRTP_AES_GCM_128 || - stream->session_keys[0].rtcp_cipher->algorithm == SRTP_AES_GCM_256) { - tag_len = 0; - } else { - tag_len = srtp_auth_get_tag_length(stream->session_keys[0].rtcp_auth); + if (cryptex_inuse) { + status = srtp_cryptex_unprotect(cryptex_inplace, hdr, rtp, + session_keys->rtp_cipher); + if (status) { + return status; + } } - return srtp_get_session_keys_for_packet(stream, hdr, pkt_octet_len, tag_len, - session_keys); -} - -static srtp_err_status_t srtp_estimate_index(srtp_rdbx_t *rdbx, - uint32_t roc, - srtp_xtd_seq_num_t *est, - srtp_sequence_number_t seq, - ssize_t *delta) -{ - *est = (srtp_xtd_seq_num_t)(((uint64_t)roc) << 16) | seq; - *delta = *est - rdbx->index; - - if (*est > rdbx->index) { - if (*est - rdbx->index > seq_num_median) { - *delta = 0; - return srtp_err_status_pkt_idx_adv; - } - } else if (*est < rdbx->index) { - if (rdbx->index - *est > seq_num_median) { - *delta = 0; - return srtp_err_status_pkt_idx_old; + /* if we're decrypting, add keystream into ciphertext */ + if (stream->rtp_services & sec_serv_conf) { + status = + srtp_cipher_decrypt(session_keys->rtp_cipher, srtp + enc_start, + enc_octet_len, rtp + enc_start, &enc_octet_len); + if (status) { + return srtp_err_status_cipher_fail; } + } else if (rtp != srtp) { + /* if no encryption and not-inplace then need to copy rest of packet */ + memcpy(rtp + enc_start, srtp + enc_start, enc_octet_len); } - return srtp_err_status_ok; -} + if (cryptex_inuse) { + srtp_cryptex_unprotect_cleanup(cryptex_inplace, hdr, rtp); + } -static srtp_err_status_t srtp_get_est_pkt_index(const srtp_hdr_t *hdr, - srtp_stream_ctx_t *stream, - srtp_xtd_seq_num_t *est, - ssize_t *delta) -{ - srtp_err_status_t result = srtp_err_status_ok; + /* + * verify that stream is for received traffic - this check will detect + * SSRC collisions + */ + if (stream->direction != dir_srtp_receiver) { + if (stream->direction == dir_unknown) { + stream->direction = dir_srtp_receiver; + } else { + srtp_handle_event(ctx, stream, event_ssrc_collision); + } + } - if (stream->pending_roc) { - result = srtp_estimate_index(&stream->rtp_rdbx, stream->pending_roc, - est, ntohs(hdr->seq), delta); + /* + * the authentication passed (or was not required), so update the replay + * database and roll-over counter + */ + if (rcc_carry) { + /* adopt the sender's ROC (RFC 4771 fast resynchronization) */ + srtp_rdbx_set_roc_seq(&stream->rtp_rdbx, roc_sender, seq); + stream->pending_roc = 0; + srtp_rdbx_add_index(&stream->rtp_rdbx, 0); + } else if (advance_packet_index) { + srtp_rdbx_set_roc_seq(&stream->rtp_rdbx, roc_to_set, seq_to_set); + stream->pending_roc = 0; + srtp_rdbx_add_index(&stream->rtp_rdbx, 0); } else { - /* estimate packet index from seq. num. in header */ - *delta = - srtp_rdbx_estimate_index(&stream->rtp_rdbx, est, ntohs(hdr->seq)); + srtp_rdbx_add_index(&stream->rtp_rdbx, delta); } - debug_print(mod_srtp, "estimated u_packet index: %016" PRIx64, *est); + *rtp_len = srtp_len - stream->mki_size - rcc_tag_len; - return result; + return srtp_err_status_ok; } /* @@ -2085,6 +2739,8 @@ static srtp_err_status_t srtp_protect_aead(srtp_ctx_t *ctx, size_t tag_len; v128_t iv; size_t aad_len; + size_t rcc_extra = 0; /* octets of ROC appended for RFC 4771 mode 3 */ + uint32_t rcc_roc = 0; /* sender's ROC carried on ROC-carrying packets */ debug_print0(mod_srtp, "function srtp_protect_aead"); @@ -2108,11 +2764,24 @@ static srtp_err_status_t srtp_protect_aead(srtp_ctx_t *ctx, /* get tag length from stream */ tag_len = srtp_auth_get_tag_length(session_keys->rtp_auth); - /* check output length */ - if (*srtp_len < rtp_len + tag_len + stream->mki_size) { - return srtp_err_status_buffer_small; + /* + * RFC 4771 mode 3 over AES-GCM: on a ROC-carrying packet (RTP sequence + * number congruent to 0 modulo R) the sender's 4-octet ROC is carried in + * the SRTP authentication tag field, which RFC 7714 section 8.2 places + * after the optional MKI (see Figure 3). No separate MAC is used; the + * GCM tag authenticates the packet and, because the ROC also feeds the + * GCM IV, it protects the carried ROC against modification. + */ + if (stream->rcc_mode == srtp_rcc_mode_3 && + (ntohs(hdr->seq) % stream->roc_tx_rate) == 0) { + rcc_extra = 4; } + /* check output length */ + if (*srtp_len < rtp_len + tag_len + stream->mki_size + rcc_extra) { + return srtp_err_status_buffer_small; + } + /* * find starting point for encryption and length of data to be * encrypted - the encrypted portion starts after the rtp header @@ -2175,6 +2844,9 @@ static srtp_err_status_t srtp_protect_aead(srtp_ctx_t *ctx, debug_print(mod_srtp, "estimated packet index: %016" PRIx64, est); + /* capture the sender's ROC before est is shifted (RFC 4771 mode 3) */ + rcc_roc = (uint32_t)(est >> 16); + /* * AEAD uses a new IV formation method */ @@ -2232,19 +2904,29 @@ static srtp_err_status_t srtp_protect_aead(srtp_ctx_t *ctx, return srtp_err_status_cipher_fail; } - if (stream->use_mki) { - srtp_inject_mki(srtp + enc_start + enc_octet_len, session_keys, - stream->mki_size); - } - if (cryptex_inuse) { srtp_cryptex_protect_cleanup(cryptex_inplace, hdr, srtp); } *srtp_len = enc_start + enc_octet_len; - /* increase the packet length by the length of the mki_size */ - *srtp_len += stream->mki_size; + /* + * Append the Raw Data fields in the order mandated by RFC 7714 + * section 8.2 (Figure 3): the optional SRTP MKI comes first, followed + * by the SRTP authentication tag field. For RFC 4771 mode 3 that tag + * field carries the sender's 4-octet ROC (the GCM tag itself is already + * part of the ciphertext written above). + */ + if (stream->use_mki) { + srtp_inject_mki(srtp + *srtp_len, session_keys, stream->mki_size); + *srtp_len += stream->mki_size; + } + + if (rcc_extra) { + uint32_t roc_net = htonl(rcc_roc); + memcpy(srtp + *srtp_len, &roc_net, sizeof(roc_net)); + *srtp_len += rcc_extra; + } return srtp_err_status_ok; } @@ -2274,6 +2956,9 @@ static srtp_err_status_t srtp_unprotect_aead(srtp_ctx_t *ctx, srtp_err_status_t status; size_t tag_len; size_t aad_len; + size_t rcc_extra = 0; /* octets of ROC carried (RFC 4771 mode 3) */ + bool rcc_adopt_roc = false; /* whether to adopt the sender's ROC */ + uint32_t rcc_roc_sender = 0; /* ROC read from a ROC-carrying packet */ debug_print0(mod_srtp, "function srtp_unprotect_aead"); @@ -2282,6 +2967,49 @@ static srtp_err_status_t srtp_unprotect_aead(srtp_ctx_t *ctx, /* get tag length from stream */ tag_len = srtp_auth_get_tag_length(session_keys->rtp_auth); + /* + * RFC 4771 mode 3 over AES-GCM: a ROC-carrying packet (RTP sequence + * number congruent to 0 modulo R) carries the sender's 4-octet ROC in the + * SRTP authentication tag field, which RFC 7714 section 8.2 places at the + * very end of the packet, after the optional MKI (see Figure 3). Read + * that ROC and use it both to form the decryption IV and to resynchronize + * the local ROC; for other packets the index is estimated from the local + * replay database as usual. The ROC is authenticated implicitly because + * it feeds the GCM IV, so a modified ROC yields a wrong IV and GCM tag + * verification fails. + */ + if (stream->rcc_mode == srtp_rcc_mode_3) { + uint16_t seq = ntohs(hdr->seq); + if ((seq % stream->roc_tx_rate) == 0) { + rcc_extra = 4; + if (srtp_len < octets_in_rtp_header + stream->mki_size + tag_len + + rcc_extra) { + return srtp_err_status_bad_param; + } + /* the ROC is the last field, after the optional MKI */ + memcpy(&rcc_roc_sender, srtp + srtp_len - rcc_extra, 4); + rcc_roc_sender = ntohl(rcc_roc_sender); + est = (((srtp_xtd_seq_num_t)rcc_roc_sender) << 16) | seq; + delta = 0; + advance_packet_index = false; + rcc_adopt_roc = true; + } else { + status = srtp_get_est_pkt_index(hdr, stream, &est, &delta); + if (status && (status != srtp_err_status_pkt_idx_adv)) { + return status; + } + if (status == srtp_err_status_pkt_idx_adv) { + advance_packet_index = true; + } else { + advance_packet_index = false; + status = srtp_rdbx_check(&stream->rtp_rdbx, delta); + if (status) { + return status; + } + } + } + } + /* * AEAD uses a new IV formation method */ @@ -2318,14 +3046,15 @@ static srtp_err_status_t srtp_unprotect_aead(srtp_ctx_t *ctx, } if (tag_len + stream->mki_size > srtp_len || - enc_start > srtp_len - tag_len - stream->mki_size) { + enc_start > srtp_len - tag_len - stream->mki_size - rcc_extra) { return srtp_err_status_parse_err; } /* - * We pass the tag down to the cipher when doing GCM mode + * We pass the tag down to the cipher when doing GCM mode. Any ROC + * carried for RFC 4771 mode 3 sits after the tag and is excluded here. */ - enc_octet_len = srtp_len - enc_start - stream->mki_size; + enc_octet_len = srtp_len - enc_start - stream->mki_size - rcc_extra; /* * Sanity check the encrypted payload length against @@ -2337,7 +3066,7 @@ static srtp_err_status_t srtp_unprotect_aead(srtp_ctx_t *ctx, } /* check output length */ - if (*rtp_len < srtp_len - stream->mki_size - tag_len) { + if (*rtp_len < srtp_len - stream->mki_size - tag_len - rcc_extra) { return srtp_err_status_buffer_small; } @@ -2459,7 +3188,13 @@ static srtp_err_status_t srtp_unprotect_aead(srtp_ctx_t *ctx, * the message authentication function passed, so add the packet * index into the replay database */ - if (advance_packet_index) { + if (rcc_adopt_roc) { + /* RFC 4771: adopt the sender's ROC for fast resynchronization */ + srtp_rdbx_set_roc_seq(&stream->rtp_rdbx, rcc_roc_sender, + (uint16_t)(est & 0xFFFF)); + stream->pending_roc = 0; + srtp_rdbx_add_index(&stream->rtp_rdbx, 0); + } else if (advance_packet_index) { uint32_t roc_to_set = (uint32_t)(est >> 16); uint16_t seq_to_set = (uint16_t)(est & 0xFFFF); srtp_rdbx_set_roc_seq(&stream->rtp_rdbx, roc_to_set, seq_to_set); @@ -2574,6 +3309,15 @@ srtp_err_status_t srtp_protect(srtp_t ctx, session_keys); } + /* + * If RFC 4771 RCC is enabled for this stream, dispatch to the RCC + * handler which carries the ROC inside the authentication tag. + */ + if (stream->rcc_mode != srtp_rcc_mode_none) { + return srtp_protect_rcc(ctx, stream, rtp, rtp_len, srtp, srtp_len, + session_keys); + } + /* * update the key usage limit, and check it to make sure that we * didn't just hit either the soft limit or the hard limit, and call @@ -2865,32 +3609,57 @@ srtp_err_status_t srtp_unprotect(srtp_t ctx, return srtp_err_status_no_ctx; } } else { - status = srtp_get_est_pkt_index(hdr, stream, &est, &delta); + /* + * For RFC 4771 RCC streams the packet index (and the replay check for + * ROC-carrying packets) is handled inside srtp_unprotect_rcc(), which + * may use the sender-supplied ROC. Estimating it here could reject a + * packet from a receiver that is not yet synchronized, so skip it. + */ + if (stream->rcc_mode != srtp_rcc_mode_none) { + est = (srtp_xtd_seq_num_t)ntohs(hdr->seq); + delta = (int)est; + } else { + status = srtp_get_est_pkt_index(hdr, stream, &est, &delta); - if (status && (status != srtp_err_status_pkt_idx_adv)) { - return status; - } + if (status && (status != srtp_err_status_pkt_idx_adv)) { + return status; + } - if (status == srtp_err_status_pkt_idx_adv) { - advance_packet_index = true; - roc_to_set = (uint32_t)(est >> 16); - seq_to_set = (uint16_t)(est & 0xFFFF); - } + if (status == srtp_err_status_pkt_idx_adv) { + advance_packet_index = true; + roc_to_set = (uint32_t)(est >> 16); + seq_to_set = (uint16_t)(est & 0xFFFF); + } - /* check replay database */ - if (!advance_packet_index) { - status = srtp_rdbx_check(&stream->rtp_rdbx, delta); - if (status) { - return status; + /* check replay database */ + if (!advance_packet_index) { + status = srtp_rdbx_check(&stream->rtp_rdbx, delta); + if (status) { + return status; + } } } } debug_print(mod_srtp, "estimated u_packet index: %016" PRIx64, est); - /* Determine if MKI is being used and what session keys should be used */ - status = srtp_get_session_keys_for_rtp_packet(stream, srtp, srtp_len, + /* + * Determine if MKI is being used and what session keys should be used. + * For RFC 4771 mode 3 the sender's 4-octet ROC is carried in the SRTP + * authentication tag field, which RFC 7714 section 8.2 places after the + * MKI. Exclude that trailing ROC from the length so the MKI is located + * correctly (the MKI lookup expects the MKI to be the last field). + */ + { + size_t mki_lookup_len = srtp_len; + if (stream->rcc_mode == srtp_rcc_mode_3 && + (ntohs(hdr->seq) % stream->roc_tx_rate) == 0 && + srtp_len >= octets_in_rtp_header + 4) { + mki_lookup_len -= 4; + } + status = srtp_get_session_keys_for_rtp_packet(stream, srtp, mki_lookup_len, &session_keys); + } if (status) { return status; } @@ -2905,6 +3674,16 @@ srtp_err_status_t srtp_unprotect(srtp_t ctx, rtp_len, session_keys, advance_packet_index); } + /* + * If RFC 4771 RCC is enabled for this stream, dispatch to the RCC handler + * which reads the sender's ROC from the authentication tag. + */ + if (stream->rcc_mode != srtp_rcc_mode_none && + stream != ctx->stream_template) { + return srtp_unprotect_rcc(ctx, stream, srtp, srtp_len, rtp, rtp_len, + session_keys); + } + /* get tag length from stream */ tag_len = srtp_auth_get_tag_length(session_keys->rtp_auth); @@ -4680,6 +5459,16 @@ srtp_err_status_t stream_get_protect_trailer_length(srtp_stream_ctx_t *stream, } if (is_rtp) { *length += srtp_auth_get_tag_length(session_key->rtp_auth); + /* + * RFC 4771 mode 3 (AES-GCM): ROC-carrying packets append a 4-octet ROC + * after the authentication tag (RFC 7714 section 8.2). Report the + * worst case so callers size their buffers for ROC-carrying packets. + * Modes 1 and 2 (AES-CM) carry the ROC inside the existing HMAC tag and + * therefore add no extra trailer octets. + */ + if (stream->rcc_mode == srtp_rcc_mode_3) { + *length += 4; + } } else { *length += srtp_auth_get_tag_length(session_key->rtcp_auth); *length += sizeof(srtcp_trailer_t); diff --git a/srtp/srtp_policy.c b/srtp/srtp_policy.c index 36d71d920..4e26ea1c0 100644 --- a/srtp/srtp_policy.c +++ b/srtp/srtp_policy.c @@ -1022,6 +1022,18 @@ srtp_err_status_t srtp_policy_get_mki_length(srtp_policy_t policy, return srtp_err_status_ok; } +srtp_err_status_t srtp_policy_set_rcc_mode_tx_rate(srtp_policy_t policy, srtp_rcc_mode_t rcc_mode, uint16_t roc_tx_rate) +{ + if (policy == NULL) { + return srtp_err_status_bad_param; + } + + policy->rcc_mode = rcc_mode; + policy->roc_tx_rate = roc_tx_rate; + + return srtp_err_status_ok; +} + srtp_err_status_t srtp_policy_add_key(srtp_policy_t policy, const uint8_t *key, size_t key_len, diff --git a/test/rcc_test.c b/test/rcc_test.c new file mode 100644 index 000000000..3359ef4a5 --- /dev/null +++ b/test/rcc_test.c @@ -0,0 +1,550 @@ +/* + * Standalone round-trip tests for the RFC 4771 RCC (Roll-over Counter Carrying) + * support added to libsrtp. + * + * The mode 1 and mode 2 tests work with the native crypto backend. The GCM + * mode 3 tests (RFC 4771 NULL-MAC carried over AES-GCM, RFC 7714) require a + * crypto backend that provides AES-GCM, so build with one enabled (e.g. + * OpenSSL). + */ + +/* + * + * Copyright (c) 2026 + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include +#include +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#include "srtp.h" + +static uint8_t key[30] = { + 0xe1, 0xf9, 0x7a, 0x0d, 0x3e, 0x01, 0x8b, 0xe0, 0xd6, 0x4f, 0xa3, 0x2c, + 0x06, 0xde, 0x41, 0x39, 0x0e, 0xc6, 0x75, 0xad, 0x49, 0x8a, 0xfe, 0xeb, + 0xb6, 0x96, 0x0b, 0x3a, 0xab, 0xe6 +}; + +#define SSRC 0xcafebabe + +static void make_policy_rate(srtp_policy_t *p, srtp_rcc_mode_t mode, + srtp_ssrc_type_t dir, uint16_t rate) +{ + srtp_ssrc_t ssrc = { dir, SSRC }; + srtp_policy_create(p); + srtp_policy_set_profile(*p, srtp_profile_aes128_cm_sha1_80); + srtp_policy_set_sec_serv(*p, sec_serv_conf_and_auth, sec_serv_conf_and_auth); + srtp_policy_set_ssrc(*p, ssrc); + srtp_policy_set_rcc_mode_tx_rate(*p, mode, rate); + srtp_policy_set_window_size(*p, 128); + srtp_policy_add_key(*p, key, SRTP_AES_128_KEY_LEN, key + SRTP_AES_128_KEY_LEN, + SRTP_SALT_LEN, NULL, 0); +} + +static void make_policy(srtp_policy_t *p, srtp_rcc_mode_t mode, + srtp_ssrc_type_t dir) +{ + make_policy_rate(p, mode, dir, 1); +} + +/* AES-GCM-128 key (16 octets) + salt (12 octets) = 28 octets */ +static uint8_t gcm_key[28] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, + 0x18, 0x19, 0x1a, 0x1b +}; + +#ifdef GCM +static void make_gcm_policy_rate(srtp_policy_t *p, srtp_rcc_mode_t mode, + srtp_ssrc_type_t dir, uint16_t rate) +{ + srtp_ssrc_t ssrc = { dir, SSRC }; + srtp_policy_create(p); + srtp_policy_set_profile(*p, srtp_profile_aead_aes_128_gcm); + srtp_policy_set_sec_serv(*p, sec_serv_conf_and_auth, sec_serv_conf_and_auth); + srtp_policy_set_ssrc(*p, ssrc); + srtp_policy_set_rcc_mode_tx_rate(*p, mode, rate); + srtp_policy_set_window_size(*p, 128); + srtp_policy_add_key(*p, gcm_key, SRTP_AES_128_KEY_LEN, + gcm_key + SRTP_AES_128_KEY_LEN, SRTP_AEAD_SALT_LEN, + NULL, 0); +} +#endif + +/* build an RTP packet with given seq and a fixed payload */ +static size_t make_rtp(uint8_t *buf, uint16_t seq, const char *payload) +{ + buf[0] = 0x80; /* V=2 */ + buf[1] = 0x00; /* PT=0 */ + uint16_t nseq = htons(seq); + memcpy(buf + 2, &nseq, 2); + uint32_t ts = htonl(0x1234); + memcpy(buf + 4, &ts, 4); + uint32_t ssrc = htonl(SSRC); + memcpy(buf + 8, &ssrc, 4); + size_t plen = strlen(payload); + memcpy(buf + 12, payload, plen); + return 12 + plen; +} + +static int roundtrip(srtp_t snd, srtp_t rcv, uint16_t seq, const char *msg) +{ + uint8_t pkt[256]; + size_t len = make_rtp(pkt, seq, msg); + uint8_t enc[256]; + size_t enc_len = sizeof(enc); + srtp_err_status_t s = srtp_protect(snd, pkt, len, enc, &enc_len, 0); + if (s) { printf(" protect seq=%u failed: %d\n", seq, s); return 1; } + + uint8_t dec[256]; + size_t dec_len = sizeof(dec); + s = srtp_unprotect(rcv, enc, enc_len, dec, &dec_len); + if (s) { printf(" unprotect seq=%u failed: %d\n", seq, s); return 1; } + + if (dec_len != len || memcmp(dec, pkt, len) != 0) { + printf(" payload mismatch seq=%u (dec_len=%zu, exp=%zu)\n", + seq, dec_len, len); + return 1; + } + return 0; +} + +int main(void) +{ + if (srtp_init() != srtp_err_status_ok) { printf("init fail\n"); return 1; } + + int fails = 0; + + /* ---- Test 1: mode 2, basic round trip several packets ---- */ + { + srtp_t snd, rcv; + srtp_policy_t sp, rp; + uint16_t rate = 3; /* carry every 3rd packet, arbitrary non-power-of-2 */ + make_policy_rate(&sp, srtp_rcc_mode_2, ssrc_specific, rate); + make_policy_rate(&rp, srtp_rcc_mode_2, ssrc_specific, rate); + srtp_create(&snd, sp); + srtp_create(&rcv, rp); + int f = 0; + for (uint16_t seq = 1; seq <= 50; seq++) + f += roundtrip(snd, rcv, seq, "hello world"); + printf("Test1 mode2 basic: %s\n", f ? "FAIL" : "PASS"); + fails += f; + srtp_dealloc(snd); srtp_dealloc(rcv); + } + + /* ---- Test 2: mode 1, basic round trip ---- */ + { + srtp_t snd, rcv; + srtp_policy_t sp, rp; + uint16_t rate = 3; /* carry every 3rd packet, arbitrary non-power-of-2 */ + make_policy_rate(&sp, srtp_rcc_mode_1, ssrc_specific, rate); + make_policy_rate(&rp, srtp_rcc_mode_1, ssrc_specific, rate); + srtp_create(&snd, sp); + srtp_create(&rcv, rp); + int f = 0; + for (uint16_t seq = 1; seq <= 50; seq++) + f += roundtrip(snd, rcv, seq, "mode one data"); + printf("Test2 mode1 basic: %s\n", f ? "FAIL" : "PASS"); + fails += f; + srtp_dealloc(snd); srtp_dealloc(rcv); + } + + /* ---- Test 3: late-joining receiver after ROC advanced (mode 2) ---- + * sender wraps seq past 65535 so ROC becomes 1, then a brand-new + * receiver must adopt the ROC carried in the packet (R=1 every packet). + */ + { + srtp_t snd; + srtp_policy_t sp; + make_policy(&sp, srtp_rcc_mode_2, ssrc_specific); + srtp_create(&snd, sp); + + uint8_t pkt[256], enc[256]; + size_t len, enc_len; + + /* push sender's ROC to 1 by walking the sequence number around */ + for (uint32_t i = 0; i < 70000; i += 4096) { + len = make_rtp(pkt, (uint16_t)i, "x"); + enc_len = sizeof(enc); + srtp_protect(snd, pkt, len, enc, &enc_len, 0); + } + uint32_t roc = 0; + srtp_stream_get_roc(snd, SSRC, &roc); + printf("Test3: sender ROC after wrap = %u\n", roc); + + /* now a fresh receiver joins and must sync via in-band ROC */ + srtp_t rcv; + srtp_policy_t rp; + make_policy(&rp, srtp_rcc_mode_2, ssrc_specific); + srtp_create(&rcv, rp); + + uint16_t seq = 5000; /* arbitrary, ROC still 1 */ + len = make_rtp(pkt, seq, "late join payload"); + enc_len = sizeof(enc); + srtp_err_status_t s = srtp_protect(snd, pkt, len, enc, &enc_len, 0); + uint8_t dec[256]; size_t dec_len = sizeof(dec); + s = srtp_unprotect(rcv, enc, enc_len, dec, &dec_len); + int f = 0; + if (s) { printf(" late-join unprotect failed: %d\n", s); f = 1; } + else if (dec_len != len || memcmp(dec, pkt, len)) { + printf(" late-join payload mismatch\n"); f = 1; + } + uint32_t rroc = 0; + srtp_stream_get_roc(rcv, SSRC, &rroc); + if (rroc != roc) { printf(" receiver ROC=%u != sender ROC=%u\n", rroc, roc); f = 1; } + printf("Test3 mode2 late-join ROC sync: %s\n", f ? "FAIL" : "PASS"); + fails += f; + srtp_dealloc(snd); srtp_dealloc(rcv); + } + + /* ---- Test 4: GCM + RCC mode 2 rejected at create ---- + * Modes 1 and 2 embed the ROC in a truncated HMAC and are not defined for + * AEAD/GCM, so srtp_create() must reject them. (Mode 3 over GCM is the + * supported combination and is exercised by the GCM tests below.) + */ + { + srtp_t s; + srtp_policy_t p; + make_gcm_policy_rate(&p, srtp_rcc_mode_2, ssrc_specific, 1); + srtp_err_status_t st = srtp_create(&s, p); + int f = (st == srtp_err_status_ok) ? 1 : 0; + printf("Test4 GCM+RCC mode2 rejected: %s (status=%d)\n", + f ? "FAIL" : "PASS", st); + fails += f; + if (st == srtp_err_status_ok) srtp_dealloc(s); + } + + /* ---- Test 5: mode 2, R=4 ---- + * Only seq % 4 == 0 carries the ROC (constant tag length); the other + * packets use the default full-length MAC computed over the local ROC. + * Walk a contiguous run starting at seq 0 so every packet type is hit. + */ + { + srtp_t snd, rcv; + srtp_policy_t sp, rp; + make_policy_rate(&sp, srtp_rcc_mode_2, ssrc_specific, 4); + make_policy_rate(&rp, srtp_rcc_mode_2, ssrc_specific, 4); + srtp_create(&snd, sp); + srtp_create(&rcv, rp); + int f = 0; + for (uint16_t seq = 0; seq <= 12; seq++) + f += roundtrip(snd, rcv, seq, "mode2 rate4 payload"); + printf("Test5 mode2 R=4 (carry + non-carry): %s\n", f ? "FAIL" : "PASS"); + fails += f; + srtp_dealloc(snd); srtp_dealloc(rcv); + } + + /* ---- Test 6: mode 1, R=4 ---- + * Carry packets (seq % 4 == 0) get TAG = ROC || MAC_tr; the other packets + * carry no tag at all (variable packet length, no authentication). + */ + { + srtp_t snd, rcv; + srtp_policy_t sp, rp; + make_policy_rate(&sp, srtp_rcc_mode_1, ssrc_specific, 4); + make_policy_rate(&rp, srtp_rcc_mode_1, ssrc_specific, 4); + srtp_create(&snd, sp); + srtp_create(&rcv, rp); + int f = 0; + for (uint16_t seq = 0; seq <= 12; seq++) + f += roundtrip(snd, rcv, seq, "mode1 rate4 payload"); + printf("Test6 mode1 R=4 (carry + untagged): %s\n", f ? "FAIL" : "PASS"); + fails += f; + srtp_dealloc(snd); srtp_dealloc(rcv); + } + + /* ---- Test 7: mode 2, R=4, late-joining receiver after a wrap ---- + * The sender advances its ROC to 1, then a fresh receiver joins. The next + * ROC-carrying packet (seq % 4 == 0) must resynchronize the receiver. + */ + { + srtp_t snd; + srtp_policy_t sp; + make_policy_rate(&sp, srtp_rcc_mode_2, ssrc_specific, 4); + srtp_create(&snd, sp); + + uint8_t pkt[256], enc[256]; + size_t len, enc_len; + for (uint32_t i = 0; i < 70000; i += 4096) { + len = make_rtp(pkt, (uint16_t)(i & ~0x3u), "x"); /* keep carry */ + enc_len = sizeof(enc); + srtp_protect(snd, pkt, len, enc, &enc_len, 0); + } + uint32_t roc = 0; + srtp_stream_get_roc(snd, SSRC, &roc); + printf("Test7: sender ROC after wrap = %u\n", roc); + + srtp_t rcv; + srtp_policy_t rp; + make_policy_rate(&rp, srtp_rcc_mode_2, ssrc_specific, 4); + srtp_create(&rcv, rp); + + uint16_t seq = 5000; /* 5000 % 4 == 0 -> carry packet */ + len = make_rtp(pkt, seq, "late join r4 payload"); + enc_len = sizeof(enc); + srtp_protect(snd, pkt, len, enc, &enc_len, 0); + uint8_t dec[256]; size_t dec_len = sizeof(dec); + srtp_err_status_t s = srtp_unprotect(rcv, enc, enc_len, dec, &dec_len); + int f = 0; + if (s) { printf(" late-join unprotect failed: %d\n", s); f = 1; } + else if (dec_len != len || memcmp(dec, pkt, len)) { + printf(" late-join payload mismatch\n"); f = 1; + } + uint32_t rroc = 0; + srtp_stream_get_roc(rcv, SSRC, &rroc); + if (rroc != roc) { printf(" receiver ROC=%u != sender ROC=%u\n", rroc, roc); f = 1; } + printf("Test7 mode2 R=4 late-join ROC sync: %s\n", f ? "FAIL" : "PASS"); + fails += f; + srtp_dealloc(snd); srtp_dealloc(rcv); + } + +#ifdef GCM + /* ---- Test 8: GCM + RCC mode 3 accepted at create ---- */ + { + srtp_t s; + srtp_policy_t p; + make_gcm_policy_rate(&p, srtp_rcc_mode_3, ssrc_specific, 1); + srtp_err_status_t st = srtp_create(&s, p); + int f = (st == srtp_err_status_ok) ? 0 : 1; + printf("Test8 GCM+RCC mode3 accepted: %s (status=%d)\n", + f ? "FAIL" : "PASS", st); + fails += f; + if (st == srtp_err_status_ok) srtp_dealloc(s); + } + + /* ---- Test 9: GCM mode 3, basic round trip (R=1, every packet carries + * the ROC in the SRTP auth tag field per RFC 7714 section 8.2) ---- */ + { + srtp_t snd, rcv; + srtp_policy_t sp, rp; + make_gcm_policy_rate(&sp, srtp_rcc_mode_3, ssrc_specific, 1); + make_gcm_policy_rate(&rp, srtp_rcc_mode_3, ssrc_specific, 1); + srtp_create(&snd, sp); + srtp_create(&rcv, rp); + int f = 0; + for (uint16_t seq = 1; seq <= 5; seq++) + f += roundtrip(snd, rcv, seq, "gcm mode3 payload"); + printf("Test9 GCM mode3 basic round trip: %s\n", f ? "FAIL" : "PASS"); + fails += f; + srtp_dealloc(snd); srtp_dealloc(rcv); + } + + /* ---- Test 10: GCM mode 3, R=4 (carry and non-carry packets) ---- + * Only seq % 4 == 0 carries the 4-octet ROC in the SRTP auth tag field; + * the other packets are plain RFC 7714 GCM packets. Both must round-trip. + */ + { + srtp_t snd, rcv; + srtp_policy_t sp, rp; + make_gcm_policy_rate(&sp, srtp_rcc_mode_3, ssrc_specific, 4); + make_gcm_policy_rate(&rp, srtp_rcc_mode_3, ssrc_specific, 4); + srtp_create(&snd, sp); + srtp_create(&rcv, rp); + int f = 0; + for (uint16_t seq = 0; seq <= 12; seq++) + f += roundtrip(snd, rcv, seq, "gcm mode3 rate4 payload"); + printf("Test10 GCM mode3 R=4 (carry + non-carry): %s\n", + f ? "FAIL" : "PASS"); + fails += f; + srtp_dealloc(snd); srtp_dealloc(rcv); + } + + /* ---- Test 11: GCM mode 3, verify the ROC is carried in the SRTP auth + * tag field (the last 4 octets, after the GCM tag and the optional MKI per + * RFC 7714 section 8.2), and that a fresh receiver resynchronizes from the + * in-band ROC ---- + */ + { + srtp_t snd; + srtp_policy_t sp; + make_gcm_policy_rate(&sp, srtp_rcc_mode_3, ssrc_specific, 1); + srtp_create(&snd, sp); + + uint8_t pkt[256], enc[256]; + size_t len, enc_len; + + /* advance the sender's ROC to 1 by walking the sequence number */ + for (uint32_t i = 0; i < 70000; i += 4096) { + len = make_rtp(pkt, (uint16_t)i, "x"); + enc_len = sizeof(enc); + srtp_protect(snd, pkt, len, enc, &enc_len, 0); + } + uint32_t roc = 0; + srtp_stream_get_roc(snd, SSRC, &roc); + printf("Test11: sender ROC after wrap = %u\n", roc); + + uint16_t seq = 5000; + len = make_rtp(pkt, seq, "gcm late join"); + enc_len = sizeof(enc); + srtp_protect(snd, pkt, len, enc, &enc_len, 0); + + /* expected layout (no MKI): header + ciphertext + 16-octet GCM tag + * + 4-octet ROC. The last four octets are the SRTP auth tag field + * carrying the sender's ROC in network order (RFC 7714 section 8.2). + */ + int f = 0; + uint32_t carried = 0; + memcpy(&carried, enc + enc_len - 4, 4); + carried = ntohl(carried); + if (carried != roc) { + printf(" carried ROC=%u != sender ROC=%u\n", carried, roc); + f = 1; + } + if (enc_len != len + 16 + 4) { + printf(" unexpected enc_len=%zu (exp=%zu)\n", enc_len, + len + 16 + 4); + f = 1; + } + + /* a fresh receiver must sync via the in-band ROC */ + srtp_t rcv; + srtp_policy_t rp; + make_gcm_policy_rate(&rp, srtp_rcc_mode_3, ssrc_specific, 1); + srtp_create(&rcv, rp); + uint8_t dec[256]; size_t dec_len = sizeof(dec); + srtp_err_status_t s = srtp_unprotect(rcv, enc, enc_len, dec, &dec_len); + if (s) { printf(" late-join unprotect failed: %d\n", s); f = 1; } + else if (dec_len != len || memcmp(dec, pkt, len)) { + printf(" late-join payload mismatch\n"); f = 1; + } + uint32_t rroc = 0; + srtp_stream_get_roc(rcv, SSRC, &rroc); + if (rroc != roc) { + printf(" receiver ROC=%u != sender ROC=%u\n", rroc, roc); + f = 1; + } + printf("Test11 GCM mode3 ROC-after-tag + late-join sync: %s\n", + f ? "FAIL" : "PASS"); + fails += f; + srtp_dealloc(snd); srtp_dealloc(rcv); + } + + /* ---- Test 12: GCM mode 3, tampering with the carried ROC is detected ---- + * Because the ROC feeds the GCM IV, flipping a ROC bit yields a wrong IV + * and GCM tag verification must fail (implicit ROC integrity). + */ + { + srtp_t snd, rcv; + srtp_policy_t sp, rp; + make_gcm_policy_rate(&sp, srtp_rcc_mode_3, ssrc_specific, 1); + make_gcm_policy_rate(&rp, srtp_rcc_mode_3, ssrc_specific, 1); + srtp_create(&snd, sp); + srtp_create(&rcv, rp); + + uint8_t pkt[256], enc[256], dec[256]; + size_t len = make_rtp(pkt, 100, "tamper test"); + size_t enc_len = sizeof(enc); + srtp_protect(snd, pkt, len, enc, &enc_len, 0); + + /* flip a bit in the carried ROC (last 4 octets) */ + enc[enc_len - 1] ^= 0x01; + + size_t dec_len = sizeof(dec); + srtp_err_status_t s = srtp_unprotect(rcv, enc, enc_len, dec, &dec_len); + int f = (s == srtp_err_status_ok) ? 1 : 0; + printf("Test12 GCM mode3 ROC tamper detected: %s (status=%d)\n", + f ? "FAIL" : "PASS", s); + fails += f; + srtp_dealloc(snd); srtp_dealloc(rcv); + } + + /* ---- Test 13: GCM mode 3 with MKI, verifying the RFC 7714 section 8.2 + * field order: ciphertext (incl. GCM tag), then SRTP MKI, then the SRTP + * authentication tag field (carrying the ROC). The MKI must be located + * correctly even though the ROC follows it, and the packet must round + * trip. + */ + { + static uint8_t mki_id[4] = { 0xde, 0xad, 0xbe, 0xef }; + + srtp_policy_t sp, rp; + srtp_ssrc_t ssrc = { ssrc_specific, SSRC }; + srtp_policy_create(&sp); + srtp_policy_set_profile(sp, srtp_profile_aead_aes_128_gcm); + srtp_policy_set_sec_serv(sp, sec_serv_conf_and_auth, sec_serv_conf_and_auth); + srtp_policy_set_ssrc(sp, ssrc); + srtp_policy_set_rcc_mode_tx_rate(sp, srtp_rcc_mode_3, 1); + srtp_policy_set_window_size(sp, 128); + srtp_policy_use_mki(sp, sizeof(mki_id)); + srtp_policy_add_key(sp, gcm_key, SRTP_AES_128_KEY_LEN, + gcm_key + SRTP_AES_128_KEY_LEN, SRTP_AEAD_SALT_LEN, + mki_id, sizeof(mki_id)); + srtp_policy_clone(sp, &rp); + + srtp_t snd, rcv; + srtp_err_status_t cs = srtp_create(&snd, sp); + srtp_err_status_t cr = srtp_create(&rcv, rp); + int f = 0; + if (cs || cr) { + printf(" create with MKI failed: snd=%d rcv=%d\n", cs, cr); + f = 1; + } else { + uint8_t pkt[256], enc[256], dec[256]; + size_t len = make_rtp(pkt, 42, "gcm mode3 mki payload"); + size_t enc_len = sizeof(enc); + srtp_err_status_t s = + srtp_protect(snd, pkt, len, enc, &enc_len, 0); + if (s) { printf(" protect failed: %d\n", s); f = 1; } + + /* layout: header + cipher + GCM tag (16) + MKI (4) + ROC (4) */ + if (!f && enc_len != len + 16 + 4 + 4) { + printf(" unexpected enc_len=%zu (exp=%zu)\n", enc_len, + len + 16 + 4 + 4); + f = 1; + } + /* MKI must sit immediately before the trailing 4-octet ROC */ + if (!f && memcmp(enc + enc_len - 4 - 4, mki_id, 4) != 0) { + printf(" MKI not found before ROC\n"); + f = 1; + } + size_t dec_len = sizeof(dec); + if (!f) { + s = srtp_unprotect(rcv, enc, enc_len, dec, &dec_len); + if (s) { printf(" unprotect failed: %d\n", s); f = 1; } + else if (dec_len != len || memcmp(dec, pkt, len)) { + printf(" payload mismatch\n"); f = 1; + } + } + } + printf("Test13 GCM mode3 with MKI (RFC 7714 field order): %s\n", + f ? "FAIL" : "PASS"); + fails += f; + if (!cs) srtp_dealloc(snd); + if (!cr) srtp_dealloc(rcv); + } +#endif /* GCM */ + + srtp_shutdown(); + printf("\n%s\n", fails ? "SOME TESTS FAILED" : "ALL TESTS PASSED"); + return fails ? 1 : 0; +} From 07ca20e9f6d51abadf71859be8735f382e2e1132 Mon Sep 17 00:00:00 2001 From: Volodymyr Snitko Date: Tue, 7 Jul 2026 19:21:36 +0200 Subject: [PATCH 2/6] Fixed formatting to satisfy format-check --- include/srtp.h | 6 +- srtp/srtp.c | 14 ++-- srtp/srtp_policy.c | 8 +- test/rcc_test.c | 179 +++++++++++++++++++++++++++++---------------- 4 files changed, 133 insertions(+), 74 deletions(-) diff --git a/include/srtp.h b/include/srtp.h index 3ae4a90dc..e55d03970 100644 --- a/include/srtp.h +++ b/include/srtp.h @@ -386,7 +386,7 @@ srtp_err_status_t srtp_policy_get_profile(srtp_policy_t policy, * - srtp_err_status_bad_param if policy is NULL or profile is unset. */ - /** +/** * @brief srtp_rcc_mode_t selects the RFC 4771 Roll-over Counter Carrying * (RCC) integrity transform mode for an SRTP stream. * @@ -470,7 +470,9 @@ srtp_err_status_t srtp_policy_get_mki_length(srtp_policy_t policy, * - srtp_err_status_ok if the RCC settings were applied. * - srtp_err_status_bad_param if policy is NULL or the rate is invalid. */ -srtp_err_status_t srtp_policy_set_rcc_mode_tx_rate(srtp_policy_t policy, srtp_rcc_mode_t rcc_mode, uint16_t roc_tx_rate); +srtp_err_status_t srtp_policy_set_rcc_mode_tx_rate(srtp_policy_t policy, + srtp_rcc_mode_t rcc_mode, + uint16_t roc_tx_rate); /** * @brief Add a master key and salt to a policy handle. diff --git a/srtp/srtp.c b/srtp/srtp.c index 43b7dd490..a21be35a6 100644 --- a/srtp/srtp.c +++ b/srtp/srtp.c @@ -2191,7 +2191,7 @@ static srtp_err_status_t srtp_protect_rcc(srtp_t ctx, srtp_err_status_t status; size_t tag_len; size_t prefix_len; - bool rcc_carry; /* whether this packet carries the ROC */ + bool rcc_carry; /* whether this packet carries the ROC */ size_t rcc_tag_len; /* number of tag octets actually appended */ debug_print0(mod_srtp, "function srtp_protect_rcc"); @@ -2777,11 +2777,11 @@ static srtp_err_status_t srtp_protect_aead(srtp_ctx_t *ctx, rcc_extra = 4; } - /* check output length */ + /* check output length */ if (*srtp_len < rtp_len + tag_len + stream->mki_size + rcc_extra) { return srtp_err_status_buffer_small; } - + /* * find starting point for encryption and length of data to be * encrypted - the encrypted portion starts after the rtp header @@ -2982,8 +2982,8 @@ static srtp_err_status_t srtp_unprotect_aead(srtp_ctx_t *ctx, uint16_t seq = ntohs(hdr->seq); if ((seq % stream->roc_tx_rate) == 0) { rcc_extra = 4; - if (srtp_len < octets_in_rtp_header + stream->mki_size + tag_len + - rcc_extra) { + if (srtp_len < + octets_in_rtp_header + stream->mki_size + tag_len + rcc_extra) { return srtp_err_status_bad_param; } /* the ROC is the last field, after the optional MKI */ @@ -3657,8 +3657,8 @@ srtp_err_status_t srtp_unprotect(srtp_t ctx, srtp_len >= octets_in_rtp_header + 4) { mki_lookup_len -= 4; } - status = srtp_get_session_keys_for_rtp_packet(stream, srtp, mki_lookup_len, - &session_keys); + status = srtp_get_session_keys_for_rtp_packet( + stream, srtp, mki_lookup_len, &session_keys); } if (status) { return status; diff --git a/srtp/srtp_policy.c b/srtp/srtp_policy.c index 4e26ea1c0..4b44976a8 100644 --- a/srtp/srtp_policy.c +++ b/srtp/srtp_policy.c @@ -1022,15 +1022,17 @@ srtp_err_status_t srtp_policy_get_mki_length(srtp_policy_t policy, return srtp_err_status_ok; } -srtp_err_status_t srtp_policy_set_rcc_mode_tx_rate(srtp_policy_t policy, srtp_rcc_mode_t rcc_mode, uint16_t roc_tx_rate) +srtp_err_status_t srtp_policy_set_rcc_mode_tx_rate(srtp_policy_t policy, + srtp_rcc_mode_t rcc_mode, + uint16_t roc_tx_rate) { if (policy == NULL) { return srtp_err_status_bad_param; } - + policy->rcc_mode = rcc_mode; policy->roc_tx_rate = roc_tx_rate; - + return srtp_err_status_ok; } diff --git a/test/rcc_test.c b/test/rcc_test.c index 3359ef4a5..76814cc0b 100644 --- a/test/rcc_test.c +++ b/test/rcc_test.c @@ -49,49 +49,54 @@ #endif #include "srtp.h" -static uint8_t key[30] = { - 0xe1, 0xf9, 0x7a, 0x0d, 0x3e, 0x01, 0x8b, 0xe0, 0xd6, 0x4f, 0xa3, 0x2c, - 0x06, 0xde, 0x41, 0x39, 0x0e, 0xc6, 0x75, 0xad, 0x49, 0x8a, 0xfe, 0xeb, - 0xb6, 0x96, 0x0b, 0x3a, 0xab, 0xe6 -}; +static uint8_t key[30] = { 0xe1, 0xf9, 0x7a, 0x0d, 0x3e, 0x01, 0x8b, 0xe0, + 0xd6, 0x4f, 0xa3, 0x2c, 0x06, 0xde, 0x41, 0x39, + 0x0e, 0xc6, 0x75, 0xad, 0x49, 0x8a, 0xfe, 0xeb, + 0xb6, 0x96, 0x0b, 0x3a, 0xab, 0xe6 }; #define SSRC 0xcafebabe -static void make_policy_rate(srtp_policy_t *p, srtp_rcc_mode_t mode, - srtp_ssrc_type_t dir, uint16_t rate) +static void make_policy_rate(srtp_policy_t *p, + srtp_rcc_mode_t mode, + srtp_ssrc_type_t dir, + uint16_t rate) { srtp_ssrc_t ssrc = { dir, SSRC }; srtp_policy_create(p); srtp_policy_set_profile(*p, srtp_profile_aes128_cm_sha1_80); - srtp_policy_set_sec_serv(*p, sec_serv_conf_and_auth, sec_serv_conf_and_auth); + srtp_policy_set_sec_serv(*p, sec_serv_conf_and_auth, + sec_serv_conf_and_auth); srtp_policy_set_ssrc(*p, ssrc); srtp_policy_set_rcc_mode_tx_rate(*p, mode, rate); srtp_policy_set_window_size(*p, 128); - srtp_policy_add_key(*p, key, SRTP_AES_128_KEY_LEN, key + SRTP_AES_128_KEY_LEN, - SRTP_SALT_LEN, NULL, 0); + srtp_policy_add_key(*p, key, SRTP_AES_128_KEY_LEN, + key + SRTP_AES_128_KEY_LEN, SRTP_SALT_LEN, NULL, 0); } -static void make_policy(srtp_policy_t *p, srtp_rcc_mode_t mode, +static void make_policy(srtp_policy_t *p, + srtp_rcc_mode_t mode, srtp_ssrc_type_t dir) { make_policy_rate(p, mode, dir, 1); } /* AES-GCM-128 key (16 octets) + salt (12 octets) = 28 octets */ -static uint8_t gcm_key[28] = { - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, - 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, - 0x18, 0x19, 0x1a, 0x1b -}; +static uint8_t gcm_key[28] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, + 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, + 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, + 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b }; #ifdef GCM -static void make_gcm_policy_rate(srtp_policy_t *p, srtp_rcc_mode_t mode, - srtp_ssrc_type_t dir, uint16_t rate) +static void make_gcm_policy_rate(srtp_policy_t *p, + srtp_rcc_mode_t mode, + srtp_ssrc_type_t dir, + uint16_t rate) { srtp_ssrc_t ssrc = { dir, SSRC }; srtp_policy_create(p); srtp_policy_set_profile(*p, srtp_profile_aead_aes_128_gcm); - srtp_policy_set_sec_serv(*p, sec_serv_conf_and_auth, sec_serv_conf_and_auth); + srtp_policy_set_sec_serv(*p, sec_serv_conf_and_auth, + sec_serv_conf_and_auth); srtp_policy_set_ssrc(*p, ssrc); srtp_policy_set_rcc_mode_tx_rate(*p, mode, rate); srtp_policy_set_window_size(*p, 128); @@ -124,16 +129,22 @@ static int roundtrip(srtp_t snd, srtp_t rcv, uint16_t seq, const char *msg) uint8_t enc[256]; size_t enc_len = sizeof(enc); srtp_err_status_t s = srtp_protect(snd, pkt, len, enc, &enc_len, 0); - if (s) { printf(" protect seq=%u failed: %d\n", seq, s); return 1; } + if (s) { + printf(" protect seq=%u failed: %d\n", seq, s); + return 1; + } uint8_t dec[256]; size_t dec_len = sizeof(dec); s = srtp_unprotect(rcv, enc, enc_len, dec, &dec_len); - if (s) { printf(" unprotect seq=%u failed: %d\n", seq, s); return 1; } + if (s) { + printf(" unprotect seq=%u failed: %d\n", seq, s); + return 1; + } if (dec_len != len || memcmp(dec, pkt, len) != 0) { - printf(" payload mismatch seq=%u (dec_len=%zu, exp=%zu)\n", - seq, dec_len, len); + printf(" payload mismatch seq=%u (dec_len=%zu, exp=%zu)\n", seq, + dec_len, len); return 1; } return 0; @@ -141,7 +152,10 @@ static int roundtrip(srtp_t snd, srtp_t rcv, uint16_t seq, const char *msg) int main(void) { - if (srtp_init() != srtp_err_status_ok) { printf("init fail\n"); return 1; } + if (srtp_init() != srtp_err_status_ok) { + printf("init fail\n"); + return 1; + } int fails = 0; @@ -149,7 +163,8 @@ int main(void) { srtp_t snd, rcv; srtp_policy_t sp, rp; - uint16_t rate = 3; /* carry every 3rd packet, arbitrary non-power-of-2 */ + uint16_t rate = + 3; /* carry every 3rd packet, arbitrary non-power-of-2 */ make_policy_rate(&sp, srtp_rcc_mode_2, ssrc_specific, rate); make_policy_rate(&rp, srtp_rcc_mode_2, ssrc_specific, rate); srtp_create(&snd, sp); @@ -159,14 +174,16 @@ int main(void) f += roundtrip(snd, rcv, seq, "hello world"); printf("Test1 mode2 basic: %s\n", f ? "FAIL" : "PASS"); fails += f; - srtp_dealloc(snd); srtp_dealloc(rcv); + srtp_dealloc(snd); + srtp_dealloc(rcv); } /* ---- Test 2: mode 1, basic round trip ---- */ { srtp_t snd, rcv; srtp_policy_t sp, rp; - uint16_t rate = 3; /* carry every 3rd packet, arbitrary non-power-of-2 */ + uint16_t rate = + 3; /* carry every 3rd packet, arbitrary non-power-of-2 */ make_policy_rate(&sp, srtp_rcc_mode_1, ssrc_specific, rate); make_policy_rate(&rp, srtp_rcc_mode_1, ssrc_specific, rate); srtp_create(&snd, sp); @@ -176,7 +193,8 @@ int main(void) f += roundtrip(snd, rcv, seq, "mode one data"); printf("Test2 mode1 basic: %s\n", f ? "FAIL" : "PASS"); fails += f; - srtp_dealloc(snd); srtp_dealloc(rcv); + srtp_dealloc(snd); + srtp_dealloc(rcv); } /* ---- Test 3: late-joining receiver after ROC advanced (mode 2) ---- @@ -212,19 +230,27 @@ int main(void) len = make_rtp(pkt, seq, "late join payload"); enc_len = sizeof(enc); srtp_err_status_t s = srtp_protect(snd, pkt, len, enc, &enc_len, 0); - uint8_t dec[256]; size_t dec_len = sizeof(dec); + uint8_t dec[256]; + size_t dec_len = sizeof(dec); s = srtp_unprotect(rcv, enc, enc_len, dec, &dec_len); int f = 0; - if (s) { printf(" late-join unprotect failed: %d\n", s); f = 1; } - else if (dec_len != len || memcmp(dec, pkt, len)) { - printf(" late-join payload mismatch\n"); f = 1; + if (s) { + printf(" late-join unprotect failed: %d\n", s); + f = 1; + } else if (dec_len != len || memcmp(dec, pkt, len)) { + printf(" late-join payload mismatch\n"); + f = 1; } uint32_t rroc = 0; srtp_stream_get_roc(rcv, SSRC, &rroc); - if (rroc != roc) { printf(" receiver ROC=%u != sender ROC=%u\n", rroc, roc); f = 1; } + if (rroc != roc) { + printf(" receiver ROC=%u != sender ROC=%u\n", rroc, roc); + f = 1; + } printf("Test3 mode2 late-join ROC sync: %s\n", f ? "FAIL" : "PASS"); fails += f; - srtp_dealloc(snd); srtp_dealloc(rcv); + srtp_dealloc(snd); + srtp_dealloc(rcv); } /* ---- Test 4: GCM + RCC mode 2 rejected at create ---- @@ -241,7 +267,8 @@ int main(void) printf("Test4 GCM+RCC mode2 rejected: %s (status=%d)\n", f ? "FAIL" : "PASS", st); fails += f; - if (st == srtp_err_status_ok) srtp_dealloc(s); + if (st == srtp_err_status_ok) + srtp_dealloc(s); } /* ---- Test 5: mode 2, R=4 ---- @@ -259,9 +286,11 @@ int main(void) int f = 0; for (uint16_t seq = 0; seq <= 12; seq++) f += roundtrip(snd, rcv, seq, "mode2 rate4 payload"); - printf("Test5 mode2 R=4 (carry + non-carry): %s\n", f ? "FAIL" : "PASS"); + printf("Test5 mode2 R=4 (carry + non-carry): %s\n", + f ? "FAIL" : "PASS"); fails += f; - srtp_dealloc(snd); srtp_dealloc(rcv); + srtp_dealloc(snd); + srtp_dealloc(rcv); } /* ---- Test 6: mode 1, R=4 ---- @@ -280,7 +309,8 @@ int main(void) f += roundtrip(snd, rcv, seq, "mode1 rate4 payload"); printf("Test6 mode1 R=4 (carry + untagged): %s\n", f ? "FAIL" : "PASS"); fails += f; - srtp_dealloc(snd); srtp_dealloc(rcv); + srtp_dealloc(snd); + srtp_dealloc(rcv); } /* ---- Test 7: mode 2, R=4, late-joining receiver after a wrap ---- @@ -313,19 +343,27 @@ int main(void) len = make_rtp(pkt, seq, "late join r4 payload"); enc_len = sizeof(enc); srtp_protect(snd, pkt, len, enc, &enc_len, 0); - uint8_t dec[256]; size_t dec_len = sizeof(dec); + uint8_t dec[256]; + size_t dec_len = sizeof(dec); srtp_err_status_t s = srtp_unprotect(rcv, enc, enc_len, dec, &dec_len); int f = 0; - if (s) { printf(" late-join unprotect failed: %d\n", s); f = 1; } - else if (dec_len != len || memcmp(dec, pkt, len)) { - printf(" late-join payload mismatch\n"); f = 1; + if (s) { + printf(" late-join unprotect failed: %d\n", s); + f = 1; + } else if (dec_len != len || memcmp(dec, pkt, len)) { + printf(" late-join payload mismatch\n"); + f = 1; } uint32_t rroc = 0; srtp_stream_get_roc(rcv, SSRC, &rroc); - if (rroc != roc) { printf(" receiver ROC=%u != sender ROC=%u\n", rroc, roc); f = 1; } + if (rroc != roc) { + printf(" receiver ROC=%u != sender ROC=%u\n", rroc, roc); + f = 1; + } printf("Test7 mode2 R=4 late-join ROC sync: %s\n", f ? "FAIL" : "PASS"); fails += f; - srtp_dealloc(snd); srtp_dealloc(rcv); + srtp_dealloc(snd); + srtp_dealloc(rcv); } #ifdef GCM @@ -339,7 +377,8 @@ int main(void) printf("Test8 GCM+RCC mode3 accepted: %s (status=%d)\n", f ? "FAIL" : "PASS", st); fails += f; - if (st == srtp_err_status_ok) srtp_dealloc(s); + if (st == srtp_err_status_ok) + srtp_dealloc(s); } /* ---- Test 9: GCM mode 3, basic round trip (R=1, every packet carries @@ -356,7 +395,8 @@ int main(void) f += roundtrip(snd, rcv, seq, "gcm mode3 payload"); printf("Test9 GCM mode3 basic round trip: %s\n", f ? "FAIL" : "PASS"); fails += f; - srtp_dealloc(snd); srtp_dealloc(rcv); + srtp_dealloc(snd); + srtp_dealloc(rcv); } /* ---- Test 10: GCM mode 3, R=4 (carry and non-carry packets) ---- @@ -376,7 +416,8 @@ int main(void) printf("Test10 GCM mode3 R=4 (carry + non-carry): %s\n", f ? "FAIL" : "PASS"); fails += f; - srtp_dealloc(snd); srtp_dealloc(rcv); + srtp_dealloc(snd); + srtp_dealloc(rcv); } /* ---- Test 11: GCM mode 3, verify the ROC is carried in the SRTP auth @@ -431,11 +472,15 @@ int main(void) srtp_policy_t rp; make_gcm_policy_rate(&rp, srtp_rcc_mode_3, ssrc_specific, 1); srtp_create(&rcv, rp); - uint8_t dec[256]; size_t dec_len = sizeof(dec); + uint8_t dec[256]; + size_t dec_len = sizeof(dec); srtp_err_status_t s = srtp_unprotect(rcv, enc, enc_len, dec, &dec_len); - if (s) { printf(" late-join unprotect failed: %d\n", s); f = 1; } - else if (dec_len != len || memcmp(dec, pkt, len)) { - printf(" late-join payload mismatch\n"); f = 1; + if (s) { + printf(" late-join unprotect failed: %d\n", s); + f = 1; + } else if (dec_len != len || memcmp(dec, pkt, len)) { + printf(" late-join payload mismatch\n"); + f = 1; } uint32_t rroc = 0; srtp_stream_get_roc(rcv, SSRC, &rroc); @@ -446,7 +491,8 @@ int main(void) printf("Test11 GCM mode3 ROC-after-tag + late-join sync: %s\n", f ? "FAIL" : "PASS"); fails += f; - srtp_dealloc(snd); srtp_dealloc(rcv); + srtp_dealloc(snd); + srtp_dealloc(rcv); } /* ---- Test 12: GCM mode 3, tampering with the carried ROC is detected ---- @@ -475,7 +521,8 @@ int main(void) printf("Test12 GCM mode3 ROC tamper detected: %s (status=%d)\n", f ? "FAIL" : "PASS", s); fails += f; - srtp_dealloc(snd); srtp_dealloc(rcv); + srtp_dealloc(snd); + srtp_dealloc(rcv); } /* ---- Test 13: GCM mode 3 with MKI, verifying the RFC 7714 section 8.2 @@ -491,7 +538,8 @@ int main(void) srtp_ssrc_t ssrc = { ssrc_specific, SSRC }; srtp_policy_create(&sp); srtp_policy_set_profile(sp, srtp_profile_aead_aes_128_gcm); - srtp_policy_set_sec_serv(sp, sec_serv_conf_and_auth, sec_serv_conf_and_auth); + srtp_policy_set_sec_serv(sp, sec_serv_conf_and_auth, + sec_serv_conf_and_auth); srtp_policy_set_ssrc(sp, ssrc); srtp_policy_set_rcc_mode_tx_rate(sp, srtp_rcc_mode_3, 1); srtp_policy_set_window_size(sp, 128); @@ -512,9 +560,11 @@ int main(void) uint8_t pkt[256], enc[256], dec[256]; size_t len = make_rtp(pkt, 42, "gcm mode3 mki payload"); size_t enc_len = sizeof(enc); - srtp_err_status_t s = - srtp_protect(snd, pkt, len, enc, &enc_len, 0); - if (s) { printf(" protect failed: %d\n", s); f = 1; } + srtp_err_status_t s = srtp_protect(snd, pkt, len, enc, &enc_len, 0); + if (s) { + printf(" protect failed: %d\n", s); + f = 1; + } /* layout: header + cipher + GCM tag (16) + MKI (4) + ROC (4) */ if (!f && enc_len != len + 16 + 4 + 4) { @@ -530,17 +580,22 @@ int main(void) size_t dec_len = sizeof(dec); if (!f) { s = srtp_unprotect(rcv, enc, enc_len, dec, &dec_len); - if (s) { printf(" unprotect failed: %d\n", s); f = 1; } - else if (dec_len != len || memcmp(dec, pkt, len)) { - printf(" payload mismatch\n"); f = 1; + if (s) { + printf(" unprotect failed: %d\n", s); + f = 1; + } else if (dec_len != len || memcmp(dec, pkt, len)) { + printf(" payload mismatch\n"); + f = 1; } } } printf("Test13 GCM mode3 with MKI (RFC 7714 field order): %s\n", f ? "FAIL" : "PASS"); fails += f; - if (!cs) srtp_dealloc(snd); - if (!cr) srtp_dealloc(rcv); + if (!cs) + srtp_dealloc(snd); + if (!cr) + srtp_dealloc(rcv); } #endif /* GCM */ From 292bc350a351a065a6e64e92c0d4675b3b564551 Mon Sep 17 00:00:00 2001 From: Volodymyr Snitko Date: Thu, 9 Jul 2026 15:09:14 +0200 Subject: [PATCH 3/6] Adopted review feedback --- CMakeLists.txt | 12 + Makefile.in | 7 +- srtp/srtp.c | 86 ---- srtp/srtp_policy.c | 67 +++ test/meson.build | 1 + test/rcc_test.c | 1145 ++++++++++++++++++++++++-------------------- 6 files changed, 722 insertions(+), 596 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d5eabbe37..59897b793 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -483,6 +483,18 @@ if(LIBSRTP_TEST_APPS) ${ENABLE_WARNINGS_AS_ERRORS}) target_link_libraries(test_srtp_policy srtp3) add_test(test_srtp_policy test_srtp_policy) + + add_executable(rcc_test test/rcc_test.c test/util.c) + target_set_warnings( + TARGET + rcc_test + ENABLE + ${ENABLE_WARNINGS} + AS_ERRORS + ${ENABLE_WARNINGS_AS_ERRORS}) + target_include_directories(rcc_test PRIVATE test) + target_link_libraries(rcc_test srtp3) + add_test(rcc_test rcc_test) endif() find_program(BASH_PROGRAM bash) diff --git a/Makefile.in b/Makefile.in index 6d4954df1..61c2d7269 100644 --- a/Makefile.in +++ b/Makefile.in @@ -48,6 +48,7 @@ runtest: test $(FIND_LIBRARIES) crypto/test/kernel_driver$(EXE) -v >/dev/null $(FIND_LIBRARIES) test/test_srtp$(EXE) >/dev/null $(FIND_LIBRARIES) test/test_srtp_policy$(EXE) >/dev/null + $(FIND_LIBRARIES) test/rcc_test$(EXE) >/dev/null $(FIND_LIBRARIES) test/rdbx_driver$(EXE) -v >/dev/null $(FIND_LIBRARIES) test/srtp_driver$(EXE) -v >/dev/null $(FIND_LIBRARIES) test/roc_driver$(EXE) -v >/dev/null @@ -63,6 +64,7 @@ runtest-valgrind: test @echo "running libsrtp3 test applications... (valgrind)" valgrind --error-exitcode=1 --leak-check=full --suppressions=./valgrind.supp test/test_srtp$(EXE) -v >/dev/null valgrind --error-exitcode=1 --leak-check=full --suppressions=./valgrind.supp test/test_srtp_policy$(EXE) -v >/dev/null + valgrind --error-exitcode=1 --leak-check=full --suppressions=./valgrind.supp test/rcc_test$(EXE) -v >/dev/null valgrind --error-exitcode=1 --leak-check=full --suppressions=./valgrind.supp test/srtp_driver$(EXE) -v >/dev/null @echo "libsrtp3 test applications passed. (valgrind)" @@ -189,7 +191,7 @@ crypto_testapp = $(AES_CALC) crypto/test/cipher_driver$(EXE) \ testapp = $(crypto_testapp) test/srtp_driver$(EXE) test/replay_driver$(EXE) \ test/roc_driver$(EXE) test/rdbx_driver$(EXE) test/rtpw$(EXE) \ - test/test_srtp$(EXE) test/test_srtp_policy$(EXE) + test/test_srtp$(EXE) test/test_srtp_policy$(EXE) test/rcc_test$(EXE) ifeq (1, $(HAVE_PCAP)) testapp += test/rtp_decoder$(EXE) @@ -216,6 +218,9 @@ test/test_srtp$(EXE): test/test_srtp.c test/test_srtp_policy$(EXE): test/test_srtp_policy.c test/util.c $(COMPILE) -I$(srcdir)/test $(LDFLAGS) -o $@ $^ $(LIBS) $(SRTPLIB) +test/rcc_test$(EXE): test/rcc_test.c test/util.c + $(COMPILE) -I$(srcdir)/test $(LDFLAGS) -o $@ $^ $(LIBS) $(SRTPLIB) + crypto/test/datatypes_driver$(EXE): crypto/test/datatypes_driver.c test/util.c $(COMPILE) -I$(srcdir)/test $(LDFLAGS) -o $@ $^ $(LIBS) $(SRTPLIB) diff --git a/srtp/srtp.c b/srtp/srtp.c index a21be35a6..a30446543 100644 --- a/srtp/srtp.c +++ b/srtp/srtp.c @@ -1646,92 +1646,6 @@ static srtp_err_status_t srtp_stream_init(srtp_stream_ctx_t *srtp, return srtp_err_status_bad_param; } - /* - * RFC 4771 RCC: validate the mode and transmission rate. When RCC is - * enabled the four-octet ROC is carried inside the authentication tag, - * so the tag must be able to hold the ROC plus at least one MAC octet. - */ - if (p->rcc_mode != srtp_rcc_mode_none) { - bool is_gcm = (p->rtp.cipher_type == SRTP_AES_GCM_128 || - p->rtp.cipher_type == SRTP_AES_GCM_256); - switch (p->rcc_mode) { - case srtp_rcc_mode_1: - case srtp_rcc_mode_2: - /* - * Modes 1 and 2 carry the ROC inside a truncated HMAC-SHA1 tag - * (TAG = ROC || MAC_tr), so the tag must hold the 4-octet ROC plus - * at least one MAC octet. They are defined only for the AES-CM - * ciphers with HMAC-SHA1, not for AEAD/GCM. - */ - if (p->rtp.auth_tag_len < 5) { - return srtp_err_status_bad_param; - } - if (is_gcm) { - return srtp_err_status_bad_param; - } - break; - case srtp_rcc_mode_3: - /* - * Mode 3 (RFC 4771 NULL-MAC) carries only the 4-octet ROC with no - * MAC of its own. It is supported here on top of AES-GCM: the - * AEAD tag authenticates the packet (RFC 7714) and the ROC is - * appended immediately after the GCM tag, in the SRTP - * authentication tag field retained by RFC 7714 section 7.1. - * Because the carried ROC also feeds the GCM IV, any tampering - * with it is detected by GCM tag verification. - */ - if (!is_gcm) { - return srtp_err_status_bad_param; - } - break; - default: - return srtp_err_status_bad_param; - } - } - - /* - * RFC 4771 RCC: validate the mode and transmission rate. When RCC is - * enabled the four-octet ROC is carried inside the authentication tag, - * so the tag must be able to hold the ROC plus at least one MAC octet. - */ - if (p->rcc_mode != srtp_rcc_mode_none) { - bool is_gcm = (p->rtp.cipher_type == SRTP_AES_GCM_128 || - p->rtp.cipher_type == SRTP_AES_GCM_256); - switch (p->rcc_mode) { - case srtp_rcc_mode_1: - case srtp_rcc_mode_2: - /* - * Modes 1 and 2 carry the ROC inside a truncated HMAC-SHA1 tag - * (TAG = ROC || MAC_tr), so the tag must hold the 4-octet ROC plus - * at least one MAC octet. They are defined only for the AES-CM - * ciphers with HMAC-SHA1, not for AEAD/GCM. - */ - if (p->rtp.auth_tag_len < 5) { - return srtp_err_status_bad_param; - } - if (is_gcm) { - return srtp_err_status_bad_param; - } - break; - case srtp_rcc_mode_3: - /* - * Mode 3 (RFC 4771 NULL-MAC) carries only the 4-octet ROC with no - * MAC of its own. It is supported here on top of AES-GCM: the - * AEAD tag authenticates the packet (RFC 7714) and the ROC is - * appended immediately after the GCM tag, in the SRTP - * authentication tag field retained by RFC 7714 section 7.1. - * Because the carried ROC also feeds the GCM IV, any tampering - * with it is detected by GCM tag verification. - */ - if (!is_gcm) { - return srtp_err_status_bad_param; - } - break; - default: - return srtp_err_status_bad_param; - } - } - if (p->window_size != 0) { err = srtp_rdbx_init(&srtp->rtp_rdbx, p->window_size); } else { diff --git a/srtp/srtp_policy.c b/srtp/srtp_policy.c index 4b44976a8..99435e82b 100644 --- a/srtp/srtp_policy.c +++ b/srtp/srtp_policy.c @@ -922,6 +922,52 @@ srtp_err_status_t srtp_policy_validate(srtp_policy_t policy) return srtp_err_status_bad_param; } + /* + * RFC 4771 RCC: validate the mode against the configured cipher. When RCC + * is enabled the four-octet ROC is carried inside the SRTP authentication + * tag, so for the HMAC modes the tag must be able to hold the ROC plus at + * least one MAC octet. + */ + if (policy->rcc_mode != srtp_rcc_mode_none) { + bool is_gcm = (policy->rtp.cipher_type == SRTP_AES_GCM_128 || + policy->rtp.cipher_type == SRTP_AES_GCM_256); + + /* the transmission rate R must be >= 1 (see set_rcc_mode_tx_rate) */ + if (policy->roc_tx_rate == 0) { + return srtp_err_status_bad_param; + } + + switch (policy->rcc_mode) { + case srtp_rcc_mode_1: + case srtp_rcc_mode_2: + /* + * Modes 1 and 2 carry the ROC inside a truncated HMAC-SHA1 tag + * (TAG = ROC || MAC_tr), so the tag must hold the 4-octet ROC plus + * at least one MAC octet. They are defined only for the AES-CM + * ciphers with HMAC-SHA1, not for AEAD/GCM. + */ + if (policy->rtp.auth_tag_len < 5 || is_gcm) { + return srtp_err_status_bad_param; + } + break; + case srtp_rcc_mode_3: + /* + * Mode 3 (RFC 4771 NULL-MAC) carries only the 4-octet ROC with no + * MAC of its own. It is supported here only on top of AES-GCM + * (RFC 7714): the AEAD tag authenticates the packet and the ROC is + * appended after the GCM tag. Because the carried ROC also feeds + * the GCM IV, any tampering with it is detected by GCM tag + * verification. + */ + if (!is_gcm) { + return srtp_err_status_bad_param; + } + break; + default: + return srtp_err_status_bad_param; + } + } + return srtp_err_status_ok; } @@ -1030,6 +1076,27 @@ srtp_err_status_t srtp_policy_set_rcc_mode_tx_rate(srtp_policy_t policy, return srtp_err_status_bad_param; } + switch (rcc_mode) { + case srtp_rcc_mode_none: + case srtp_rcc_mode_1: + case srtp_rcc_mode_2: + case srtp_rcc_mode_3: + break; + default: + return srtp_err_status_bad_param; + } + + /* + * The transmission rate R selects which packets carry the ROC (those + * whose sequence number is 0 modulo R), so R == 0 is meaningless when RCC + * is enabled; require R >= 1. The rate is ignored when RCC is disabled. + * Cipher/mode consistency (AES-CM vs AES-GCM, tag length) is enforced by + * srtp_policy_validate() once the profile is known. + */ + if (rcc_mode != srtp_rcc_mode_none && roc_tx_rate == 0) { + return srtp_err_status_bad_param; + } + policy->rcc_mode = rcc_mode; policy->roc_tx_rate = roc_tx_rate; diff --git a/test/meson.build b/test/meson.build index c8c2054f2..9528967c1 100644 --- a/test/meson.build +++ b/test/meson.build @@ -12,6 +12,7 @@ test_apps = [ ['rdbx_driver', {'extra_sources': 'ut_sim.c', 'run_args': '-v'}], ['test_srtp', {'run_args': '-v'}], ['test_srtp_policy', {'extra_sources': 'util.c', 'run_args': '-v'}], + ['rcc_test', {'extra_sources': 'util.c', 'run_args': '-v'}], ['rtpw', {'extra_sources': ['rtp.c', 'util.c', '../crypto/math/datatypes.c'], 'define_test': false}], ] diff --git a/test/rcc_test.c b/test/rcc_test.c index 76814cc0b..b38b06b58 100644 --- a/test/rcc_test.c +++ b/test/rcc_test.c @@ -1,13 +1,15 @@ /* - * Standalone round-trip tests for the RFC 4771 RCC (Roll-over Counter Carrying) - * support added to libsrtp. + * rcc_test.c + * + * Unit tests for the RFC 4771 RCC (Roll-over Counter Carrying) integrity + * transform support added to libSRTP. + * + * Modes 1 and 2 use the AES-CM + HMAC-SHA1 transform and work with any crypto + * backend. Mode 3 (RFC 4771 NULL-MAC carried over AES-GCM, RFC 7714) requires + * a backend that provides AES-GCM; those tests are compiled only when GCM is + * available (config.h defines GCM). * - * The mode 1 and mode 2 tests work with the native crypto backend. The GCM - * mode 3 tests (RFC 4771 NULL-MAC carried over AES-GCM, RFC 7714) require a - * crypto backend that provides AES-GCM, so build with one enabled (e.g. - * OpenSSL). */ - /* * * Copyright (c) 2026 @@ -40,566 +42,691 @@ * */ -#include -#include -#include -#include #ifdef HAVE_CONFIG_H #include "config.h" #endif + +#include "cutest.h" + #include "srtp.h" +#include "util.h" -static uint8_t key[30] = { 0xe1, 0xf9, 0x7a, 0x0d, 0x3e, 0x01, 0x8b, 0xe0, - 0xd6, 0x4f, 0xa3, 0x2c, 0x06, 0xde, 0x41, 0x39, - 0x0e, 0xc6, 0x75, 0xad, 0x49, 0x8a, 0xfe, 0xeb, - 0xb6, 0x96, 0x0b, 0x3a, 0xab, 0xe6 }; +#include -#define SSRC 0xcafebabe +#ifdef HAVE_NETINET_IN_H +#include +#elif defined(HAVE_WINSOCK2_H) +#include +#endif -static void make_policy_rate(srtp_policy_t *p, - srtp_rcc_mode_t mode, - srtp_ssrc_type_t dir, - uint16_t rate) -{ - srtp_ssrc_t ssrc = { dir, SSRC }; - srtp_policy_create(p); - srtp_policy_set_profile(*p, srtp_profile_aes128_cm_sha1_80); - srtp_policy_set_sec_serv(*p, sec_serv_conf_and_auth, - sec_serv_conf_and_auth); - srtp_policy_set_ssrc(*p, ssrc); - srtp_policy_set_rcc_mode_tx_rate(*p, mode, rate); - srtp_policy_set_window_size(*p, 128); - srtp_policy_add_key(*p, key, SRTP_AES_128_KEY_LEN, - key + SRTP_AES_128_KEY_LEN, SRTP_SALT_LEN, NULL, 0); -} +#define TEST_SSRC 0xcafebabe -static void make_policy(srtp_policy_t *p, - srtp_rcc_mode_t mode, - srtp_ssrc_type_t dir) -{ - make_policy_rate(p, mode, dir, 1); -} +static const uint8_t cm_master_key[16] = { + 0xe1, 0xf9, 0x7a, 0x0d, 0x3e, 0x01, 0x8b, 0xe0, + 0xd6, 0x4f, 0xa3, 0x2c, 0x06, 0xde, 0x41, 0x39, +}; +static const uint8_t cm_master_salt[14] = { + 0x0e, 0xc6, 0x75, 0xad, 0x49, 0x8a, 0xfe, + 0xeb, 0xb6, 0x96, 0x0b, 0x3a, 0xab, 0xe6, +}; -/* AES-GCM-128 key (16 octets) + salt (12 octets) = 28 octets */ -static uint8_t gcm_key[28] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, - 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, - 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, - 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b }; +static const uint8_t gcm_master_key[16] = { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, +}; +static const uint8_t gcm_master_salt[12] = { + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, +}; -#ifdef GCM -static void make_gcm_policy_rate(srtp_policy_t *p, +static const uint8_t mki4[4] = { 0xde, 0xad, 0xbe, 0xef }; + +static void create_cm_rcc_policy(srtp_policy_t *policy, srtp_rcc_mode_t mode, - srtp_ssrc_type_t dir, uint16_t rate) { - srtp_ssrc_t ssrc = { dir, SSRC }; - srtp_policy_create(p); - srtp_policy_set_profile(*p, srtp_profile_aead_aes_128_gcm); - srtp_policy_set_sec_serv(*p, sec_serv_conf_and_auth, - sec_serv_conf_and_auth); - srtp_policy_set_ssrc(*p, ssrc); - srtp_policy_set_rcc_mode_tx_rate(*p, mode, rate); - srtp_policy_set_window_size(*p, 128); - srtp_policy_add_key(*p, gcm_key, SRTP_AES_128_KEY_LEN, - gcm_key + SRTP_AES_128_KEY_LEN, SRTP_AEAD_SALT_LEN, - NULL, 0); + CHECK_OK(srtp_policy_create(policy)); + CHECK_OK(srtp_policy_set_profile(*policy, srtp_profile_aes128_cm_sha1_80)); + CHECK_OK(srtp_policy_set_sec_serv(*policy, sec_serv_conf_and_auth, + sec_serv_conf_and_auth)); + CHECK_OK(srtp_policy_set_ssrc(*policy, + (srtp_ssrc_t){ ssrc_specific, TEST_SSRC })); + CHECK_OK(srtp_policy_set_rcc_mode_tx_rate(*policy, mode, rate)); + CHECK_OK(srtp_policy_set_window_size(*policy, 128)); + CHECK_OK(srtp_policy_add_key(*policy, cm_master_key, sizeof(cm_master_key), + cm_master_salt, sizeof(cm_master_salt), NULL, + 0)); +} + +#ifdef GCM +static void create_gcm_rcc_policy(srtp_policy_t *policy, + srtp_rcc_mode_t mode, + uint16_t rate) +{ + CHECK_OK(srtp_policy_create(policy)); + CHECK_OK(srtp_policy_set_profile(*policy, srtp_profile_aead_aes_128_gcm)); + CHECK_OK(srtp_policy_set_sec_serv(*policy, sec_serv_conf_and_auth, + sec_serv_conf_and_auth)); + CHECK_OK(srtp_policy_set_ssrc(*policy, + (srtp_ssrc_t){ ssrc_specific, TEST_SSRC })); + CHECK_OK(srtp_policy_set_rcc_mode_tx_rate(*policy, mode, rate)); + CHECK_OK(srtp_policy_set_window_size(*policy, 128)); + CHECK_OK(srtp_policy_add_key(*policy, gcm_master_key, + sizeof(gcm_master_key), gcm_master_salt, + sizeof(gcm_master_salt), NULL, 0)); } #endif -/* build an RTP packet with given seq and a fixed payload */ +/* build an RTP packet with the given sequence number and a fixed payload */ static size_t make_rtp(uint8_t *buf, uint16_t seq, const char *payload) { + uint16_t nseq = htons(seq); + uint32_t ts = htonl(0x1234); + uint32_t ssrc = htonl(TEST_SSRC); + size_t plen = strlen(payload); + buf[0] = 0x80; /* V=2 */ buf[1] = 0x00; /* PT=0 */ - uint16_t nseq = htons(seq); memcpy(buf + 2, &nseq, 2); - uint32_t ts = htonl(0x1234); memcpy(buf + 4, &ts, 4); - uint32_t ssrc = htonl(SSRC); memcpy(buf + 8, &ssrc, 4); - size_t plen = strlen(payload); memcpy(buf + 12, payload, plen); + return 12 + plen; } -static int roundtrip(srtp_t snd, srtp_t rcv, uint16_t seq, const char *msg) +static void rcc_roundtrip(srtp_t snd, srtp_t rcv, uint16_t seq, const char *msg) { - uint8_t pkt[256]; + uint8_t pkt[256], enc[256], dec[256]; size_t len = make_rtp(pkt, seq, msg); - uint8_t enc[256]; size_t enc_len = sizeof(enc); - srtp_err_status_t s = srtp_protect(snd, pkt, len, enc, &enc_len, 0); - if (s) { - printf(" protect seq=%u failed: %d\n", seq, s); - return 1; - } - - uint8_t dec[256]; size_t dec_len = sizeof(dec); - s = srtp_unprotect(rcv, enc, enc_len, dec, &dec_len); - if (s) { - printf(" unprotect seq=%u failed: %d\n", seq, s); - return 1; - } - if (dec_len != len || memcmp(dec, pkt, len) != 0) { - printf(" payload mismatch seq=%u (dec_len=%zu, exp=%zu)\n", seq, - dec_len, len); - return 1; - } - return 0; + CHECK_OK(srtp_protect(snd, pkt, len, enc, &enc_len, 0)); + CHECK_OK(srtp_unprotect(rcv, enc, enc_len, dec, &dec_len)); + CHECK(dec_len == len); + CHECK_BUFFER_EQUAL(dec, pkt, len); } -int main(void) +/* advance the sender's ROC to 1 by walking the sequence number past a wrap */ +static void advance_sender_roc(srtp_t snd) { - if (srtp_init() != srtp_err_status_ok) { - printf("init fail\n"); - return 1; - } + uint8_t pkt[256], enc[256]; + uint32_t roc = 0; - int fails = 0; - - /* ---- Test 1: mode 2, basic round trip several packets ---- */ - { - srtp_t snd, rcv; - srtp_policy_t sp, rp; - uint16_t rate = - 3; /* carry every 3rd packet, arbitrary non-power-of-2 */ - make_policy_rate(&sp, srtp_rcc_mode_2, ssrc_specific, rate); - make_policy_rate(&rp, srtp_rcc_mode_2, ssrc_specific, rate); - srtp_create(&snd, sp); - srtp_create(&rcv, rp); - int f = 0; - for (uint16_t seq = 1; seq <= 50; seq++) - f += roundtrip(snd, rcv, seq, "hello world"); - printf("Test1 mode2 basic: %s\n", f ? "FAIL" : "PASS"); - fails += f; - srtp_dealloc(snd); - srtp_dealloc(rcv); + for (uint32_t i = 0; i < 70000; i += 4096) { + size_t len = make_rtp(pkt, (uint16_t)i, "x"); + size_t enc_len = sizeof(enc); + CHECK_OK(srtp_protect(snd, pkt, len, enc, &enc_len, 0)); } + CHECK_OK(srtp_stream_get_roc(snd, TEST_SSRC, &roc)); + CHECK(roc == 1); +} - /* ---- Test 2: mode 1, basic round trip ---- */ - { - srtp_t snd, rcv; - srtp_policy_t sp, rp; - uint16_t rate = - 3; /* carry every 3rd packet, arbitrary non-power-of-2 */ - make_policy_rate(&sp, srtp_rcc_mode_1, ssrc_specific, rate); - make_policy_rate(&rp, srtp_rcc_mode_1, ssrc_specific, rate); - srtp_create(&snd, sp); - srtp_create(&rcv, rp); - int f = 0; - for (uint16_t seq = 1; seq <= 50; seq++) - f += roundtrip(snd, rcv, seq, "mode one data"); - printf("Test2 mode1 basic: %s\n", f ? "FAIL" : "PASS"); - fails += f; - srtp_dealloc(snd); - srtp_dealloc(rcv); - } +/* + * policy-level validation + */ - /* ---- Test 3: late-joining receiver after ROC advanced (mode 2) ---- - * sender wraps seq past 65535 so ROC becomes 1, then a brand-new - * receiver must adopt the ROC carried in the packet (R=1 every packet). - */ - { - srtp_t snd; - srtp_policy_t sp; - make_policy(&sp, srtp_rcc_mode_2, ssrc_specific); - srtp_create(&snd, sp); - - uint8_t pkt[256], enc[256]; - size_t len, enc_len; - - /* push sender's ROC to 1 by walking the sequence number around */ - for (uint32_t i = 0; i < 70000; i += 4096) { - len = make_rtp(pkt, (uint16_t)i, "x"); - enc_len = sizeof(enc); - srtp_protect(snd, pkt, len, enc, &enc_len, 0); - } - uint32_t roc = 0; - srtp_stream_get_roc(snd, SSRC, &roc); - printf("Test3: sender ROC after wrap = %u\n", roc); - - /* now a fresh receiver joins and must sync via in-band ROC */ - srtp_t rcv; - srtp_policy_t rp; - make_policy(&rp, srtp_rcc_mode_2, ssrc_specific); - srtp_create(&rcv, rp); - - uint16_t seq = 5000; /* arbitrary, ROC still 1 */ - len = make_rtp(pkt, seq, "late join payload"); - enc_len = sizeof(enc); - srtp_err_status_t s = srtp_protect(snd, pkt, len, enc, &enc_len, 0); - uint8_t dec[256]; - size_t dec_len = sizeof(dec); - s = srtp_unprotect(rcv, enc, enc_len, dec, &dec_len); - int f = 0; - if (s) { - printf(" late-join unprotect failed: %d\n", s); - f = 1; - } else if (dec_len != len || memcmp(dec, pkt, len)) { - printf(" late-join payload mismatch\n"); - f = 1; - } - uint32_t rroc = 0; - srtp_stream_get_roc(rcv, SSRC, &rroc); - if (rroc != roc) { - printf(" receiver ROC=%u != sender ROC=%u\n", rroc, roc); - f = 1; - } - printf("Test3 mode2 late-join ROC sync: %s\n", f ? "FAIL" : "PASS"); - fails += f; - srtp_dealloc(snd); - srtp_dealloc(rcv); +/* + * The transmission rate R selects which packets carry the ROC (those whose + * sequence number is 0 modulo R), so R == 0 is meaningless once RCC is + * enabled. srtp_policy_set_rcc_mode_tx_rate() must therefore accept R == 0 + * only for srtp_rcc_mode_none and reject it for every active mode, while a + * rate of 1 remains valid for any mode. + */ +static void rcc_set_mode_rejects_zero_rate(void) +{ + srtp_policy_t policy; + CHECK_OK(srtp_policy_create(&policy)); + + /* rate 0 is only meaningful when RCC is disabled */ + CHECK_OK(srtp_policy_set_rcc_mode_tx_rate(policy, srtp_rcc_mode_none, 0)); + CHECK_RETURN(srtp_policy_set_rcc_mode_tx_rate(policy, srtp_rcc_mode_1, 0), + srtp_err_status_bad_param); + CHECK_RETURN(srtp_policy_set_rcc_mode_tx_rate(policy, srtp_rcc_mode_2, 0), + srtp_err_status_bad_param); + CHECK_RETURN(srtp_policy_set_rcc_mode_tx_rate(policy, srtp_rcc_mode_3, 0), + srtp_err_status_bad_param); + CHECK_OK(srtp_policy_set_rcc_mode_tx_rate(policy, srtp_rcc_mode_2, 1)); + + srtp_policy_destroy(policy); +} + +/* + * Only the four enumerated RCC modes are valid. An out-of-range mode value + * and a NULL policy handle must both be rejected with bad_param. + */ +static void rcc_set_mode_rejects_invalid_mode(void) +{ + srtp_policy_t policy; + CHECK_OK(srtp_policy_create(&policy)); + CHECK_RETURN( + srtp_policy_set_rcc_mode_tx_rate(policy, (srtp_rcc_mode_t)99, 1), + srtp_err_status_bad_param); + CHECK_RETURN(srtp_policy_set_rcc_mode_tx_rate(NULL, srtp_rcc_mode_1, 1), + srtp_err_status_bad_param); + srtp_policy_destroy(policy); +} + +/* + * Modes 1 and 2 embed the ROC inside a truncated HMAC-SHA1 tag and are defined + * only for the AES-CM ciphers, not for AEAD/GCM. srtp_policy_validate() must + * accept them with an AES-CM profile and reject them with a GCM profile. The + * cipher/mode consistency is checked by validate() rather than by the setter + * because the profile may be assigned after the mode. + */ +static void rcc_validate_modes_1_2_require_aes_cm(void) +{ + srtp_policy_t policy; + + create_cm_rcc_policy(&policy, srtp_rcc_mode_1, 1); + CHECK_OK(srtp_policy_validate(policy)); + srtp_policy_destroy(policy); + + create_cm_rcc_policy(&policy, srtp_rcc_mode_2, 1); + CHECK_OK(srtp_policy_validate(policy)); + srtp_policy_destroy(policy); + +#ifdef GCM + /* modes 1 and 2 are not defined for AEAD/GCM */ + create_gcm_rcc_policy(&policy, srtp_rcc_mode_1, 1); + CHECK_RETURN(srtp_policy_validate(policy), srtp_err_status_bad_param); + srtp_policy_destroy(policy); + + create_gcm_rcc_policy(&policy, srtp_rcc_mode_2, 1); + CHECK_RETURN(srtp_policy_validate(policy), srtp_err_status_bad_param); + srtp_policy_destroy(policy); +#endif +} + +/* + * Mode 3 is the RFC 4771 NULL-MAC variant carried over AES-GCM (RFC 7714); it + * has no MAC of its own, so it is only meaningful with an AEAD/GCM profile. + * srtp_policy_validate() must reject it with an AES-CM profile and accept it + * with a GCM profile. + */ +static void rcc_validate_mode3_requires_gcm(void) +{ + srtp_policy_t policy; + + /* mode 3 (NULL-MAC) is only defined over AES-GCM */ + create_cm_rcc_policy(&policy, srtp_rcc_mode_3, 1); + CHECK_RETURN(srtp_policy_validate(policy), srtp_err_status_bad_param); + srtp_policy_destroy(policy); + +#ifdef GCM + create_gcm_rcc_policy(&policy, srtp_rcc_mode_3, 1); + CHECK_OK(srtp_policy_validate(policy)); + srtp_policy_destroy(policy); +#endif +} + +/* + * AES-CM round trips (modes 1 and 2) + */ + +/* + * Mode 2, R == 1: every packet carries the ROC (TAG = ROC || MAC_tr), so this + * exercises the ROC-carrying path exclusively, over a long run of sequential + * packets. The complementary case, where only some packets carry the ROC and + * the rest fall back to the default full-length MAC, is covered by the R == 4 + * test below. All packets must decrypt back to the original payload. + */ +static void rcc_mode2_rate1_roundtrip(void) +{ + srtp_policy_t sp, rp; + srtp_t snd, rcv; + + CHECK_OK(srtp_init()); + create_cm_rcc_policy(&sp, srtp_rcc_mode_2, 1); + create_cm_rcc_policy(&rp, srtp_rcc_mode_2, 1); + CHECK_OK(srtp_create(&snd, sp)); + CHECK_OK(srtp_create(&rcv, rp)); + + for (uint16_t seq = 1; seq <= 50; seq++) { + rcc_roundtrip(snd, rcv, seq, "hello world"); } - /* ---- Test 4: GCM + RCC mode 2 rejected at create ---- - * Modes 1 and 2 embed the ROC in a truncated HMAC and are not defined for - * AEAD/GCM, so srtp_create() must reject them. (Mode 3 over GCM is the - * supported combination and is exercised by the GCM tests below.) - */ - { - srtp_t s; - srtp_policy_t p; - make_gcm_policy_rate(&p, srtp_rcc_mode_2, ssrc_specific, 1); - srtp_err_status_t st = srtp_create(&s, p); - int f = (st == srtp_err_status_ok) ? 1 : 0; - printf("Test4 GCM+RCC mode2 rejected: %s (status=%d)\n", - f ? "FAIL" : "PASS", st); - fails += f; - if (st == srtp_err_status_ok) - srtp_dealloc(s); + CHECK_OK(srtp_dealloc(snd)); + CHECK_OK(srtp_dealloc(rcv)); + srtp_policy_destroy(sp); + srtp_policy_destroy(rp); + CHECK_OK(srtp_shutdown()); +} + +/* + * Mode 1, R == 1: every packet carries the ROC (TAG = ROC || MAC_tr), so all + * packets are authenticated. This exercises the mode 1 carry path + * exclusively; the distinctive mode 1 behaviour where non-carry packets are + * sent completely untagged (no authentication) is covered by the R == 4 test + * below. All packets must round trip. + */ +static void rcc_mode1_rate1_roundtrip(void) +{ + srtp_policy_t sp, rp; + srtp_t snd, rcv; + + CHECK_OK(srtp_init()); + create_cm_rcc_policy(&sp, srtp_rcc_mode_1, 1); + create_cm_rcc_policy(&rp, srtp_rcc_mode_1, 1); + CHECK_OK(srtp_create(&snd, sp)); + CHECK_OK(srtp_create(&rcv, rp)); + + for (uint16_t seq = 1; seq <= 50; seq++) { + rcc_roundtrip(snd, rcv, seq, "mode one data"); } - /* ---- Test 5: mode 2, R=4 ---- - * Only seq % 4 == 0 carries the ROC (constant tag length); the other - * packets use the default full-length MAC computed over the local ROC. - * Walk a contiguous run starting at seq 0 so every packet type is hit. - */ - { - srtp_t snd, rcv; - srtp_policy_t sp, rp; - make_policy_rate(&sp, srtp_rcc_mode_2, ssrc_specific, 4); - make_policy_rate(&rp, srtp_rcc_mode_2, ssrc_specific, 4); - srtp_create(&snd, sp); - srtp_create(&rcv, rp); - int f = 0; - for (uint16_t seq = 0; seq <= 12; seq++) - f += roundtrip(snd, rcv, seq, "mode2 rate4 payload"); - printf("Test5 mode2 R=4 (carry + non-carry): %s\n", - f ? "FAIL" : "PASS"); - fails += f; - srtp_dealloc(snd); - srtp_dealloc(rcv); + CHECK_OK(srtp_dealloc(snd)); + CHECK_OK(srtp_dealloc(rcv)); + srtp_policy_destroy(sp); + srtp_policy_destroy(rp); + CHECK_OK(srtp_shutdown()); +} + +/* + * Mode 2, R == 4: this is the mode 2 test that exercises the non-carry branch. + * Walking a contiguous run starting at sequence number 0 hits both packet + * types: packets with seq % 4 == 0 carry the ROC (a constant-length tag), + * while the other three out of four use the default full-length MAC computed + * over the locally maintained ROC. All must round trip. + */ +static void rcc_mode2_rate4_carry_and_noncarry(void) +{ + srtp_policy_t sp, rp; + srtp_t snd, rcv; + + CHECK_OK(srtp_init()); + create_cm_rcc_policy(&sp, srtp_rcc_mode_2, 4); + create_cm_rcc_policy(&rp, srtp_rcc_mode_2, 4); + CHECK_OK(srtp_create(&snd, sp)); + CHECK_OK(srtp_create(&rcv, rp)); + + /* seq % 4 == 0 carries the ROC; the rest use the default full-length MAC */ + for (uint16_t seq = 0; seq <= 12; seq++) { + rcc_roundtrip(snd, rcv, seq, "mode2 rate4 payload"); } - /* ---- Test 6: mode 1, R=4 ---- - * Carry packets (seq % 4 == 0) get TAG = ROC || MAC_tr; the other packets - * carry no tag at all (variable packet length, no authentication). - */ - { - srtp_t snd, rcv; - srtp_policy_t sp, rp; - make_policy_rate(&sp, srtp_rcc_mode_1, ssrc_specific, 4); - make_policy_rate(&rp, srtp_rcc_mode_1, ssrc_specific, 4); - srtp_create(&snd, sp); - srtp_create(&rcv, rp); - int f = 0; - for (uint16_t seq = 0; seq <= 12; seq++) - f += roundtrip(snd, rcv, seq, "mode1 rate4 payload"); - printf("Test6 mode1 R=4 (carry + untagged): %s\n", f ? "FAIL" : "PASS"); - fails += f; - srtp_dealloc(snd); - srtp_dealloc(rcv); + CHECK_OK(srtp_dealloc(snd)); + CHECK_OK(srtp_dealloc(rcv)); + srtp_policy_destroy(sp); + srtp_policy_destroy(rp); + CHECK_OK(srtp_shutdown()); +} + +/* + * Mode 1, R == 4: this is the mode 1 test that exercises the untagged branch. + * Carry packets (seq % 4 == 0) get TAG = ROC || MAC_tr, while the other three + * out of four carry no tag at all (variable packet length, no authentication). + * Both kinds must round trip. + */ +static void rcc_mode1_rate4_carry_and_untagged(void) +{ + srtp_policy_t sp, rp; + srtp_t snd, rcv; + + CHECK_OK(srtp_init()); + create_cm_rcc_policy(&sp, srtp_rcc_mode_1, 4); + create_cm_rcc_policy(&rp, srtp_rcc_mode_1, 4); + CHECK_OK(srtp_create(&snd, sp)); + CHECK_OK(srtp_create(&rcv, rp)); + + /* carry packets get TAG = ROC || MAC_tr; other packets carry no tag */ + for (uint16_t seq = 0; seq <= 12; seq++) { + rcc_roundtrip(snd, rcv, seq, "mode1 rate4 payload"); } - /* ---- Test 7: mode 2, R=4, late-joining receiver after a wrap ---- - * The sender advances its ROC to 1, then a fresh receiver joins. The next - * ROC-carrying packet (seq % 4 == 0) must resynchronize the receiver. + CHECK_OK(srtp_dealloc(snd)); + CHECK_OK(srtp_dealloc(rcv)); + srtp_policy_destroy(sp); + srtp_policy_destroy(rp); + CHECK_OK(srtp_shutdown()); +} + +/* + * Mode 2, R == 1, late-joining receiver. The sender first wraps its sequence + * number past 65535 so its ROC becomes 1. A brand-new receiver (ROC 0) then + * joins: because every packet carries the ROC at R == 1, the very first + * packet it sees must let it adopt the sender's ROC (RFC 4771 fast + * resynchronization) and decrypt successfully. + */ +static void rcc_mode2_late_join_roc_sync(void) +{ + srtp_policy_t sp, rp; + srtp_t snd, rcv; + uint8_t pkt[256], enc[256], dec[256]; + size_t len, enc_len, dec_len; + uint32_t sender_roc = 0, receiver_roc = 0; + + CHECK_OK(srtp_init()); + create_cm_rcc_policy(&sp, srtp_rcc_mode_2, 1); + CHECK_OK(srtp_create(&snd, sp)); + + /* push the sender's ROC to 1 */ + advance_sender_roc(snd); + CHECK_OK(srtp_stream_get_roc(snd, TEST_SSRC, &sender_roc)); + + /* a fresh receiver must synchronize via the in-band ROC (R == 1) */ + create_cm_rcc_policy(&rp, srtp_rcc_mode_2, 1); + CHECK_OK(srtp_create(&rcv, rp)); + + len = make_rtp(pkt, 5000, "late join payload"); + enc_len = sizeof(enc); + CHECK_OK(srtp_protect(snd, pkt, len, enc, &enc_len, 0)); + dec_len = sizeof(dec); + CHECK_OK(srtp_unprotect(rcv, enc, enc_len, dec, &dec_len)); + CHECK(dec_len == len); + CHECK_BUFFER_EQUAL(dec, pkt, len); + + CHECK_OK(srtp_stream_get_roc(rcv, TEST_SSRC, &receiver_roc)); + CHECK(receiver_roc == sender_roc); + + CHECK_OK(srtp_dealloc(snd)); + CHECK_OK(srtp_dealloc(rcv)); + srtp_policy_destroy(sp); + srtp_policy_destroy(rp); + CHECK_OK(srtp_shutdown()); +} + +/* + * Mode 2, R == 4, late-joining receiver. The sender advances its ROC to 1 + * (the ROC advances on the sequence-number wrap regardless of whether packets + * are ROC-carrying, so the shared helper's plain sequential walk is enough). + * A fresh receiver then joins and receives a ROC-carrying packet (seq 5000, + * and 5000 % 4 == 0), which must resynchronize it to the sender's ROC. + */ +static void rcc_mode2_rate4_late_join(void) +{ + srtp_policy_t sp, rp; + srtp_t snd, rcv; + uint8_t pkt[256], enc[256], dec[256]; + size_t len, enc_len, dec_len; + uint32_t sender_roc = 0, receiver_roc = 0; + + CHECK_OK(srtp_init()); + create_cm_rcc_policy(&sp, srtp_rcc_mode_2, 4); + CHECK_OK(srtp_create(&snd, sp)); + + /* push the sender's ROC to 1 */ + advance_sender_roc(snd); + CHECK_OK(srtp_stream_get_roc(snd, TEST_SSRC, &sender_roc)); + + create_cm_rcc_policy(&rp, srtp_rcc_mode_2, 4); + CHECK_OK(srtp_create(&rcv, rp)); + + /* + * 5000 % 4 == 0, so this is a ROC-carrying packet: the fresh receiver must + * adopt the sender's ROC from it (RFC 4771 fast resynchronization). */ - { - srtp_t snd; - srtp_policy_t sp; - make_policy_rate(&sp, srtp_rcc_mode_2, ssrc_specific, 4); - srtp_create(&snd, sp); - - uint8_t pkt[256], enc[256]; - size_t len, enc_len; - for (uint32_t i = 0; i < 70000; i += 4096) { - len = make_rtp(pkt, (uint16_t)(i & ~0x3u), "x"); /* keep carry */ - enc_len = sizeof(enc); - srtp_protect(snd, pkt, len, enc, &enc_len, 0); - } - uint32_t roc = 0; - srtp_stream_get_roc(snd, SSRC, &roc); - printf("Test7: sender ROC after wrap = %u\n", roc); - - srtp_t rcv; - srtp_policy_t rp; - make_policy_rate(&rp, srtp_rcc_mode_2, ssrc_specific, 4); - srtp_create(&rcv, rp); - - uint16_t seq = 5000; /* 5000 % 4 == 0 -> carry packet */ - len = make_rtp(pkt, seq, "late join r4 payload"); - enc_len = sizeof(enc); - srtp_protect(snd, pkt, len, enc, &enc_len, 0); - uint8_t dec[256]; - size_t dec_len = sizeof(dec); - srtp_err_status_t s = srtp_unprotect(rcv, enc, enc_len, dec, &dec_len); - int f = 0; - if (s) { - printf(" late-join unprotect failed: %d\n", s); - f = 1; - } else if (dec_len != len || memcmp(dec, pkt, len)) { - printf(" late-join payload mismatch\n"); - f = 1; - } - uint32_t rroc = 0; - srtp_stream_get_roc(rcv, SSRC, &rroc); - if (rroc != roc) { - printf(" receiver ROC=%u != sender ROC=%u\n", rroc, roc); - f = 1; - } - printf("Test7 mode2 R=4 late-join ROC sync: %s\n", f ? "FAIL" : "PASS"); - fails += f; - srtp_dealloc(snd); - srtp_dealloc(rcv); - } + len = make_rtp(pkt, 5000, "late join r4 payload"); + enc_len = sizeof(enc); + CHECK_OK(srtp_protect(snd, pkt, len, enc, &enc_len, 0)); + dec_len = sizeof(dec); + CHECK_OK(srtp_unprotect(rcv, enc, enc_len, dec, &dec_len)); + CHECK(dec_len == len); + CHECK_BUFFER_EQUAL(dec, pkt, len); + + CHECK_OK(srtp_stream_get_roc(rcv, TEST_SSRC, &receiver_roc)); + CHECK(receiver_roc == sender_roc); + + CHECK_OK(srtp_dealloc(snd)); + CHECK_OK(srtp_dealloc(rcv)); + srtp_policy_destroy(sp); + srtp_policy_destroy(rp); + CHECK_OK(srtp_shutdown()); +} #ifdef GCM - /* ---- Test 8: GCM + RCC mode 3 accepted at create ---- */ - { - srtp_t s; - srtp_policy_t p; - make_gcm_policy_rate(&p, srtp_rcc_mode_3, ssrc_specific, 1); - srtp_err_status_t st = srtp_create(&s, p); - int f = (st == srtp_err_status_ok) ? 0 : 1; - printf("Test8 GCM+RCC mode3 accepted: %s (status=%d)\n", - f ? "FAIL" : "PASS", st); - fails += f; - if (st == srtp_err_status_ok) - srtp_dealloc(s); - } +/* + * AES-GCM round trips (mode 3, RFC 7714 layout) + */ - /* ---- Test 9: GCM mode 3, basic round trip (R=1, every packet carries - * the ROC in the SRTP auth tag field per RFC 7714 section 8.2) ---- */ - { - srtp_t snd, rcv; - srtp_policy_t sp, rp; - make_gcm_policy_rate(&sp, srtp_rcc_mode_3, ssrc_specific, 1); - make_gcm_policy_rate(&rp, srtp_rcc_mode_3, ssrc_specific, 1); - srtp_create(&snd, sp); - srtp_create(&rcv, rp); - int f = 0; - for (uint16_t seq = 1; seq <= 5; seq++) - f += roundtrip(snd, rcv, seq, "gcm mode3 payload"); - printf("Test9 GCM mode3 basic round trip: %s\n", f ? "FAIL" : "PASS"); - fails += f; - srtp_dealloc(snd); - srtp_dealloc(rcv); - } +/* + * Modes 1 and 2 embed the ROC in a truncated HMAC and are not defined for + * AEAD/GCM. Setting mode 2 on a GCM policy succeeds (the setter does not know + * the cipher yet), but srtp_create() validates the policy and must reject the + * GCM/mode-2 combination. (Mode 3 over GCM is the supported pairing and is + * exercised by the tests below.) + */ +static void rcc_gcm_mode2_rejected_at_create(void) +{ + srtp_policy_t p; + srtp_t s; + + CHECK_OK(srtp_init()); + /* setting the mode succeeds; the GCM/mode-2 conflict is caught at create */ + create_gcm_rcc_policy(&p, srtp_rcc_mode_2, 1); + CHECK_RETURN(srtp_create(&s, p), srtp_err_status_bad_param); + srtp_policy_destroy(p); + CHECK_OK(srtp_shutdown()); +} - /* ---- Test 10: GCM mode 3, R=4 (carry and non-carry packets) ---- - * Only seq % 4 == 0 carries the 4-octet ROC in the SRTP auth tag field; - * the other packets are plain RFC 7714 GCM packets. Both must round-trip. - */ - { - srtp_t snd, rcv; - srtp_policy_t sp, rp; - make_gcm_policy_rate(&sp, srtp_rcc_mode_3, ssrc_specific, 4); - make_gcm_policy_rate(&rp, srtp_rcc_mode_3, ssrc_specific, 4); - srtp_create(&snd, sp); - srtp_create(&rcv, rp); - int f = 0; - for (uint16_t seq = 0; seq <= 12; seq++) - f += roundtrip(snd, rcv, seq, "gcm mode3 rate4 payload"); - printf("Test10 GCM mode3 R=4 (carry + non-carry): %s\n", - f ? "FAIL" : "PASS"); - fails += f; - srtp_dealloc(snd); - srtp_dealloc(rcv); +/* + * Mode 3, R == 1: every packet carries the 4-octet ROC in the SRTP + * authentication tag field (RFC 7714 section 8.2). A basic round trip over + * several packets must succeed. + */ +static void rcc_gcm_mode3_basic_roundtrip(void) +{ + srtp_policy_t sp, rp; + srtp_t snd, rcv; + + CHECK_OK(srtp_init()); + create_gcm_rcc_policy(&sp, srtp_rcc_mode_3, 1); + create_gcm_rcc_policy(&rp, srtp_rcc_mode_3, 1); + CHECK_OK(srtp_create(&snd, sp)); + CHECK_OK(srtp_create(&rcv, rp)); + + for (uint16_t seq = 1; seq <= 5; seq++) { + rcc_roundtrip(snd, rcv, seq, "gcm mode3 payload"); } - /* ---- Test 11: GCM mode 3, verify the ROC is carried in the SRTP auth - * tag field (the last 4 octets, after the GCM tag and the optional MKI per - * RFC 7714 section 8.2), and that a fresh receiver resynchronizes from the - * in-band ROC ---- - */ - { - srtp_t snd; - srtp_policy_t sp; - make_gcm_policy_rate(&sp, srtp_rcc_mode_3, ssrc_specific, 1); - srtp_create(&snd, sp); - - uint8_t pkt[256], enc[256]; - size_t len, enc_len; - - /* advance the sender's ROC to 1 by walking the sequence number */ - for (uint32_t i = 0; i < 70000; i += 4096) { - len = make_rtp(pkt, (uint16_t)i, "x"); - enc_len = sizeof(enc); - srtp_protect(snd, pkt, len, enc, &enc_len, 0); - } - uint32_t roc = 0; - srtp_stream_get_roc(snd, SSRC, &roc); - printf("Test11: sender ROC after wrap = %u\n", roc); - - uint16_t seq = 5000; - len = make_rtp(pkt, seq, "gcm late join"); - enc_len = sizeof(enc); - srtp_protect(snd, pkt, len, enc, &enc_len, 0); - - /* expected layout (no MKI): header + ciphertext + 16-octet GCM tag - * + 4-octet ROC. The last four octets are the SRTP auth tag field - * carrying the sender's ROC in network order (RFC 7714 section 8.2). - */ - int f = 0; - uint32_t carried = 0; - memcpy(&carried, enc + enc_len - 4, 4); - carried = ntohl(carried); - if (carried != roc) { - printf(" carried ROC=%u != sender ROC=%u\n", carried, roc); - f = 1; - } - if (enc_len != len + 16 + 4) { - printf(" unexpected enc_len=%zu (exp=%zu)\n", enc_len, - len + 16 + 4); - f = 1; - } - - /* a fresh receiver must sync via the in-band ROC */ - srtp_t rcv; - srtp_policy_t rp; - make_gcm_policy_rate(&rp, srtp_rcc_mode_3, ssrc_specific, 1); - srtp_create(&rcv, rp); - uint8_t dec[256]; - size_t dec_len = sizeof(dec); - srtp_err_status_t s = srtp_unprotect(rcv, enc, enc_len, dec, &dec_len); - if (s) { - printf(" late-join unprotect failed: %d\n", s); - f = 1; - } else if (dec_len != len || memcmp(dec, pkt, len)) { - printf(" late-join payload mismatch\n"); - f = 1; - } - uint32_t rroc = 0; - srtp_stream_get_roc(rcv, SSRC, &rroc); - if (rroc != roc) { - printf(" receiver ROC=%u != sender ROC=%u\n", rroc, roc); - f = 1; - } - printf("Test11 GCM mode3 ROC-after-tag + late-join sync: %s\n", - f ? "FAIL" : "PASS"); - fails += f; - srtp_dealloc(snd); - srtp_dealloc(rcv); + CHECK_OK(srtp_dealloc(snd)); + CHECK_OK(srtp_dealloc(rcv)); + srtp_policy_destroy(sp); + srtp_policy_destroy(rp); + CHECK_OK(srtp_shutdown()); +} + +/* + * Mode 3, R == 4: only packets with seq % 4 == 0 carry the 4-octet ROC after + * the GCM tag; the others are plain RFC 7714 GCM packets. Both packet types + * must round trip. + */ +static void rcc_gcm_mode3_rate4(void) +{ + srtp_policy_t sp, rp; + srtp_t snd, rcv; + + CHECK_OK(srtp_init()); + create_gcm_rcc_policy(&sp, srtp_rcc_mode_3, 4); + create_gcm_rcc_policy(&rp, srtp_rcc_mode_3, 4); + CHECK_OK(srtp_create(&snd, sp)); + CHECK_OK(srtp_create(&rcv, rp)); + + /* seq % 4 == 0 carries the ROC after the GCM tag; others are plain 7714 */ + for (uint16_t seq = 0; seq <= 12; seq++) { + rcc_roundtrip(snd, rcv, seq, "gcm mode3 rate4 payload"); } - /* ---- Test 12: GCM mode 3, tampering with the carried ROC is detected ---- - * Because the ROC feeds the GCM IV, flipping a ROC bit yields a wrong IV - * and GCM tag verification must fail (implicit ROC integrity). + CHECK_OK(srtp_dealloc(snd)); + CHECK_OK(srtp_dealloc(rcv)); + srtp_policy_destroy(sp); + srtp_policy_destroy(rp); + CHECK_OK(srtp_shutdown()); +} + +/* + * Mode 3: verify the on-the-wire field order and late-join resynchronization. + * After advancing the sender's ROC to 1, a protected packet must have the + * layout header + ciphertext + 16-octet GCM tag + 4-octet ROC, with the + * trailing four octets carrying the sender's ROC in network order (RFC 7714 + * section 8.2). A fresh receiver must then adopt that in-band ROC and decrypt + * the packet successfully. + */ +static void rcc_gcm_mode3_roc_after_tag_and_late_join(void) +{ + srtp_policy_t sp, rp; + srtp_t snd, rcv; + uint8_t pkt[256], enc[256], dec[256]; + size_t len, enc_len, dec_len; + uint32_t sender_roc = 0, receiver_roc = 0, carried = 0; + + CHECK_OK(srtp_init()); + create_gcm_rcc_policy(&sp, srtp_rcc_mode_3, 1); + CHECK_OK(srtp_create(&snd, sp)); + + advance_sender_roc(snd); + CHECK_OK(srtp_stream_get_roc(snd, TEST_SSRC, &sender_roc)); + + len = make_rtp(pkt, 5000, "gcm late join"); + enc_len = sizeof(enc); + CHECK_OK(srtp_protect(snd, pkt, len, enc, &enc_len, 0)); + + /* + * expected layout (no MKI): header + ciphertext + 16-octet GCM tag + + * 4-octet ROC. The trailing four octets are the SRTP authentication tag + * field carrying the sender's ROC in network order (RFC 7714 section 8.2). */ - { - srtp_t snd, rcv; - srtp_policy_t sp, rp; - make_gcm_policy_rate(&sp, srtp_rcc_mode_3, ssrc_specific, 1); - make_gcm_policy_rate(&rp, srtp_rcc_mode_3, ssrc_specific, 1); - srtp_create(&snd, sp); - srtp_create(&rcv, rp); - - uint8_t pkt[256], enc[256], dec[256]; - size_t len = make_rtp(pkt, 100, "tamper test"); - size_t enc_len = sizeof(enc); - srtp_protect(snd, pkt, len, enc, &enc_len, 0); - - /* flip a bit in the carried ROC (last 4 octets) */ - enc[enc_len - 1] ^= 0x01; - - size_t dec_len = sizeof(dec); - srtp_err_status_t s = srtp_unprotect(rcv, enc, enc_len, dec, &dec_len); - int f = (s == srtp_err_status_ok) ? 1 : 0; - printf("Test12 GCM mode3 ROC tamper detected: %s (status=%d)\n", - f ? "FAIL" : "PASS", s); - fails += f; - srtp_dealloc(snd); - srtp_dealloc(rcv); - } + CHECK(enc_len == len + 16 + 4); + memcpy(&carried, enc + enc_len - 4, 4); + carried = ntohl(carried); + CHECK(carried == sender_roc); + + /* a fresh receiver must synchronize via the in-band ROC */ + create_gcm_rcc_policy(&rp, srtp_rcc_mode_3, 1); + CHECK_OK(srtp_create(&rcv, rp)); + dec_len = sizeof(dec); + CHECK_OK(srtp_unprotect(rcv, enc, enc_len, dec, &dec_len)); + CHECK(dec_len == len); + CHECK_BUFFER_EQUAL(dec, pkt, len); + + CHECK_OK(srtp_stream_get_roc(rcv, TEST_SSRC, &receiver_roc)); + CHECK(receiver_roc == sender_roc); + + CHECK_OK(srtp_dealloc(snd)); + CHECK_OK(srtp_dealloc(rcv)); + srtp_policy_destroy(sp); + srtp_policy_destroy(rp); + CHECK_OK(srtp_shutdown()); +} - /* ---- Test 13: GCM mode 3 with MKI, verifying the RFC 7714 section 8.2 - * field order: ciphertext (incl. GCM tag), then SRTP MKI, then the SRTP - * authentication tag field (carrying the ROC). The MKI must be located - * correctly even though the ROC follows it, and the packet must round - * trip. +/* + * Mode 3: the carried ROC is implicitly authenticated because it feeds the GCM + * IV. Flipping a bit in the trailing ROC yields a wrong IV, so GCM tag + * verification must fail and srtp_unprotect() must return auth_fail. + */ +static void rcc_gcm_mode3_roc_tamper_detected(void) +{ + srtp_policy_t sp, rp; + srtp_t snd, rcv; + uint8_t pkt[256], enc[256], dec[256]; + size_t len, enc_len, dec_len; + + CHECK_OK(srtp_init()); + create_gcm_rcc_policy(&sp, srtp_rcc_mode_3, 1); + create_gcm_rcc_policy(&rp, srtp_rcc_mode_3, 1); + CHECK_OK(srtp_create(&snd, sp)); + CHECK_OK(srtp_create(&rcv, rp)); + + len = make_rtp(pkt, 100, "tamper test"); + enc_len = sizeof(enc); + CHECK_OK(srtp_protect(snd, pkt, len, enc, &enc_len, 0)); + + /* + * flip a bit in the carried ROC (last 4 octets). The ROC feeds the GCM + * IV, so a tampered ROC yields a wrong IV and GCM tag verification fails. */ - { - static uint8_t mki_id[4] = { 0xde, 0xad, 0xbe, 0xef }; - - srtp_policy_t sp, rp; - srtp_ssrc_t ssrc = { ssrc_specific, SSRC }; - srtp_policy_create(&sp); - srtp_policy_set_profile(sp, srtp_profile_aead_aes_128_gcm); - srtp_policy_set_sec_serv(sp, sec_serv_conf_and_auth, - sec_serv_conf_and_auth); - srtp_policy_set_ssrc(sp, ssrc); - srtp_policy_set_rcc_mode_tx_rate(sp, srtp_rcc_mode_3, 1); - srtp_policy_set_window_size(sp, 128); - srtp_policy_use_mki(sp, sizeof(mki_id)); - srtp_policy_add_key(sp, gcm_key, SRTP_AES_128_KEY_LEN, - gcm_key + SRTP_AES_128_KEY_LEN, SRTP_AEAD_SALT_LEN, - mki_id, sizeof(mki_id)); - srtp_policy_clone(sp, &rp); - - srtp_t snd, rcv; - srtp_err_status_t cs = srtp_create(&snd, sp); - srtp_err_status_t cr = srtp_create(&rcv, rp); - int f = 0; - if (cs || cr) { - printf(" create with MKI failed: snd=%d rcv=%d\n", cs, cr); - f = 1; - } else { - uint8_t pkt[256], enc[256], dec[256]; - size_t len = make_rtp(pkt, 42, "gcm mode3 mki payload"); - size_t enc_len = sizeof(enc); - srtp_err_status_t s = srtp_protect(snd, pkt, len, enc, &enc_len, 0); - if (s) { - printf(" protect failed: %d\n", s); - f = 1; - } - - /* layout: header + cipher + GCM tag (16) + MKI (4) + ROC (4) */ - if (!f && enc_len != len + 16 + 4 + 4) { - printf(" unexpected enc_len=%zu (exp=%zu)\n", enc_len, - len + 16 + 4 + 4); - f = 1; - } - /* MKI must sit immediately before the trailing 4-octet ROC */ - if (!f && memcmp(enc + enc_len - 4 - 4, mki_id, 4) != 0) { - printf(" MKI not found before ROC\n"); - f = 1; - } - size_t dec_len = sizeof(dec); - if (!f) { - s = srtp_unprotect(rcv, enc, enc_len, dec, &dec_len); - if (s) { - printf(" unprotect failed: %d\n", s); - f = 1; - } else if (dec_len != len || memcmp(dec, pkt, len)) { - printf(" payload mismatch\n"); - f = 1; - } - } - } - printf("Test13 GCM mode3 with MKI (RFC 7714 field order): %s\n", - f ? "FAIL" : "PASS"); - fails += f; - if (!cs) - srtp_dealloc(snd); - if (!cr) - srtp_dealloc(rcv); - } -#endif /* GCM */ + enc[enc_len - 1] ^= 0x01; + + dec_len = sizeof(dec); + CHECK_RETURN(srtp_unprotect(rcv, enc, enc_len, dec, &dec_len), + srtp_err_status_auth_fail); - srtp_shutdown(); - printf("\n%s\n", fails ? "SOME TESTS FAILED" : "ALL TESTS PASSED"); - return fails ? 1 : 0; + CHECK_OK(srtp_dealloc(snd)); + CHECK_OK(srtp_dealloc(rcv)); + srtp_policy_destroy(sp); + srtp_policy_destroy(rp); + CHECK_OK(srtp_shutdown()); } + +/* + * Mode 3 with MKI: verify the RFC 7714 section 8.2 field order, which places + * the ROC after the optional MKI: header + ciphertext (incl. GCM tag) + MKI + + * 4-octet ROC. Even though the ROC follows the MKI, the receiver must still + * locate the MKI correctly and the packet must round trip. + */ +static void rcc_gcm_mode3_with_mki_field_order(void) +{ + srtp_policy_t sp, rp; + srtp_t snd, rcv; + uint8_t pkt[256], enc[256], dec[256]; + size_t len, enc_len, dec_len; + + CHECK_OK(srtp_init()); + + CHECK_OK(srtp_policy_create(&sp)); + CHECK_OK(srtp_policy_set_profile(sp, srtp_profile_aead_aes_128_gcm)); + CHECK_OK(srtp_policy_set_sec_serv(sp, sec_serv_conf_and_auth, + sec_serv_conf_and_auth)); + CHECK_OK( + srtp_policy_set_ssrc(sp, (srtp_ssrc_t){ ssrc_specific, TEST_SSRC })); + CHECK_OK(srtp_policy_set_rcc_mode_tx_rate(sp, srtp_rcc_mode_3, 1)); + CHECK_OK(srtp_policy_set_window_size(sp, 128)); + CHECK_OK(srtp_policy_use_mki(sp, sizeof(mki4))); + CHECK_OK(srtp_policy_add_key(sp, gcm_master_key, sizeof(gcm_master_key), + gcm_master_salt, sizeof(gcm_master_salt), mki4, + sizeof(mki4))); + CHECK_OK(srtp_policy_clone(sp, &rp)); + + CHECK_OK(srtp_create(&snd, sp)); + CHECK_OK(srtp_create(&rcv, rp)); + + len = make_rtp(pkt, 42, "gcm mode3 mki payload"); + enc_len = sizeof(enc); + CHECK_OK(srtp_protect(snd, pkt, len, enc, &enc_len, 0)); + + /* layout: header + ciphertext + GCM tag (16) + MKI (4) + ROC (4) */ + CHECK(enc_len == len + 16 + 4 + 4); + /* the MKI must sit immediately before the trailing 4-octet ROC */ + CHECK_BUFFER_EQUAL(enc + enc_len - 4 - 4, mki4, sizeof(mki4)); + + dec_len = sizeof(dec); + CHECK_OK(srtp_unprotect(rcv, enc, enc_len, dec, &dec_len)); + CHECK(dec_len == len); + CHECK_BUFFER_EQUAL(dec, pkt, len); + + CHECK_OK(srtp_dealloc(snd)); + CHECK_OK(srtp_dealloc(rcv)); + srtp_policy_destroy(sp); + srtp_policy_destroy(rp); + CHECK_OK(srtp_shutdown()); +} +#endif /* GCM */ + +TEST_LIST = { + { "rcc_set_mode_rejects_zero_rate()", rcc_set_mode_rejects_zero_rate }, + { "rcc_set_mode_rejects_invalid_mode()", + rcc_set_mode_rejects_invalid_mode }, + { "rcc_validate_modes_1_2_require_aes_cm()", + rcc_validate_modes_1_2_require_aes_cm }, + { "rcc_validate_mode3_requires_gcm()", rcc_validate_mode3_requires_gcm }, + { "rcc_mode2_rate1_roundtrip()", rcc_mode2_rate1_roundtrip }, + { "rcc_mode1_rate1_roundtrip()", rcc_mode1_rate1_roundtrip }, + { "rcc_mode2_rate4_carry_and_noncarry()", + rcc_mode2_rate4_carry_and_noncarry }, + { "rcc_mode1_rate4_carry_and_untagged()", + rcc_mode1_rate4_carry_and_untagged }, + { "rcc_mode2_late_join_roc_sync()", rcc_mode2_late_join_roc_sync }, + { "rcc_mode2_rate4_late_join()", rcc_mode2_rate4_late_join }, +#ifdef GCM + { "rcc_gcm_mode2_rejected_at_create()", rcc_gcm_mode2_rejected_at_create }, + { "rcc_gcm_mode3_basic_roundtrip()", rcc_gcm_mode3_basic_roundtrip }, + { "rcc_gcm_mode3_rate4()", rcc_gcm_mode3_rate4 }, + { "rcc_gcm_mode3_roc_after_tag_and_late_join()", + rcc_gcm_mode3_roc_after_tag_and_late_join }, + { "rcc_gcm_mode3_roc_tamper_detected()", + rcc_gcm_mode3_roc_tamper_detected }, + { "rcc_gcm_mode3_with_mki_field_order()", + rcc_gcm_mode3_with_mki_field_order }, +#endif + { 0 } +}; From c1be3fb2503ae3114712111fb0158468b0a59062 Mon Sep 17 00:00:00 2001 From: Volodymyr Snitko Date: Fri, 10 Jul 2026 08:44:53 +0200 Subject: [PATCH 4/6] rcc_test: fixed compilation with certain configurations --- test/rcc_test.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/test/rcc_test.c b/test/rcc_test.c index b38b06b58..4f137063f 100644 --- a/test/rcc_test.c +++ b/test/rcc_test.c @@ -46,6 +46,12 @@ #include "config.h" #endif +#ifdef HAVE_NETINET_IN_H +#include +#elif defined(HAVE_WINSOCK2_H) +#include +#endif + #include "cutest.h" #include "srtp.h" @@ -53,12 +59,6 @@ #include -#ifdef HAVE_NETINET_IN_H -#include -#elif defined(HAVE_WINSOCK2_H) -#include -#endif - #define TEST_SSRC 0xcafebabe static const uint8_t cm_master_key[16] = { @@ -70,6 +70,7 @@ static const uint8_t cm_master_salt[14] = { 0xeb, 0xb6, 0x96, 0x0b, 0x3a, 0xab, 0xe6, }; +#ifdef GCM static const uint8_t gcm_master_key[16] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, @@ -79,6 +80,7 @@ static const uint8_t gcm_master_salt[12] = { }; static const uint8_t mki4[4] = { 0xde, 0xad, 0xbe, 0xef }; +#endif static void create_cm_rcc_policy(srtp_policy_t *policy, srtp_rcc_mode_t mode, From 5acfeadd7a99ac414ff5b3b1db5ea7ef3d84f6c1 Mon Sep 17 00:00:00 2001 From: Volodymyr Snitko Date: Mon, 17 Aug 2026 19:58:12 +0200 Subject: [PATCH 5/6] Adopted further review feedback --- srtp/srtp.c | 782 +++++++++--------------------------------------- test/rcc_test.c | 305 ++++++++++++++++++- 2 files changed, 440 insertions(+), 647 deletions(-) diff --git a/srtp/srtp.c b/srtp/srtp.c index a30446543..7f27cffb0 100644 --- a/srtp/srtp.c +++ b/srtp/srtp.c @@ -2071,566 +2071,6 @@ static srtp_err_status_t srtp_get_est_pkt_index(const srtp_hdr_t *hdr, return result; } -/* - * srtp_protect_rcc() implements the RFC 4771 Roll-over Counter Carrying (RCC) - * integrity transform (modes 1 and 2) for sending. It is dispatched to from - * srtp_protect() when stream->rcc_mode is not srtp_rcc_mode_none. - * - * For a packet whose RTP sequence number is congruent to 0 modulo the - * transmission rate R (a "ROC-carrying" packet) the authentication tag is - * built as TAG = ROC(4 octets, network order) || MAC_tr, where MAC_tr is the - * (tag_len - 4) most significant octets of HMAC-SHA1(auth_key, - * authenticated_portion || ROC). For other packets: - * - mode 1: no MAC is computed and no tag is appended; - * - mode 2: the default integrity transform is applied (full tag_len MAC). - * - * RCC is only defined here for the AES-CM ciphers with HMAC-SHA1; GCM streams - * are rejected at stream initialization time. - */ -static srtp_err_status_t srtp_protect_rcc(srtp_t ctx, - srtp_stream_ctx_t *stream, - const uint8_t *rtp, - size_t rtp_len, - uint8_t *srtp, - size_t *srtp_len, - srtp_session_keys_t *session_keys) -{ - const srtp_hdr_t *hdr = (const srtp_hdr_t *)rtp; - size_t enc_start; /* offset to start of encrypted portion */ - uint8_t *auth_start; /* pointer to start of auth. portion */ - size_t enc_octet_len = 0; /* number of octets in encrypted portion */ - srtp_xtd_seq_num_t est; /* estimated xtd_seq_num_t of *hdr */ - ssize_t delta; /* delta of local pkt idx and that in hdr */ - uint8_t *auth_tag = NULL; /* location of auth_tag within packet */ - srtp_err_status_t status; - size_t tag_len; - size_t prefix_len; - bool rcc_carry; /* whether this packet carries the ROC */ - size_t rcc_tag_len; /* number of tag octets actually appended */ - - debug_print0(mod_srtp, "function srtp_protect_rcc"); - - /* - * This handler implements only the AES-CM/HMAC RCC modes (1 and 2). - * Mode 3 (AEAD) is handled by srtp_protect_aead, and srtp_rcc_mode_none - * streams never dispatch here. Reject anything else instead of silently - * applying the wrong transform. - */ - if (stream->rcc_mode != srtp_rcc_mode_1 && - stream->rcc_mode != srtp_rcc_mode_2) { - return srtp_err_status_bad_param; - } - - /* RFC 4771: a packet carries the ROC when seq is 0 modulo R */ - rcc_carry = (ntohs(hdr->seq) % stream->roc_tx_rate) == 0; - - /* - * update the key usage limit, and check it to make sure that we - * didn't just hit either the soft limit or the hard limit - */ - switch (srtp_key_limit_update(session_keys->limit)) { - case srtp_key_event_normal: - break; - case srtp_key_event_soft_limit: - srtp_handle_event(ctx, stream, event_key_soft_limit); - break; - case srtp_key_event_hard_limit: - srtp_handle_event(ctx, stream, event_key_hard_limit); - return srtp_err_status_key_expired; - default: - break; - } - - /* get tag length from stream */ - tag_len = srtp_auth_get_tag_length(session_keys->rtp_auth); - - /* - * in mode 1, packets that do not carry the ROC are not integrity - * protected and carry no authentication tag at all - */ - if (stream->rcc_mode == srtp_rcc_mode_1 && !rcc_carry) { - rcc_tag_len = 0; - } else { - rcc_tag_len = tag_len; - } - - /* - * find starting point for encryption and length of data to be - * encrypted - the encrypted portion starts after the rtp header - * extension, if present; otherwise, it starts after the last csrc, - * if any are present - */ - enc_start = srtp_get_rtp_hdr_len(hdr); - if (hdr->x == 1) { - enc_start += srtp_get_rtp_hdr_xtnd_len(hdr, rtp); - } - - bool cryptex_inuse, cryptex_inplace; - status = srtp_cryptex_protect_init(stream, hdr, rtp, srtp, &cryptex_inuse, - &cryptex_inplace, &enc_start); - if (status) { - return status; - } - - if (enc_start > rtp_len) { - return srtp_err_status_parse_err; - } - enc_octet_len = rtp_len - enc_start; - - /* check output length */ - if (*srtp_len < rtp_len + stream->mki_size + rcc_tag_len) { - return srtp_err_status_buffer_small; - } - - /* if not-inplace then need to copy full rtp header */ - if (rtp != srtp) { - memcpy(srtp, rtp, enc_start); - } - - if (stream->use_mki) { - srtp_inject_mki(srtp + rtp_len, session_keys, stream->mki_size); - } - - /* - * if we're providing authentication, set the auth_start and auth_tag - * pointers to the proper locations; otherwise, set auth_start to NULL - */ - if ((stream->rtp_services & sec_serv_auth) && rcc_tag_len > 0) { - auth_start = srtp; - auth_tag = srtp + rtp_len + stream->mki_size; - } else { - auth_start = NULL; - auth_tag = NULL; - } - - /* - * estimate the packet index using the start of the replay window - * and the sequence number from the header - */ - status = srtp_get_est_pkt_index(hdr, stream, &est, &delta); - - if (status && (status != srtp_err_status_pkt_idx_adv)) { - return status; - } - - if (status == srtp_err_status_pkt_idx_adv) { - srtp_rdbx_set_roc_seq(&stream->rtp_rdbx, (uint32_t)(est >> 16), - (uint16_t)(est & 0xFFFF)); - stream->pending_roc = 0; - srtp_rdbx_add_index(&stream->rtp_rdbx, 0); - } else { - status = srtp_rdbx_check(&stream->rtp_rdbx, delta); - if (status) { - if (status != srtp_err_status_replay_fail || - !stream->allow_repeat_tx) - return status; /* we've been asked to reuse an index */ - } - srtp_rdbx_add_index(&stream->rtp_rdbx, delta); - } - - debug_print(mod_srtp, "estimated packet index: %016" PRIx64, est); - - /* set the AES counter-mode nonce and sequence */ - { - v128_t iv; - - iv.v32[0] = 0; - iv.v32[1] = hdr->ssrc; - iv.v64[1] = be64_to_cpu(est << 16); - status = srtp_cipher_set_iv(session_keys->rtp_cipher, (uint8_t *)&iv, - srtp_direction_encrypt); - if (!status && session_keys->rtp_xtn_hdr_cipher) { - status = srtp_cipher_set_iv(session_keys->rtp_xtn_hdr_cipher, - (uint8_t *)&iv, srtp_direction_encrypt); - } - if (status) { - return srtp_err_status_cipher_fail; - } - } - - /* shift est, put into network byte order */ - est = be64_to_cpu(est << 16); - - /* - * if we're authenticating using a universal hash, put the keystream - * prefix into the authentication tag - */ - if (auth_start) { - prefix_len = srtp_auth_get_prefix_length(session_keys->rtp_auth); - if (prefix_len) { - status = srtp_cipher_output(session_keys->rtp_cipher, auth_tag, - &prefix_len); - if (status) { - return srtp_err_status_cipher_fail; - } - } - } - - if (hdr->x == 1 && session_keys->rtp_xtn_hdr_cipher) { - /* extensions header encryption RFC 6904 */ - status = srtp_process_header_encryption( - stream, srtp_get_rtp_xtn_hdr(hdr, srtp), session_keys); - if (status) { - return status; - } - } - - if (cryptex_inuse) { - status = srtp_cryptex_protect(cryptex_inplace, hdr, srtp, - session_keys->rtp_cipher); - if (status) { - return status; - } - } - - /* if we're encrypting, exor keystream into the message */ - if (stream->rtp_services & sec_serv_conf) { - status = srtp_cipher_encrypt(session_keys->rtp_cipher, rtp + enc_start, - enc_octet_len, srtp + enc_start, - &enc_octet_len); - if (status) { - return srtp_err_status_cipher_fail; - } - } else if (rtp != srtp) { - /* if no encryption and not-inplace then need to copy rest of packet */ - memcpy(srtp + enc_start, rtp + enc_start, enc_octet_len); - } - - if (cryptex_inuse) { - srtp_cryptex_protect_cleanup(cryptex_inplace, hdr, srtp); - } - - /* - * if we're authenticating, run the authentication function and build - * the RFC 4771 tag - */ - if (auth_start) { - status = srtp_auth_start(session_keys->rtp_auth); - if (status) { - return status; - } - - status = srtp_auth_update(session_keys->rtp_auth, auth_start, rtp_len); - if (status) { - return status; - } - - if (rcc_carry) { - /* TAG = ROC (4 octets, network order) || MAC_tr */ - uint8_t mac[SRTP_MAX_TAG_LEN]; - status = srtp_auth_compute(session_keys->rtp_auth, (uint8_t *)&est, - 4, mac); - if (status) { - return status; - } - memcpy(auth_tag, (uint8_t *)&est, 4); - memcpy(auth_tag + 4, mac, tag_len - 4); - } else { - /* default integrity transform (mode 2, non-ROC packets) */ - status = srtp_auth_compute(session_keys->rtp_auth, (uint8_t *)&est, - 4, auth_tag); - if (status) { - return status; - } - } - } - - *srtp_len = enc_start + enc_octet_len; - - /* increase the packet length by the length of the auth tag (if any) */ - *srtp_len += rcc_tag_len; - - /* increase the packet length by the mki size if used */ - *srtp_len += stream->mki_size; - - return srtp_err_status_ok; -} - -/* - * srtp_unprotect_rcc() implements the RFC 4771 Roll-over Counter Carrying - * (RCC) integrity transform (modes 1 and 2) for receiving. It is dispatched - * to from srtp_unprotect() when stream->rcc_mode is not srtp_rcc_mode_none. - * - * For a ROC-carrying packet (seq congruent to 0 modulo R) the sender's ROC is - * read from the first four octets of the tag and used both to build the - * decryption IV and to compute the MAC; the remaining (tag_len - 4) octets of - * the tag are the truncated MAC and are verified. On success the receiver - * adopts the sender's ROC, providing fast and robust resynchronization. - * - * For packets that do not carry the ROC, mode 1 performs no authentication - * (no tag is present), while mode 2 applies the default integrity transform - * using the locally maintained ROC. - */ -static srtp_err_status_t srtp_unprotect_rcc(srtp_t ctx, - srtp_stream_ctx_t *stream, - const uint8_t *srtp, - size_t srtp_len, - uint8_t *rtp, - size_t *rtp_len, - srtp_session_keys_t *session_keys) -{ - const srtp_hdr_t *hdr = (const srtp_hdr_t *)srtp; - size_t enc_start; - const uint8_t *auth_start; - size_t enc_octet_len = 0; - const uint8_t *auth_tag = NULL; - srtp_xtd_seq_num_t est; - ssize_t delta = 0; - v128_t iv; - srtp_err_status_t status; - uint8_t tmp_tag[SRTP_MAX_TAG_LEN]; - size_t tag_len, prefix_len; - uint16_t seq; - bool rcc_carry; - size_t rcc_tag_len; - uint32_t roc_sender = 0; - bool advance_packet_index = false; - uint32_t roc_to_set = 0; - uint16_t seq_to_set = 0; - - debug_print0(mod_srtp, "function srtp_unprotect_rcc"); - - /* - * This handler implements only the AES-CM/HMAC RCC modes (1 and 2). - * Mode 3 (AEAD) is handled by srtp_unprotect_aead, and srtp_rcc_mode_none - * streams never dispatch here. Reject anything else instead of silently - * applying the wrong transform. - */ - if (stream->rcc_mode != srtp_rcc_mode_1 && - stream->rcc_mode != srtp_rcc_mode_2) { - return srtp_err_status_bad_param; - } - - seq = ntohs(hdr->seq); - rcc_carry = (seq % stream->roc_tx_rate) == 0; - - /* get tag length from stream */ - tag_len = srtp_auth_get_tag_length(session_keys->rtp_auth); - - /* - * in mode 1 packets that do not carry the ROC have no tag at all; in - * every other case the full tag_len octets are present - */ - if (stream->rcc_mode == srtp_rcc_mode_1 && !rcc_carry) { - rcc_tag_len = 0; - } else { - rcc_tag_len = tag_len; - } - - /* - * determine the packet index. For ROC-carrying packets the sender's ROC - * is taken directly from the tag (RFC 4771); otherwise it is estimated - * from the local replay database as in the default transform. - */ - if (rcc_carry) { - if (srtp_len < octets_in_rtp_header + stream->mki_size + tag_len) { - return srtp_err_status_bad_param; - } - /* the ROC is the first four octets of the tag, in network order */ - memcpy(&roc_sender, srtp + srtp_len - tag_len, 4); - roc_sender = ntohl(roc_sender); - est = (((srtp_xtd_seq_num_t)roc_sender) << 16) | seq; - } else { - status = srtp_get_est_pkt_index(hdr, stream, &est, &delta); - if (status && (status != srtp_err_status_pkt_idx_adv)) { - return status; - } - if (status == srtp_err_status_pkt_idx_adv) { - advance_packet_index = true; - roc_to_set = (uint32_t)(est >> 16); - seq_to_set = (uint16_t)(est & 0xFFFF); - } else { - status = srtp_rdbx_check(&stream->rtp_rdbx, delta); - if (status) { - return status; - } - } - } - - debug_print(mod_srtp, "estimated u_packet index: %016" PRIx64, est); - - /* set the AES counter-mode IV from the (possibly sender-supplied) index */ - iv.v32[0] = 0; - iv.v32[1] = hdr->ssrc; /* still in network order */ - iv.v64[1] = be64_to_cpu(est << 16); - status = srtp_cipher_set_iv(session_keys->rtp_cipher, (uint8_t *)&iv, - srtp_direction_decrypt); - if (!status && session_keys->rtp_xtn_hdr_cipher) { - status = srtp_cipher_set_iv(session_keys->rtp_xtn_hdr_cipher, - (uint8_t *)&iv, srtp_direction_decrypt); - } - if (status) { - return srtp_err_status_cipher_fail; - } - - /* shift est, put into network byte order */ - est = be64_to_cpu(est << 16); - - enc_start = srtp_get_rtp_hdr_len(hdr); - if (hdr->x == 1) { - enc_start += srtp_get_rtp_hdr_xtnd_len(hdr, srtp); - } - - bool cryptex_inuse, cryptex_inplace; - status = srtp_cryptex_unprotect_init(stream, hdr, srtp, rtp, &cryptex_inuse, - &cryptex_inplace, &enc_start); - if (status) { - return status; - } - - if (enc_start > srtp_len - rcc_tag_len - stream->mki_size) { - return srtp_err_status_parse_err; - } - enc_octet_len = srtp_len - enc_start - stream->mki_size - rcc_tag_len; - - /* check output length */ - if (*rtp_len < srtp_len - stream->mki_size - rcc_tag_len) { - return srtp_err_status_buffer_small; - } - - /* if not-inplace then need to copy full rtp header */ - if (srtp != rtp) { - memcpy(rtp, srtp, enc_start); - } - - if ((stream->rtp_services & sec_serv_auth) && rcc_tag_len > 0) { - auth_start = srtp; - /* the tag (ROC and/or MAC) is located at the end of the packet */ - auth_tag = srtp + srtp_len - tag_len; - } else { - auth_start = NULL; - auth_tag = NULL; - } - - /* - * if we expect message authentication, run the authentication function - * and compare the result with the value of the tag - */ - if (auth_start) { - if (session_keys->rtp_auth->prefix_len != 0) { - prefix_len = srtp_auth_get_prefix_length(session_keys->rtp_auth); - status = srtp_cipher_output(session_keys->rtp_cipher, tmp_tag, - &prefix_len); - if (status) { - return srtp_err_status_cipher_fail; - } - } - - status = srtp_auth_start(session_keys->rtp_auth); - if (status) { - return status; - } - - /* the MAC covers the packet up to the start of the tag */ - status = srtp_auth_update(session_keys->rtp_auth, auth_start, - srtp_len - tag_len - stream->mki_size); - if (status) { - return status; - } - - status = srtp_auth_compute(session_keys->rtp_auth, (uint8_t *)&est, 4, - tmp_tag); - if (status) { - return srtp_err_status_auth_fail; - } - - if (rcc_carry) { - /* MAC_tr occupies the (tag_len - 4) octets after the ROC */ - if (!srtp_octet_string_equal(tmp_tag, auth_tag + 4, tag_len - 4)) { - return srtp_err_status_auth_fail; - } - } else { - if (!srtp_octet_string_equal(tmp_tag, auth_tag, tag_len)) { - return srtp_err_status_auth_fail; - } - } - } - - /* - * update the key usage limit, and check it to make sure that we - * didn't just hit either the soft limit or the hard limit - */ - switch (srtp_key_limit_update(session_keys->limit)) { - case srtp_key_event_normal: - break; - case srtp_key_event_soft_limit: - srtp_handle_event(ctx, stream, event_key_soft_limit); - break; - case srtp_key_event_hard_limit: - srtp_handle_event(ctx, stream, event_key_hard_limit); - return srtp_err_status_key_expired; - default: - break; - } - - if (hdr->x == 1 && session_keys->rtp_xtn_hdr_cipher) { - /* extensions header encryption RFC 6904 */ - status = srtp_process_header_encryption( - stream, srtp_get_rtp_xtn_hdr(hdr, rtp), session_keys); - if (status) { - return status; - } - } - - if (cryptex_inuse) { - status = srtp_cryptex_unprotect(cryptex_inplace, hdr, rtp, - session_keys->rtp_cipher); - if (status) { - return status; - } - } - - /* if we're decrypting, add keystream into ciphertext */ - if (stream->rtp_services & sec_serv_conf) { - status = - srtp_cipher_decrypt(session_keys->rtp_cipher, srtp + enc_start, - enc_octet_len, rtp + enc_start, &enc_octet_len); - if (status) { - return srtp_err_status_cipher_fail; - } - } else if (rtp != srtp) { - /* if no encryption and not-inplace then need to copy rest of packet */ - memcpy(rtp + enc_start, srtp + enc_start, enc_octet_len); - } - - if (cryptex_inuse) { - srtp_cryptex_unprotect_cleanup(cryptex_inplace, hdr, rtp); - } - - /* - * verify that stream is for received traffic - this check will detect - * SSRC collisions - */ - if (stream->direction != dir_srtp_receiver) { - if (stream->direction == dir_unknown) { - stream->direction = dir_srtp_receiver; - } else { - srtp_handle_event(ctx, stream, event_ssrc_collision); - } - } - - /* - * the authentication passed (or was not required), so update the replay - * database and roll-over counter - */ - if (rcc_carry) { - /* adopt the sender's ROC (RFC 4771 fast resynchronization) */ - srtp_rdbx_set_roc_seq(&stream->rtp_rdbx, roc_sender, seq); - stream->pending_roc = 0; - srtp_rdbx_add_index(&stream->rtp_rdbx, 0); - } else if (advance_packet_index) { - srtp_rdbx_set_roc_seq(&stream->rtp_rdbx, roc_to_set, seq_to_set); - stream->pending_roc = 0; - srtp_rdbx_add_index(&stream->rtp_rdbx, 0); - } else { - srtp_rdbx_add_index(&stream->rtp_rdbx, delta); - } - - *rtp_len = srtp_len - stream->mki_size - rcc_tag_len; - - return srtp_err_status_ok; -} - /* * This function handles outgoing SRTP packets while in AEAD mode, * which currently supports AES-GCM encryption. All packets are @@ -2871,7 +2311,6 @@ static srtp_err_status_t srtp_unprotect_aead(srtp_ctx_t *ctx, size_t tag_len; size_t aad_len; size_t rcc_extra = 0; /* octets of ROC carried (RFC 4771 mode 3) */ - bool rcc_adopt_roc = false; /* whether to adopt the sender's ROC */ uint32_t rcc_roc_sender = 0; /* ROC read from a ROC-carrying packet */ debug_print0(mod_srtp, "function srtp_unprotect_aead"); @@ -2903,10 +2342,26 @@ static srtp_err_status_t srtp_unprotect_aead(srtp_ctx_t *ctx, /* the ROC is the last field, after the optional MKI */ memcpy(&rcc_roc_sender, srtp + srtp_len - rcc_extra, 4); rcc_roc_sender = ntohl(rcc_roc_sender); - est = (((srtp_xtd_seq_num_t)rcc_roc_sender) << 16) | seq; - delta = 0; - advance_packet_index = false; - rcc_adopt_roc = true; + + /* + * The carried ROC still has to pass replay detection: accept it + * only if it advances the index, and when it lands inside the + * current window record it there rather than resetting it. + */ + status = srtp_estimate_index(&stream->rtp_rdbx, rcc_roc_sender, + &est, seq, &delta); + if (status && (status != srtp_err_status_pkt_idx_adv)) { + return status; + } + if (status == srtp_err_status_pkt_idx_adv) { + advance_packet_index = true; + } else { + advance_packet_index = false; + status = srtp_rdbx_check(&stream->rtp_rdbx, delta); + if (status) { + return status; + } + } } else { status = srtp_get_est_pkt_index(hdr, stream, &est, &delta); if (status && (status != srtp_err_status_pkt_idx_adv)) { @@ -3102,13 +2557,7 @@ static srtp_err_status_t srtp_unprotect_aead(srtp_ctx_t *ctx, * the message authentication function passed, so add the packet * index into the replay database */ - if (rcc_adopt_roc) { - /* RFC 4771: adopt the sender's ROC for fast resynchronization */ - srtp_rdbx_set_roc_seq(&stream->rtp_rdbx, rcc_roc_sender, - (uint16_t)(est & 0xFFFF)); - stream->pending_roc = 0; - srtp_rdbx_add_index(&stream->rtp_rdbx, 0); - } else if (advance_packet_index) { + if (advance_packet_index) { uint32_t roc_to_set = (uint32_t)(est >> 16); uint16_t seq_to_set = (uint16_t)(est & 0xFFFF); srtp_rdbx_set_roc_seq(&stream->rtp_rdbx, roc_to_set, seq_to_set); @@ -3142,6 +2591,8 @@ srtp_err_status_t srtp_protect(srtp_t ctx, srtp_stream_ctx_t *stream; size_t prefix_len; srtp_session_keys_t *session_keys = NULL; + bool rcc_carry; /* whether this packet carries the ROC (RFC 4771) */ + size_t rcc_tag_len; /* number of tag octets actually appended */ debug_print0(mod_srtp, "function srtp_protect"); @@ -3223,15 +2674,6 @@ srtp_err_status_t srtp_protect(srtp_t ctx, session_keys); } - /* - * If RFC 4771 RCC is enabled for this stream, dispatch to the RCC - * handler which carries the ROC inside the authentication tag. - */ - if (stream->rcc_mode != srtp_rcc_mode_none) { - return srtp_protect_rcc(ctx, stream, rtp, rtp_len, srtp, srtp_len, - session_keys); - } - /* * update the key usage limit, and check it to make sure that we * didn't just hit either the soft limit or the hard limit, and call @@ -3253,8 +2695,19 @@ srtp_err_status_t srtp_protect(srtp_t ctx, /* get tag length from stream */ tag_len = srtp_auth_get_tag_length(session_keys->rtp_auth); + /* + * RFC 4771 RCC (modes 1 and 2, AES-CM only): a packet carries the ROC + * when its sequence number is 0 modulo R, and in mode 1 the packets that + * do not carry it are sent with no tag at all, so the number of appended + * tag octets varies per packet. + */ + rcc_carry = stream->rcc_mode != srtp_rcc_mode_none && + (ntohs(hdr->seq) % stream->roc_tx_rate) == 0; + rcc_tag_len = + (stream->rcc_mode == srtp_rcc_mode_1 && !rcc_carry) ? 0 : tag_len; + /* check output length */ - if (*srtp_len < rtp_len + stream->mki_size + tag_len) { + if (*srtp_len < rtp_len + stream->mki_size + rcc_tag_len) { return srtp_err_status_buffer_small; } @@ -3295,7 +2748,7 @@ srtp_err_status_t srtp_protect(srtp_t ctx, * pointers to the proper locations; otherwise, set auth_start to NULL * to indicate that no authentication is needed */ - if (stream->rtp_services & sec_serv_auth) { + if ((stream->rtp_services & sec_serv_auth) && rcc_tag_len > 0) { auth_start = srtp; auth_tag = srtp + rtp_len + stream->mki_size; } else { @@ -3439,19 +2892,31 @@ srtp_err_status_t srtp_protect(srtp_t ctx, /* run auth func over ROC, put result into auth_tag */ debug_print(mod_srtp, "estimated packet index: %016" PRIx64, est); - status = srtp_auth_compute(session_keys->rtp_auth, (uint8_t *)&est, 4, - auth_tag); + if (rcc_carry) { + /* RFC 4771: TAG = ROC (4 octets, network order) || MAC_tr */ + uint8_t mac[SRTP_MAX_TAG_LEN]; + status = srtp_auth_compute(session_keys->rtp_auth, (uint8_t *)&est, + 4, mac); + if (status) { + return status; + } + memcpy(auth_tag, (uint8_t *)&est, 4); + memcpy(auth_tag + 4, mac, tag_len - 4); + } else { + status = srtp_auth_compute(session_keys->rtp_auth, (uint8_t *)&est, + 4, auth_tag); + if (status) { + return status; + } + } debug_print(mod_srtp, "srtp auth tag: %s", srtp_octet_string_hex_string(auth_tag, tag_len)); - if (status) { - return status; - } } *srtp_len = enc_start + enc_octet_len; /* increase the packet length by the length of the auth tag */ - *srtp_len += tag_len; + *srtp_len += rcc_tag_len; /* increate the packet length by the mki size if used */ *srtp_len += stream->mki_size; @@ -3481,6 +2946,10 @@ srtp_err_status_t srtp_unprotect(srtp_t ctx, bool advance_packet_index = false; uint32_t roc_to_set = 0; uint16_t seq_to_set = 0; + bool from_template = false; /* packet matched the wildcard template */ + bool rcc_carry; /* packet carries the ROC (RFC 4771) */ + size_t rcc_tag_len; /* number of tag octets actually present */ + uint32_t roc_sender = 0; /* ROC read from a ROC-carrying packet */ debug_print0(mod_srtp, "function srtp_unprotect"); @@ -3508,13 +2977,7 @@ srtp_err_status_t srtp_unprotect(srtp_t ctx, stream = ctx->stream_template; debug_print(mod_srtp, "using provisional stream (SSRC: 0x%08x)", (unsigned int)ntohl(hdr->ssrc)); - - /* - * set estimated packet index to sequence number from header, - * and set delta equal to the same value - */ - est = (srtp_xtd_seq_num_t)ntohs(hdr->seq); - delta = (int)est; + from_template = true; } else { /* * no stream corresponding to SSRC found, and we don't do @@ -3522,35 +2985,43 @@ srtp_err_status_t srtp_unprotect(srtp_t ctx, */ return srtp_err_status_no_ctx; } - } else { + } + + /* + * RFC 4771 RCC: a packet whose sequence number is congruent to 0 modulo R + * carries the sender's ROC. Reading it requires the tag length, so for + * those packets the index is determined further below once the session + * keys are known; estimating it here could reject a packet from a + * receiver that is not yet synchronized. + */ + rcc_carry = stream->rcc_mode != srtp_rcc_mode_none && + (ntohs(hdr->seq) % stream->roc_tx_rate) == 0; + + if (from_template || rcc_carry || stream->rcc_mode == srtp_rcc_mode_3) { /* - * For RFC 4771 RCC streams the packet index (and the replay check for - * ROC-carrying packets) is handled inside srtp_unprotect_rcc(), which - * may use the sender-supplied ROC. Estimating it here could reject a - * packet from a receiver that is not yet synchronized, so skip it. + * set estimated packet index to sequence number from header, + * and set delta equal to the same value */ - if (stream->rcc_mode != srtp_rcc_mode_none) { - est = (srtp_xtd_seq_num_t)ntohs(hdr->seq); - delta = (int)est; - } else { - status = srtp_get_est_pkt_index(hdr, stream, &est, &delta); + est = (srtp_xtd_seq_num_t)ntohs(hdr->seq); + delta = (int)est; + } else { + status = srtp_get_est_pkt_index(hdr, stream, &est, &delta); - if (status && (status != srtp_err_status_pkt_idx_adv)) { - return status; - } + if (status && (status != srtp_err_status_pkt_idx_adv)) { + return status; + } - if (status == srtp_err_status_pkt_idx_adv) { - advance_packet_index = true; - roc_to_set = (uint32_t)(est >> 16); - seq_to_set = (uint16_t)(est & 0xFFFF); - } + if (status == srtp_err_status_pkt_idx_adv) { + advance_packet_index = true; + roc_to_set = (uint32_t)(est >> 16); + seq_to_set = (uint16_t)(est & 0xFFFF); + } - /* check replay database */ - if (!advance_packet_index) { - status = srtp_rdbx_check(&stream->rtp_rdbx, delta); - if (status) { - return status; - } + /* check replay database */ + if (!advance_packet_index) { + status = srtp_rdbx_check(&stream->rtp_rdbx, delta); + if (status) { + return status; } } } @@ -3566,8 +3037,7 @@ srtp_err_status_t srtp_unprotect(srtp_t ctx, */ { size_t mki_lookup_len = srtp_len; - if (stream->rcc_mode == srtp_rcc_mode_3 && - (ntohs(hdr->seq) % stream->roc_tx_rate) == 0 && + if (stream->rcc_mode == srtp_rcc_mode_3 && rcc_carry && srtp_len >= octets_in_rtp_header + 4) { mki_lookup_len -= 4; } @@ -3588,18 +3058,50 @@ srtp_err_status_t srtp_unprotect(srtp_t ctx, rtp_len, session_keys, advance_packet_index); } + /* get tag length from stream */ + tag_len = srtp_auth_get_tag_length(session_keys->rtp_auth); + /* - * If RFC 4771 RCC is enabled for this stream, dispatch to the RCC handler - * which reads the sender's ROC from the authentication tag. + * RFC 4771 RCC (modes 1 and 2, AES-CM only): in mode 1 the packets that + * do not carry the ROC have no tag at all. On a ROC-carrying packet the + * sender's ROC is the first four octets of the tag and replaces the + * locally estimated index, which is what lets an unsynchronized receiver + * (including one still using the wildcard template stream) decrypt. */ - if (stream->rcc_mode != srtp_rcc_mode_none && - stream != ctx->stream_template) { - return srtp_unprotect_rcc(ctx, stream, srtp, srtp_len, rtp, rtp_len, - session_keys); - } + rcc_tag_len = + (stream->rcc_mode == srtp_rcc_mode_1 && !rcc_carry) ? 0 : tag_len; - /* get tag length from stream */ - tag_len = srtp_auth_get_tag_length(session_keys->rtp_auth); + if (rcc_carry) { + if (srtp_len < octets_in_rtp_header + stream->mki_size + tag_len) { + return srtp_err_status_bad_param; + } + memcpy(&roc_sender, srtp + srtp_len - tag_len, 4); + roc_sender = ntohl(roc_sender); + + /* + * The carried ROC is authenticated (it is fed into the MAC), but it + * must still pass replay detection: accept it only if it advances the + * index, and when it lands inside the current window record it there + * rather than resetting the window. + */ + status = srtp_estimate_index(&stream->rtp_rdbx, roc_sender, &est, + ntohs(hdr->seq), &delta); + if (status && (status != srtp_err_status_pkt_idx_adv)) { + return status; + } + if (status == srtp_err_status_pkt_idx_adv) { + advance_packet_index = true; + roc_to_set = roc_sender; + seq_to_set = ntohs(hdr->seq); + } else { + advance_packet_index = false; + status = srtp_rdbx_check(&stream->rtp_rdbx, delta); + if (status) { + return status; + } + } + debug_print(mod_srtp, "carried u_packet index: %016" PRIx64, est); + } /* * set the cipher's IV properly, depending on whatever cipher we @@ -3648,14 +3150,14 @@ srtp_err_status_t srtp_unprotect(srtp_t ctx, return status; } - if (tag_len + stream->mki_size > srtp_len || - enc_start > srtp_len - tag_len - stream->mki_size) { + if (rcc_tag_len + stream->mki_size > srtp_len || + enc_start > srtp_len - rcc_tag_len - stream->mki_size) { return srtp_err_status_parse_err; } - enc_octet_len = srtp_len - enc_start - stream->mki_size - tag_len; + enc_octet_len = srtp_len - enc_start - stream->mki_size - rcc_tag_len; /* check output length */ - if (*rtp_len < srtp_len - stream->mki_size - tag_len) { + if (*rtp_len < srtp_len - stream->mki_size - rcc_tag_len) { return srtp_err_status_buffer_small; } @@ -3669,7 +3171,7 @@ srtp_err_status_t srtp_unprotect(srtp_t ctx, * pointers to the proper locations; otherwise, set auth_start to NULL * to indicate that no authentication is needed */ - if (stream->rtp_services & sec_serv_auth) { + if ((stream->rtp_services & sec_serv_auth) && rcc_tag_len > 0) { auth_start = srtp; auth_tag = srtp + srtp_len - tag_len; } else { @@ -3725,7 +3227,13 @@ srtp_err_status_t srtp_unprotect(srtp_t ctx, return srtp_err_status_auth_fail; } - if (!srtp_octet_string_equal(tmp_tag, auth_tag, tag_len)) { + if (rcc_carry) { + /* RFC 4771: MAC_tr occupies the (tag_len - 4) octets after the + * ROC */ + if (!srtp_octet_string_equal(tmp_tag, auth_tag + 4, tag_len - 4)) { + return srtp_err_status_auth_fail; + } + } else if (!srtp_octet_string_equal(tmp_tag, auth_tag, tag_len)) { return srtp_err_status_auth_fail; } } diff --git a/test/rcc_test.c b/test/rcc_test.c index 4f137063f..b9c35a609 100644 --- a/test/rcc_test.c +++ b/test/rcc_test.c @@ -82,16 +82,17 @@ static const uint8_t gcm_master_salt[12] = { static const uint8_t mki4[4] = { 0xde, 0xad, 0xbe, 0xef }; #endif -static void create_cm_rcc_policy(srtp_policy_t *policy, - srtp_rcc_mode_t mode, - uint16_t rate) +static void create_cm_rcc_policy_ssrc(srtp_policy_t *policy, + srtp_rcc_mode_t mode, + uint16_t rate, + srtp_ssrc_type_t ssrc_type) { CHECK_OK(srtp_policy_create(policy)); CHECK_OK(srtp_policy_set_profile(*policy, srtp_profile_aes128_cm_sha1_80)); CHECK_OK(srtp_policy_set_sec_serv(*policy, sec_serv_conf_and_auth, sec_serv_conf_and_auth)); - CHECK_OK(srtp_policy_set_ssrc(*policy, - (srtp_ssrc_t){ ssrc_specific, TEST_SSRC })); + CHECK_OK( + srtp_policy_set_ssrc(*policy, (srtp_ssrc_t){ ssrc_type, TEST_SSRC })); CHECK_OK(srtp_policy_set_rcc_mode_tx_rate(*policy, mode, rate)); CHECK_OK(srtp_policy_set_window_size(*policy, 128)); CHECK_OK(srtp_policy_add_key(*policy, cm_master_key, sizeof(cm_master_key), @@ -99,23 +100,38 @@ static void create_cm_rcc_policy(srtp_policy_t *policy, 0)); } +static void create_cm_rcc_policy(srtp_policy_t *policy, + srtp_rcc_mode_t mode, + uint16_t rate) +{ + create_cm_rcc_policy_ssrc(policy, mode, rate, ssrc_specific); +} + #ifdef GCM -static void create_gcm_rcc_policy(srtp_policy_t *policy, - srtp_rcc_mode_t mode, - uint16_t rate) +static void create_gcm_rcc_policy_ssrc(srtp_policy_t *policy, + srtp_rcc_mode_t mode, + uint16_t rate, + srtp_ssrc_type_t ssrc_type) { CHECK_OK(srtp_policy_create(policy)); CHECK_OK(srtp_policy_set_profile(*policy, srtp_profile_aead_aes_128_gcm)); CHECK_OK(srtp_policy_set_sec_serv(*policy, sec_serv_conf_and_auth, sec_serv_conf_and_auth)); - CHECK_OK(srtp_policy_set_ssrc(*policy, - (srtp_ssrc_t){ ssrc_specific, TEST_SSRC })); + CHECK_OK( + srtp_policy_set_ssrc(*policy, (srtp_ssrc_t){ ssrc_type, TEST_SSRC })); CHECK_OK(srtp_policy_set_rcc_mode_tx_rate(*policy, mode, rate)); CHECK_OK(srtp_policy_set_window_size(*policy, 128)); CHECK_OK(srtp_policy_add_key(*policy, gcm_master_key, sizeof(gcm_master_key), gcm_master_salt, sizeof(gcm_master_salt), NULL, 0)); } + +static void create_gcm_rcc_policy(srtp_policy_t *policy, + srtp_rcc_mode_t mode, + uint16_t rate) +{ + create_gcm_rcc_policy_ssrc(policy, mode, rate, ssrc_specific); +} #endif /* build an RTP packet with the given sequence number and a fixed payload */ @@ -476,6 +492,174 @@ static void rcc_mode2_rate4_late_join(void) CHECK_OK(srtp_shutdown()); } +/* + * Mode 2, R == 1, receiver using a wildcard ssrc_any_inbound policy. The + * receiver has no stream for the SSRC yet, so the first packet is processed + * against the provisional template stream; the RCC transform must still be + * applied there (the tag is ROC || MAC_tr, not a plain MAC) and, once it + * authenticates, the template must be instantiated into a real stream that + * has adopted the sender's ROC. The sender's ROC is advanced past a wrap + * first, so a receiver that fell back to the default transform (assuming + * ROC 0) would fail authentication. + */ +static void rcc_mode2_wildcard_inbound_late_join(void) +{ + srtp_policy_t sp, rp; + srtp_t snd, rcv; + uint8_t pkt[256], enc[256], dec[256]; + size_t len, enc_len, dec_len; + uint32_t sender_roc = 0, receiver_roc = 0; + + CHECK_OK(srtp_init()); + create_cm_rcc_policy(&sp, srtp_rcc_mode_2, 1); + CHECK_OK(srtp_create(&snd, sp)); + + advance_sender_roc(snd); + CHECK_OK(srtp_stream_get_roc(snd, TEST_SSRC, &sender_roc)); + + create_cm_rcc_policy_ssrc(&rp, srtp_rcc_mode_2, 1, ssrc_any_inbound); + CHECK_OK(srtp_create(&rcv, rp)); + + len = make_rtp(pkt, 5000, "wildcard inbound payload"); + enc_len = sizeof(enc); + CHECK_OK(srtp_protect(snd, pkt, len, enc, &enc_len, 0)); + dec_len = sizeof(dec); + CHECK_OK(srtp_unprotect(rcv, enc, enc_len, dec, &dec_len)); + CHECK(dec_len == len); + CHECK_BUFFER_EQUAL(dec, pkt, len); + + /* the template must have been instantiated with the sender's ROC */ + CHECK_OK(srtp_stream_get_roc(rcv, TEST_SSRC, &receiver_roc)); + CHECK(receiver_roc == sender_roc); + + /* subsequent packets are handled by the newly created stream */ + rcc_roundtrip(snd, rcv, 5001, "wildcard inbound follow up"); + + CHECK_OK(srtp_dealloc(snd)); + CHECK_OK(srtp_dealloc(rcv)); + srtp_policy_destroy(sp); + srtp_policy_destroy(rp); + CHECK_OK(srtp_shutdown()); +} + +/* + * Mode 1, R == 4, receiver using a wildcard ssrc_any_inbound policy. Mode 1 + * sends non-carry packets with no authentication tag at all, so the template + * stream must go through the RCC transform to parse them correctly. Starting + * at seq 0 exercises both the carry and the untagged packet through the + * provisional stream. + */ +static void rcc_mode1_wildcard_inbound(void) +{ + srtp_policy_t sp, rp; + srtp_t snd, rcv; + + CHECK_OK(srtp_init()); + create_cm_rcc_policy(&sp, srtp_rcc_mode_1, 4); + CHECK_OK(srtp_create(&snd, sp)); + create_cm_rcc_policy_ssrc(&rp, srtp_rcc_mode_1, 4, ssrc_any_inbound); + CHECK_OK(srtp_create(&rcv, rp)); + + for (uint16_t seq = 0; seq <= 8; seq++) { + rcc_roundtrip(snd, rcv, seq, "wildcard inbound mode1"); + } + + CHECK_OK(srtp_dealloc(snd)); + CHECK_OK(srtp_dealloc(rcv)); + srtp_policy_destroy(sp); + srtp_policy_destroy(rp); + CHECK_OK(srtp_shutdown()); +} + +/* + * Mode 2, R == 1: a ROC-carrying packet must not bypass replay detection. + * Every packet carries the ROC here, so replaying one that was already + * accepted has to be rejected rather than silently resetting the replay + * window (which would then let the whole tail of the stream be replayed). + */ +static void rcc_mode2_carry_replay_rejected(void) +{ + srtp_policy_t sp, rp; + srtp_t snd, rcv; + uint8_t pkt[256], enc[256], saved[256], dec[256]; + size_t len, enc_len, saved_len = 0, dec_len; + + CHECK_OK(srtp_init()); + create_cm_rcc_policy(&sp, srtp_rcc_mode_2, 1); + create_cm_rcc_policy(&rp, srtp_rcc_mode_2, 1); + CHECK_OK(srtp_create(&snd, sp)); + CHECK_OK(srtp_create(&rcv, rp)); + + /* keep a copy of the packet with seq 3 as it goes over the wire */ + for (uint16_t seq = 1; seq <= 5; seq++) { + len = make_rtp(pkt, seq, "replay me"); + enc_len = sizeof(enc); + CHECK_OK(srtp_protect(snd, pkt, len, enc, &enc_len, 0)); + if (seq == 3) { + memcpy(saved, enc, enc_len); + saved_len = enc_len; + } + dec_len = sizeof(dec); + CHECK_OK(srtp_unprotect(rcv, enc, enc_len, dec, &dec_len)); + } + + dec_len = sizeof(dec); + CHECK_RETURN(srtp_unprotect(rcv, saved, saved_len, dec, &dec_len), + srtp_err_status_replay_fail); + + CHECK_OK(srtp_dealloc(snd)); + CHECK_OK(srtp_dealloc(rcv)); + srtp_policy_destroy(sp); + srtp_policy_destroy(rp); + CHECK_OK(srtp_shutdown()); +} + +/* + * Mode 2, R == 1: a ROC-carrying packet that is new but falls inside the + * currently tracked window must only mark itself in the window, not reset it. + * Delivering 10, then the still-missing 8 and 9, must all succeed, and a + * second copy of 8 must then be rejected -- which only holds if accepting 8 + * and 9 left the window (and the already-set bits) intact. + */ +static void rcc_mode2_carry_out_of_order_keeps_window(void) +{ + srtp_policy_t sp, rp; + srtp_t snd, rcv; + uint8_t pkt[3][256], enc[3][256], dec[256]; + size_t len[3], enc_len[3], dec_len; + + CHECK_OK(srtp_init()); + create_cm_rcc_policy(&sp, srtp_rcc_mode_2, 1); + create_cm_rcc_policy(&rp, srtp_rcc_mode_2, 1); + CHECK_OK(srtp_create(&snd, sp)); + CHECK_OK(srtp_create(&rcv, rp)); + + for (uint16_t i = 0; i < 3; i++) { + len[i] = make_rtp(pkt[i], (uint16_t)(8 + i), "out of order"); + enc_len[i] = sizeof(enc[i]); + CHECK_OK(srtp_protect(snd, pkt[i], len[i], enc[i], &enc_len[i], 0)); + } + + /* deliver 10 first, then the earlier 8 and 9 that are still in flight */ + dec_len = sizeof(dec); + CHECK_OK(srtp_unprotect(rcv, enc[2], enc_len[2], dec, &dec_len)); + dec_len = sizeof(dec); + CHECK_OK(srtp_unprotect(rcv, enc[0], enc_len[0], dec, &dec_len)); + dec_len = sizeof(dec); + CHECK_OK(srtp_unprotect(rcv, enc[1], enc_len[1], dec, &dec_len)); + + /* the window must have been updated, not reset, so 8 is now a replay */ + dec_len = sizeof(dec); + CHECK_RETURN(srtp_unprotect(rcv, enc[0], enc_len[0], dec, &dec_len), + srtp_err_status_replay_fail); + + CHECK_OK(srtp_dealloc(snd)); + CHECK_OK(srtp_dealloc(rcv)); + srtp_policy_destroy(sp); + srtp_policy_destroy(rp); + CHECK_OK(srtp_shutdown()); +} + #ifdef GCM /* * AES-GCM round trips (mode 3, RFC 7714 layout) @@ -702,6 +886,97 @@ static void rcc_gcm_mode3_with_mki_field_order(void) srtp_policy_destroy(rp); CHECK_OK(srtp_shutdown()); } + +/* + * Mode 3, receiver using a wildcard ssrc_any_inbound policy. The AEAD path + * already instantiates the template once the GCM tag verifies; this pins that + * behaviour so the provisional stream keeps adopting the carried ROC. + */ +static void rcc_gcm_mode3_wildcard_inbound_late_join(void) +{ + srtp_policy_t sp, rp; + srtp_t snd, rcv; + uint8_t pkt[256], enc[256], dec[256]; + size_t len, enc_len, dec_len; + uint32_t sender_roc = 0, receiver_roc = 0; + + CHECK_OK(srtp_init()); + create_gcm_rcc_policy(&sp, srtp_rcc_mode_3, 1); + CHECK_OK(srtp_create(&snd, sp)); + + advance_sender_roc(snd); + CHECK_OK(srtp_stream_get_roc(snd, TEST_SSRC, &sender_roc)); + + create_gcm_rcc_policy_ssrc(&rp, srtp_rcc_mode_3, 1, ssrc_any_inbound); + CHECK_OK(srtp_create(&rcv, rp)); + + len = make_rtp(pkt, 5000, "gcm wildcard inbound"); + enc_len = sizeof(enc); + CHECK_OK(srtp_protect(snd, pkt, len, enc, &enc_len, 0)); + dec_len = sizeof(dec); + CHECK_OK(srtp_unprotect(rcv, enc, enc_len, dec, &dec_len)); + CHECK(dec_len == len); + CHECK_BUFFER_EQUAL(dec, pkt, len); + + /* the template must have been instantiated with the sender's ROC */ + CHECK_OK(srtp_stream_get_roc(rcv, TEST_SSRC, &receiver_roc)); + CHECK(receiver_roc == sender_roc); + + /* subsequent packets are handled by the newly created stream */ + rcc_roundtrip(snd, rcv, 5001, "gcm wildcard follow up"); + + CHECK_OK(srtp_dealloc(snd)); + CHECK_OK(srtp_dealloc(rcv)); + srtp_policy_destroy(sp); + srtp_policy_destroy(rp); + CHECK_OK(srtp_shutdown()); +} + +/* + * Mode 3, R == 1: the ROC carried after the GCM tag must not bypass replay + * detection either. A replayed ROC-carrying packet has to be rejected, and + * an out-of-order but still unseen packet must update the window rather than + * reset it. + */ +static void rcc_gcm_mode3_carry_replay_rejected(void) +{ + srtp_policy_t sp, rp; + srtp_t snd, rcv; + uint8_t pkt[3][256], enc[3][256], dec[256]; + size_t len[3], enc_len[3], dec_len; + + CHECK_OK(srtp_init()); + create_gcm_rcc_policy(&sp, srtp_rcc_mode_3, 1); + create_gcm_rcc_policy(&rp, srtp_rcc_mode_3, 1); + CHECK_OK(srtp_create(&snd, sp)); + CHECK_OK(srtp_create(&rcv, rp)); + + for (uint16_t i = 0; i < 3; i++) { + len[i] = make_rtp(pkt[i], (uint16_t)(8 + i), "gcm replay"); + enc_len[i] = sizeof(enc[i]); + CHECK_OK(srtp_protect(snd, pkt[i], len[i], enc[i], &enc_len[i], 0)); + } + + dec_len = sizeof(dec); + CHECK_OK(srtp_unprotect(rcv, enc[2], enc_len[2], dec, &dec_len)); + dec_len = sizeof(dec); + CHECK_OK(srtp_unprotect(rcv, enc[0], enc_len[0], dec, &dec_len)); + + /* replaying the packet just accepted must fail */ + dec_len = sizeof(dec); + CHECK_RETURN(srtp_unprotect(rcv, enc[0], enc_len[0], dec, &dec_len), + srtp_err_status_replay_fail); + + /* the still-unseen 9 must remain acceptable */ + dec_len = sizeof(dec); + CHECK_OK(srtp_unprotect(rcv, enc[1], enc_len[1], dec, &dec_len)); + + CHECK_OK(srtp_dealloc(snd)); + CHECK_OK(srtp_dealloc(rcv)); + srtp_policy_destroy(sp); + srtp_policy_destroy(rp); + CHECK_OK(srtp_shutdown()); +} #endif /* GCM */ TEST_LIST = { @@ -719,6 +994,12 @@ TEST_LIST = { rcc_mode1_rate4_carry_and_untagged }, { "rcc_mode2_late_join_roc_sync()", rcc_mode2_late_join_roc_sync }, { "rcc_mode2_rate4_late_join()", rcc_mode2_rate4_late_join }, + { "rcc_mode2_wildcard_inbound_late_join()", + rcc_mode2_wildcard_inbound_late_join }, + { "rcc_mode1_wildcard_inbound()", rcc_mode1_wildcard_inbound }, + { "rcc_mode2_carry_replay_rejected()", rcc_mode2_carry_replay_rejected }, + { "rcc_mode2_carry_out_of_order_keeps_window()", + rcc_mode2_carry_out_of_order_keeps_window }, #ifdef GCM { "rcc_gcm_mode2_rejected_at_create()", rcc_gcm_mode2_rejected_at_create }, { "rcc_gcm_mode3_basic_roundtrip()", rcc_gcm_mode3_basic_roundtrip }, @@ -729,6 +1010,10 @@ TEST_LIST = { rcc_gcm_mode3_roc_tamper_detected }, { "rcc_gcm_mode3_with_mki_field_order()", rcc_gcm_mode3_with_mki_field_order }, + { "rcc_gcm_mode3_wildcard_inbound_late_join()", + rcc_gcm_mode3_wildcard_inbound_late_join }, + { "rcc_gcm_mode3_carry_replay_rejected()", + rcc_gcm_mode3_carry_replay_rejected }, #endif { 0 } }; From 80f2a0b0d26a51b9ff29a8994ffc3a1e512f6450 Mon Sep 17 00:00:00 2001 From: Volodymyr Snitko Date: Fri, 28 Aug 2026 16:15:17 +0200 Subject: [PATCH 6/6] Addressed the remaining feedback --- include/srtp.h | 12 ++++++------ srtp/srtp.c | 24 ++++++++++++++---------- srtp/srtp_policy.c | 9 +++++---- test/rcc_test.c | 14 +++++++------- 4 files changed, 32 insertions(+), 27 deletions(-) diff --git a/include/srtp.h b/include/srtp.h index 564fdd117..29f3d4a7c 100644 --- a/include/srtp.h +++ b/include/srtp.h @@ -387,6 +387,9 @@ srtp_err_status_t srtp_policy_get_profile(srtp_policy_t policy, * - srtp_err_status_ok if flags were applied. * - srtp_err_status_bad_param if policy is NULL or profile is unset. */ +srtp_err_status_t srtp_policy_set_sec_serv(srtp_policy_t policy, + srtp_sec_serv_t rtp_sec_serv, + srtp_sec_serv_t rtcp_sec_serv); /** * @brief srtp_rcc_mode_t selects the RFC 4771 Roll-over Counter Carrying @@ -403,7 +406,8 @@ srtp_err_status_t srtp_policy_get_profile(srtp_policy_t policy, * for the AES-CM ciphers. Mode 3 is the RFC 4771 NULL-MAC variant: it * carries only the 4-octet ROC with no MAC of its own. Mode 3 is supported * here on top of AES-GCM (RFC 7714); the AEAD tag authenticates the packet - * and the ROC is appended immediately after the GCM tag. + * and the ROC occupies the SRTP authentication tag field, which RFC 7714 + * section 8.2 places at the end of the packet, after the optional MKI. */ typedef enum { srtp_rcc_mode_none = 0, /**< RCC disabled (default RFC 3711 transform). */ @@ -415,13 +419,9 @@ typedef enum { /**< default integrity transform. */ srtp_rcc_mode_3 = 3 /**< RFC 4771 mode 3: NULL-MAC, ROC only. */ /**< Supported with AES-GCM, where the ROC is */ - /**< appended after the GCM tag. */ + /**< the last field, after the optional MKI. */ } srtp_rcc_mode_t; -srtp_err_status_t srtp_policy_set_sec_serv(srtp_policy_t policy, - srtp_sec_serv_t rtp_sec_serv, - srtp_sec_serv_t rtcp_sec_serv); - /** * @brief Enable or disable MKI on the policy. * diff --git a/srtp/srtp.c b/srtp/srtp.c index 7182cb6f6..90545f963 100644 --- a/srtp/srtp.c +++ b/srtp/srtp.c @@ -2420,8 +2420,9 @@ static srtp_err_status_t srtp_unprotect_aead(srtp_ctx_t *ctx, } /* - * We pass the tag down to the cipher when doing GCM mode. Any ROC - * carried for RFC 4771 mode 3 sits after the tag and is excluded here. + * We pass the tag down to the cipher when doing GCM mode. Any ROC carried + * for RFC 4771 mode 3 is the last field, after the optional MKI, and is + * excluded here. */ enc_octet_len = srtp_len - enc_start - stream->mki_size - rcc_extra; @@ -2889,15 +2890,18 @@ srtp_err_status_t srtp_protect(srtp_t ctx, /* run auth func over ROC, put result into auth_tag */ debug_print(mod_srtp, "estimated packet index: %016" PRIx64, est); if (rcc_carry) { - /* RFC 4771: TAG = ROC (4 octets, network order) || MAC_tr */ - uint8_t mac[SRTP_MAX_TAG_LEN]; + /* + * RFC 4771: TAG = ROC (4 octets, network order) || MAC_tr, where + * MAC_tr is the leading (tag_len - 4) octets of the MAC, so the + * MAC is shifted right rather than partly overwritten. + */ status = srtp_auth_compute(session_keys->rtp_auth, (uint8_t *)&est, - 4, mac); + 4, auth_tag); if (status) { return status; } + memmove(auth_tag + 4, auth_tag, tag_len - 4); memcpy(auth_tag, (uint8_t *)&est, 4); - memcpy(auth_tag + 4, mac, tag_len - 4); } else { status = srtp_auth_compute(session_keys->rtp_auth, (uint8_t *)&est, 4, auth_tag); @@ -4923,10 +4927,10 @@ srtp_err_status_t stream_get_protect_trailer_length(srtp_stream_ctx_t *stream, *length += srtp_auth_get_tag_length(session_key->rtp_auth); /* * RFC 4771 mode 3 (AES-GCM): ROC-carrying packets append a 4-octet ROC - * after the authentication tag (RFC 7714 section 8.2). Report the - * worst case so callers size their buffers for ROC-carrying packets. - * Modes 1 and 2 (AES-CM) carry the ROC inside the existing HMAC tag and - * therefore add no extra trailer octets. + * as the last field, after the optional MKI (RFC 7714 section 8.2). + * Report the worst case so callers size their buffers for ROC-carrying + * packets. Modes 1 and 2 (AES-CM) carry the ROC inside the existing + * HMAC tag and therefore add no extra trailer octets. */ if (stream->rcc_mode == srtp_rcc_mode_3) { *length += 4; diff --git a/srtp/srtp_policy.c b/srtp/srtp_policy.c index 99435e82b..7106234e5 100644 --- a/srtp/srtp_policy.c +++ b/srtp/srtp_policy.c @@ -954,10 +954,11 @@ srtp_err_status_t srtp_policy_validate(srtp_policy_t policy) /* * Mode 3 (RFC 4771 NULL-MAC) carries only the 4-octet ROC with no * MAC of its own. It is supported here only on top of AES-GCM - * (RFC 7714): the AEAD tag authenticates the packet and the ROC is - * appended after the GCM tag. Because the carried ROC also feeds - * the GCM IV, any tampering with it is detected by GCM tag - * verification. + * (RFC 7714): the AEAD tag authenticates the packet and the ROC + * occupies the SRTP authentication tag field, which RFC 7714 + * section 8.2 places after the optional MKI. Because the carried + * ROC also feeds the GCM IV, any tampering with it is detected by + * GCM tag verification. */ if (!is_gcm) { return srtp_err_status_bad_param; diff --git a/test/rcc_test.c b/test/rcc_test.c index b9c35a609..b68370206 100644 --- a/test/rcc_test.c +++ b/test/rcc_test.c @@ -713,9 +713,9 @@ static void rcc_gcm_mode3_basic_roundtrip(void) } /* - * Mode 3, R == 4: only packets with seq % 4 == 0 carry the 4-octet ROC after - * the GCM tag; the others are plain RFC 7714 GCM packets. Both packet types - * must round trip. + * Mode 3, R == 4: only packets with seq % 4 == 0 carry the 4-octet ROC as the + * last field of the packet; the others are plain RFC 7714 GCM packets. Both + * packet types must round trip. */ static void rcc_gcm_mode3_rate4(void) { @@ -728,7 +728,7 @@ static void rcc_gcm_mode3_rate4(void) CHECK_OK(srtp_create(&snd, sp)); CHECK_OK(srtp_create(&rcv, rp)); - /* seq % 4 == 0 carries the ROC after the GCM tag; others are plain 7714 */ + /* seq % 4 == 0 carries the trailing ROC; others are plain RFC 7714 */ for (uint16_t seq = 0; seq <= 12; seq++) { rcc_roundtrip(snd, rcv, seq, "gcm mode3 rate4 payload"); } @@ -933,9 +933,9 @@ static void rcc_gcm_mode3_wildcard_inbound_late_join(void) } /* - * Mode 3, R == 1: the ROC carried after the GCM tag must not bypass replay - * detection either. A replayed ROC-carrying packet has to be rejected, and - * an out-of-order but still unseen packet must update the window rather than + * Mode 3, R == 1: the trailing carried ROC must not bypass replay detection + * either. A replayed ROC-carrying packet has to be rejected, and an + * out-of-order but still unseen packet must update the window rather than * reset it. */ static void rcc_gcm_mode3_carry_replay_rejected(void)