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/include/srtp.h b/include/srtp.h index 2faf8aaba..29f3d4a7c 100644 --- a/include/srtp.h +++ b/include/srtp.h @@ -391,6 +391,37 @@ 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 + * (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 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). */ + 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 */ + /**< the last field, after the optional MKI. */ +} srtp_rcc_mode_t; + /** * @brief Enable or disable MKI on the policy. * @@ -420,6 +451,31 @@ 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 0f1555798..90545f963 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; } @@ -1674,6 +1678,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 */ @@ -2085,6 +2093,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,8 +2118,21 @@ 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); + /* + * 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) { + if (*srtp_len < rtp_len + tag_len + stream->mki_size + rcc_extra) { return srtp_err_status_buffer_small; } @@ -2175,6 +2198,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 +2258,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 +2310,8 @@ 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) */ + uint32_t rcc_roc_sender = 0; /* ROC read from a ROC-carrying packet */ debug_print0(mod_srtp, "function srtp_unprotect_aead"); @@ -2282,6 +2320,65 @@ 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); + + /* + * 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)) { + 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 +2415,16 @@ 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 is the last field, after the optional MKI, 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 +2436,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; } @@ -2484,6 +2583,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"); @@ -2591,8 +2692,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; } @@ -2633,7 +2745,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 { @@ -2777,19 +2889,34 @@ 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, 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, auth_tag); + if (status) { + return status; + } + memmove(auth_tag + 4, auth_tag, tag_len - 4); + memcpy(auth_tag, (uint8_t *)&est, 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; @@ -2819,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"); @@ -2850,13 +2981,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 @@ -2864,6 +2989,25 @@ srtp_err_status_t srtp_unprotect(srtp_t ctx, */ return srtp_err_status_no_ctx; } + } + + /* + * 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) { + /* + * 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; } else { /* * Verify that stream is for received traffic - this check will @@ -2900,9 +3044,22 @@ srtp_err_status_t srtp_unprotect(srtp_t ctx, 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, - &session_keys); + /* + * 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 && rcc_carry && + 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; } @@ -2920,6 +3077,48 @@ srtp_err_status_t srtp_unprotect(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): 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. + */ + rcc_tag_len = + (stream->rcc_mode == srtp_rcc_mode_1 && !rcc_carry) ? 0 : tag_len; + + 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 * happen to be using @@ -2967,14 +3166,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; } @@ -2988,7 +3187,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 { @@ -3044,7 +3243,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; } } @@ -4720,6 +4925,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 + * 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; + } } 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..7106234e5 100644 --- a/srtp/srtp_policy.c +++ b/srtp/srtp_policy.c @@ -922,6 +922,53 @@ 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 + * 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; + } + break; + default: + return srtp_err_status_bad_param; + } + } + return srtp_err_status_ok; } @@ -1022,6 +1069,41 @@ 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; + } + + 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; + + 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/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 new file mode 100644 index 000000000..b68370206 --- /dev/null +++ b/test/rcc_test.c @@ -0,0 +1,1019 @@ +/* + * 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). + * + */ +/* + * + * 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. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#ifdef HAVE_NETINET_IN_H +#include +#elif defined(HAVE_WINSOCK2_H) +#include +#endif + +#include "cutest.h" + +#include "srtp.h" +#include "util.h" + +#include + +#define TEST_SSRC 0xcafebabe + +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, +}; + +#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, +}; +static const uint8_t gcm_master_salt[12] = { + 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, +}; + +static const uint8_t mki4[4] = { 0xde, 0xad, 0xbe, 0xef }; +#endif + +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_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), + cm_master_salt, sizeof(cm_master_salt), NULL, + 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_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_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 */ +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 */ + memcpy(buf + 2, &nseq, 2); + memcpy(buf + 4, &ts, 4); + memcpy(buf + 8, &ssrc, 4); + memcpy(buf + 12, payload, plen); + + return 12 + plen; +} + +static void rcc_roundtrip(srtp_t snd, srtp_t rcv, uint16_t seq, const char *msg) +{ + uint8_t pkt[256], enc[256], dec[256]; + size_t len = make_rtp(pkt, seq, msg); + size_t enc_len = sizeof(enc); + size_t dec_len = sizeof(dec); + + 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); +} + +/* advance the sender's ROC to 1 by walking the sequence number past a wrap */ +static void advance_sender_roc(srtp_t snd) +{ + uint8_t pkt[256], enc[256]; + uint32_t roc = 0; + + 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); +} + +/* + * policy-level validation + */ + +/* + * 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"); + } + + 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"); + } + + 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"); + } + + 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"); + } + + 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). + */ + 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()); +} + +/* + * 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) + */ + +/* + * 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()); +} + +/* + * 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"); + } + + 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 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) +{ + 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 trailing ROC; others are plain RFC 7714 */ + for (uint16_t seq = 0; seq <= 12; seq++) { + rcc_roundtrip(snd, rcv, seq, "gcm mode3 rate4 payload"); + } + + 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). + */ + 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()); +} + +/* + * 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. + */ + 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); + + 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()); +} + +/* + * 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 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) +{ + 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 = { + { "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 }, + { "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 }, + { "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 }, + { "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 } +};