1. Problem Overview
libpeer allows NULL encryption profiles to enter the negotiable set during DTLS-SRTP negotiation, and after negotiation completes it does not map the actually-selected profile to the SRTP/SRTCP policy used by libsrtp. The media layer always uses a fixed default policy. When the remote side actually selects a NULL profile or a non-default profile (such as AES128-CM-HMAC-SHA1-32), libpeer's media path violates the RFC 8827 prohibition on plaintext media or causes an SRTP parameter mismatch.
2. Specification Requirements
RFC 8827 §6.5 "Communications Security" states:
"Media traffic MUST NOT be sent over plain (unencrypted) RTP or RTCP; that is, implementations MUST NOT negotiate cipher suites with NULL encryption modes."
RFC 5764 §4.1.2 "SRTP Protection Profiles" states:
"A DTLS-SRTP SRTP Protection Profile defines the parameters and options that are in effect for the SRTP processing."
Therefore the actually-selected profile must be consistent with the cipher, authentication algorithm, and tag length of the subsequent SRTP policy.
3. Code Analysis
When the negotiation condition holds, the following path exposes two violation points at the same time. The NULL profile is configured as an acceptable result, while the actually-obtained profile after the handshake is not passed to the policy configuration; the key-export callback directly creates a fixed default context.
/* src/dtls_srtp.c:148-154 — dtls_srtp_init */
static const mbedtls_ssl_srtp_profile default_profiles[] = {
MBEDTLS_TLS_SRTP_AES128_CM_HMAC_SHA1_80,
MBEDTLS_TLS_SRTP_AES128_CM_HMAC_SHA1_32,
MBEDTLS_TLS_SRTP_NULL_HMAC_SHA1_80,
MBEDTLS_TLS_SRTP_NULL_HMAC_SHA1_32,
MBEDTLS_TLS_SRTP_UNSET};
/* src/dtls_srtp.c:210-216 — dtls_srtp_init */
mbedtls_ssl_conf_dtls_srtp_protection_profiles(&dtls_srtp->conf, default_profiles);
mbedtls_ssl_conf_srtp_mki_value_supported(&dtls_srtp->conf, MBEDTLS_SSL_DTLS_SRTP_MKI_UNSUPPORTED);
mbedtls_ssl_setup(&dtls_srtp->ssl, &dtls_srtp->conf);
/* src/dtls_srtp.c:369-385 — dtls_srtp_do_handshake */
#if CONFIG_MBEDTLS_2_X
mbedtls_ssl_conf_export_keys_ext_cb(&dtls_srtp->conf, dtls_srtp_key_derivation_cb, dtls_srtp);
#else
mbedtls_ssl_set_export_keys_cb(&dtls_srtp->ssl, dtls_srtp_key_derivation_cb, dtls_srtp);
#endif
ret = mbedtls_ssl_handshake(&dtls_srtp->ssl);
/* src/dtls_srtp.c:291-314 — dtls_srtp_key_derivation */
memset(&dtls_srtp->remote_policy, 0, sizeof(dtls_srtp->remote_policy));
srtp_crypto_policy_set_rtp_default(&dtls_srtp->remote_policy.rtp);
srtp_crypto_policy_set_rtcp_default(&dtls_srtp->remote_policy.rtcp);
memset(&dtls_srtp->local_policy, 0, sizeof(dtls_srtp->local_policy));
srtp_crypto_policy_set_rtp_default(&dtls_srtp->local_policy.rtp);
srtp_crypto_policy_set_rtcp_default(&dtls_srtp->local_policy.rtcp);
/* src/dtls_srtp.c:303-304, 325-326 — dtls_srtp_key_derivation */
srtp_create(&dtls_srtp->srtp_in, &dtls_srtp->remote_policy);
srtp_create(&dtls_srtp->srtp_out, &dtls_srtp->local_policy);
/* src/dtls_srtp.c:461-464 — dtls_srtp_handshake */
mbedtls_dtls_srtp_info dtls_srtp_negotiation_result;
mbedtls_ssl_get_dtls_srtp_negotiation_result(&dtls_srtp->ssl, &dtls_srtp_negotiation_result);
return ret;
/* src/dtls_srtp.c:509-522 — media consumers */
srtp_unprotect(dtls_srtp->srtp_in, packet, bytes);
srtp_protect(dtls_srtp->srtp_out, packet, bytes);
srtp_protect_rtcp(dtls_srtp->srtp_out, packet, bytes);
4. Impact Analysis
This is a functional bug. If the remote side selects AES32 or another profile different from the default policy, the two sides may have different understandings of the authentication tag length or cipher parameters, and SRTP packets may fail authentication or cannot be decrypted, causing persistent media failure. It is also a security bug: if the remote side actually selects NULL encryption, the media session violates WebRTC's prohibition on plaintext media and may lose confidentiality. The error enters the media context after the DTLS handshake; retrying the same negotiation does not repair the missing profile-to-policy mapping.
5. Dynamic Verification
Server command:
cd /8T/xiangpu/Code/webrtcfuzzing/aiortc
python3 examples/libpeer/c5_dtls_srtp_protection_profile_probe.py \
--port 9457 \
--cert /tmp/libpeer-c3-cert.pem \
--key /tmp/libpeer-c3-key.pem
The client still uses libpeer's bundled sample:
cd /home/xiangpu/Code/webrtc_evaluation/projects/libpeer
timeout 20s stdbuf -oL env LD_LIBRARY_PATH=build_aiortc_c3/src \
./build_aiortc_c3/examples/generic/sample \
-u https://127.0.0.1:9457/whip
Key server logs:
forced ClientHello profile=SRTP_AES128_CM_SHA1_32
DTLS ServerHello selected use_srtp profile=SRTP_AES128_CM_SHA1_32
C5 TRIGGER: libpeer selected AES128_CM_HMAC_SHA1_32; compare with its fixed AES128_CM_HMAC_SHA1_80 policy
libpeer logs:
Created inbound SRTP session
Created outbound SRTP session
DTLS server handshake done
DTLS-SRTP handshake done
state is changed: completed
The logs show:
- The test side only offers AES32 in the DTLS
ClientHello.
DTLS ServerHello selected ... AES128_CM_HMAC_SHA1_32 is the actual negotiation result.
- libpeer then completes ICE/DTLS and creates its own SRTP contexts.
- But libpeer's source code always calls:
srtp_crypto_policy_set_rtp_default()
srtp_crypto_policy_set_rtcp_default()
These default policies are AES128-CM/HMAC-SHA1-80, not the negotiated AES32. Therefore:
Actually negotiated: AES128_CM_HMAC_SHA1_32
Actual policy: AES128_CM_HMAC_SHA1_80
This proves the C5 problem.
Supplementary Material
c5_DTLS_MEDIA_srtp_protection_profile.zip
1. Problem Overview
libpeer allows NULL encryption profiles to enter the negotiable set during DTLS-SRTP negotiation, and after negotiation completes it does not map the actually-selected profile to the SRTP/SRTCP policy used by libsrtp. The media layer always uses a fixed default policy. When the remote side actually selects a NULL profile or a non-default profile (such as AES128-CM-HMAC-SHA1-32), libpeer's media path violates the RFC 8827 prohibition on plaintext media or causes an SRTP parameter mismatch.
2. Specification Requirements
RFC 8827 §6.5 "Communications Security" states:
RFC 5764 §4.1.2 "SRTP Protection Profiles" states:
Therefore the actually-selected profile must be consistent with the cipher, authentication algorithm, and tag length of the subsequent SRTP policy.
3. Code Analysis
When the negotiation condition holds, the following path exposes two violation points at the same time. The NULL profile is configured as an acceptable result, while the actually-obtained profile after the handshake is not passed to the policy configuration; the key-export callback directly creates a fixed default context.
4. Impact Analysis
This is a functional bug. If the remote side selects AES32 or another profile different from the default policy, the two sides may have different understandings of the authentication tag length or cipher parameters, and SRTP packets may fail authentication or cannot be decrypted, causing persistent media failure. It is also a security bug: if the remote side actually selects NULL encryption, the media session violates WebRTC's prohibition on plaintext media and may lose confidentiality. The error enters the media context after the DTLS handshake; retrying the same negotiation does not repair the missing profile-to-policy mapping.
5. Dynamic Verification
Server command:
cd /8T/xiangpu/Code/webrtcfuzzing/aiortc python3 examples/libpeer/c5_dtls_srtp_protection_profile_probe.py \ --port 9457 \ --cert /tmp/libpeer-c3-cert.pem \ --key /tmp/libpeer-c3-key.pemThe client still uses libpeer's bundled sample:
cd /home/xiangpu/Code/webrtc_evaluation/projects/libpeer timeout 20s stdbuf -oL env LD_LIBRARY_PATH=build_aiortc_c3/src \ ./build_aiortc_c3/examples/generic/sample \ -u https://127.0.0.1:9457/whipKey server logs:
forced ClientHello profile=SRTP_AES128_CM_SHA1_32 DTLS ServerHello selected use_srtp profile=SRTP_AES128_CM_SHA1_32 C5 TRIGGER: libpeer selected AES128_CM_HMAC_SHA1_32; compare with its fixed AES128_CM_HMAC_SHA1_80 policylibpeer logs:
The logs show:
ClientHello.DTLS ServerHello selected ... AES128_CM_HMAC_SHA1_32is the actual negotiation result.These default policies are AES128-CM/HMAC-SHA1-80, not the negotiated AES32. Therefore:
This proves the C5 problem.
Supplementary Material
c5_DTLS_MEDIA_srtp_protection_profile.zip