@@ -165,6 +165,27 @@ static void rcc_roundtrip(srtp_t snd, srtp_t rcv, uint16_t seq, const char *msg)
165165 CHECK_BUFFER_EQUAL (dec , pkt , len );
166166}
167167
168+ /*
169+ * Protect a packet on policy, then try to unprotect it on the same session.
170+ * After srtp_protect() the stream is a sender, so unprotect must report an
171+ * SSRC collision rather than decrypting the packet.
172+ */
173+ static void rcc_sender_must_reject_unprotect (srtp_policy_t policy , uint16_t seq )
174+ {
175+ srtp_t sess ;
176+ uint8_t pkt [256 ], enc [256 ], dec [256 ];
177+ size_t len , enc_len , dec_len ;
178+
179+ CHECK_OK (srtp_create (& sess , policy ));
180+ len = make_rtp (pkt , seq , "sender unprotect" );
181+ enc_len = sizeof (enc );
182+ CHECK_OK (srtp_protect (sess , pkt , len , enc , & enc_len , 0 ));
183+ dec_len = sizeof (dec );
184+ CHECK_RETURN (srtp_unprotect (sess , enc , enc_len , dec , & dec_len ),
185+ srtp_err_status_direction_mismatch );
186+ CHECK_OK (srtp_dealloc (sess ));
187+ }
188+
168189/* advance the sender's ROC to 1 by walking the sequence number past a wrap */
169190static void advance_sender_roc (srtp_t snd )
170191{
@@ -660,6 +681,36 @@ static void rcc_mode2_carry_out_of_order_keeps_window(void)
660681 CHECK_OK (srtp_shutdown ());
661682}
662683
684+ /*
685+ * An RCC-enabled sender stream must still reject unprotect as an SSRC
686+ * collision. ROC-carrying packets (and every mode-3 packet) used to skip
687+ * that check because their index is estimated later.
688+ */
689+ static void rcc_mode2_sender_rejects_unprotect (void )
690+ {
691+ srtp_policy_t p ;
692+
693+ CHECK_OK (srtp_init ());
694+ create_cm_rcc_policy (& p , srtp_rcc_mode_2 , 1 );
695+ /* R == 1: every packet carries the ROC */
696+ rcc_sender_must_reject_unprotect (p , 4 );
697+ srtp_policy_destroy (p );
698+ CHECK_OK (srtp_shutdown ());
699+ }
700+
701+ static void rcc_mode1_sender_rejects_unprotect (void )
702+ {
703+ srtp_policy_t p ;
704+
705+ CHECK_OK (srtp_init ());
706+ create_cm_rcc_policy (& p , srtp_rcc_mode_1 , 4 );
707+ /* seq 0 is ROC-carrying; seq 1 is untagged. Both must collide. */
708+ rcc_sender_must_reject_unprotect (p , 0 );
709+ rcc_sender_must_reject_unprotect (p , 1 );
710+ srtp_policy_destroy (p );
711+ CHECK_OK (srtp_shutdown ());
712+ }
713+
663714#ifdef GCM
664715/*
665716 * AES-GCM round trips (mode 3, RFC 7714 layout)
@@ -977,6 +1028,20 @@ static void rcc_gcm_mode3_carry_replay_rejected(void)
9771028 srtp_policy_destroy (rp );
9781029 CHECK_OK (srtp_shutdown ());
9791030}
1031+
1032+ static void rcc_gcm_mode3_sender_rejects_unprotect (void )
1033+ {
1034+ srtp_policy_t p ;
1035+
1036+ CHECK_OK (srtp_init ());
1037+ create_gcm_rcc_policy (& p , srtp_rcc_mode_3 , 4 );
1038+ /* seq 0 carries the ROC; seq 1 does not. Mode 3 skipped the check on
1039+ * both. */
1040+ rcc_sender_must_reject_unprotect (p , 0 );
1041+ rcc_sender_must_reject_unprotect (p , 1 );
1042+ srtp_policy_destroy (p );
1043+ CHECK_OK (srtp_shutdown ());
1044+ }
9801045#endif /* GCM */
9811046
9821047TEST_LIST = {
@@ -1000,6 +1065,10 @@ TEST_LIST = {
10001065 { "rcc_mode2_carry_replay_rejected()" , rcc_mode2_carry_replay_rejected },
10011066 { "rcc_mode2_carry_out_of_order_keeps_window()" ,
10021067 rcc_mode2_carry_out_of_order_keeps_window },
1068+ { "rcc_mode2_sender_rejects_unprotect()" ,
1069+ rcc_mode2_sender_rejects_unprotect },
1070+ { "rcc_mode1_sender_rejects_unprotect()" ,
1071+ rcc_mode1_sender_rejects_unprotect },
10031072#ifdef GCM
10041073 { "rcc_gcm_mode2_rejected_at_create()" , rcc_gcm_mode2_rejected_at_create },
10051074 { "rcc_gcm_mode3_basic_roundtrip()" , rcc_gcm_mode3_basic_roundtrip },
@@ -1014,6 +1083,8 @@ TEST_LIST = {
10141083 rcc_gcm_mode3_wildcard_inbound_late_join },
10151084 { "rcc_gcm_mode3_carry_replay_rejected()" ,
10161085 rcc_gcm_mode3_carry_replay_rejected },
1086+ { "rcc_gcm_mode3_sender_rejects_unprotect()" ,
1087+ rcc_gcm_mode3_sender_rejects_unprotect },
10171088#endif
10181089 { 0 }
10191090};
0 commit comments