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 8e0ab1fca..e55d03970 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,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 3d14d3364..a30446543 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 */ @@ -2063,6 +2071,566 @@ 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 @@ -2085,6 +2653,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 +2678,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 +2758,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 +2818,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 +2870,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 +2881,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 +2960,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 +2980,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 +3102,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 +3223,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 +3523,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, - &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 && + (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 +3588,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 +5373,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..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; } @@ -1022,6 +1068,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..4f137063f --- /dev/null +++ b/test/rcc_test.c @@ -0,0 +1,734 @@ +/* + * 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(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_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 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()); +} + +#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 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"); + } + + 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()); +} +#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 } +};