Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/_cffi_src/build_srtp.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@
ssrc_any_outbound = 3
} srtp_ssrc_type_t;

typedef uint32_t srtp_cipher_type_id_t;

typedef struct srtp_crypto_policy_t {
srtp_cipher_type_id_t cipher_type;
int cipher_key_len;
...;
} srtp_crypto_policy_t;

Expand Down Expand Up @@ -64,6 +68,13 @@

void srtp_crypto_policy_set_rtp_default(srtp_crypto_policy_t *p);
void srtp_crypto_policy_set_rtcp_default(srtp_crypto_policy_t *p);
void srtp_crypto_policy_set_aes_cm_128_hmac_sha1_32(srtp_crypto_policy_t *p);
void srtp_crypto_policy_set_aes_cm_256_hmac_sha1_80(srtp_crypto_policy_t *p);
void srtp_crypto_policy_set_aes_cm_256_hmac_sha1_32(srtp_crypto_policy_t *p);
void srtp_crypto_policy_set_aes_cm_192_hmac_sha1_80(srtp_crypto_policy_t *p);
void srtp_crypto_policy_set_aes_cm_192_hmac_sha1_32(srtp_crypto_policy_t *p);
void srtp_crypto_policy_set_aes_gcm_128_16_auth(srtp_crypto_policy_t *p);
void srtp_crypto_policy_set_aes_gcm_256_16_auth(srtp_crypto_policy_t *p);

srtp_err_status_t srtp_crypto_policy_set_from_profile_for_rtp(
srtp_crypto_policy_t *policy, srtp_profile_t profile);
Expand Down
85 changes: 73 additions & 12 deletions src/pylibsrtp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@
Policy for single SRTP stream.
"""

AES_CM_128_HMAC_SHA1_80 = 1
AES_CM_128_HMAC_SHA1_32 = 2
AES_CM_192_HMAC_SHA1_80 = 3
AES_CM_192_HMAC_SHA1_32 = 4
AES_CM_256_HMAC_SHA1_80 = 5
AES_CM_256_HMAC_SHA1_32 = 6
AES_GCM_128_16 = 7
AES_GCM_256_16 = 8

#: AES-128 CM mode with 80-bit authentication tag (default)
SRTP_PROFILE_AES128_CM_SHA1_80 = lib.srtp_profile_aes128_cm_sha1_80
#: AES-128 CM mode with 32-bit authentication tag
Expand All @@ -83,26 +92,68 @@
key: Optional[bytes] = None,
ssrc_type: int = SSRC_UNDEFINED,
ssrc_value: int = 0,
srtp_profile: int = SRTP_PROFILE_AES128_CM_SHA1_80,
srtp_profile: Optional[int] = None,
crypto_policy: Optional[int] = None,
) -> None:
if srtp_profile is None and crypto_policy is None:
srtp_profile = self.SRTP_PROFILE_AES128_CM_SHA1_80

self._policy = ffi.new("srtp_policy_t *")
self._srtp_profile = srtp_profile
self._crypto_policy = crypto_policy

_srtp_assert(
lib.srtp_crypto_policy_set_from_profile_for_rtp(
ffi.addressof(self._policy.rtp), srtp_profile
if srtp_profile is not None:
_srtp_assert(
lib.srtp_crypto_policy_set_from_profile_for_rtp(
ffi.addressof(self._policy.rtp), srtp_profile
)
)
)
_srtp_assert(
lib.srtp_crypto_policy_set_from_profile_for_rtcp(
ffi.addressof(self._policy.rtcp), srtp_profile
_srtp_assert(
lib.srtp_crypto_policy_set_from_profile_for_rtcp(
ffi.addressof(self._policy.rtcp), srtp_profile
)
)
)
else:
self.__set_crypto_policy(crypto_policy)

self.key = key
self.ssrc_type = ssrc_type
self.ssrc_value = ssrc_value

def __set_crypto_policy(self, crypto_policy: int) -> None:
rtp = ffi.addressof(self._policy.rtp)
rtcp = ffi.addressof(self._policy.rtcp)

if crypto_policy == self.AES_CM_128_HMAC_SHA1_80:
lib.srtp_crypto_policy_set_rtp_default(rtp)
lib.srtp_crypto_policy_set_rtcp_default(rtcp)
elif crypto_policy == self.AES_CM_128_HMAC_SHA1_32:
lib.srtp_crypto_policy_set_aes_cm_128_hmac_sha1_32(rtp)
# RTCP use an 80-bit auth tag.
lib.srtp_crypto_policy_set_rtcp_default(rtcp)
elif crypto_policy == self.AES_CM_192_HMAC_SHA1_80:
lib.srtp_crypto_policy_set_aes_cm_192_hmac_sha1_80(rtp)
lib.srtp_crypto_policy_set_aes_cm_192_hmac_sha1_80(rtcp)
elif crypto_policy == self.AES_CM_192_HMAC_SHA1_32:
lib.srtp_crypto_policy_set_aes_cm_192_hmac_sha1_32(rtp)

Check warning on line 138 in src/pylibsrtp/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/pylibsrtp/__init__.py#L138

Added line #L138 was not covered by tests
# RTCP use an 80-bit auth tag.
lib.srtp_crypto_policy_set_aes_cm_192_hmac_sha1_80(rtcp)

Check warning on line 140 in src/pylibsrtp/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/pylibsrtp/__init__.py#L140

Added line #L140 was not covered by tests
elif crypto_policy == self.AES_CM_256_HMAC_SHA1_80:
lib.srtp_crypto_policy_set_aes_cm_256_hmac_sha1_80(rtp)
lib.srtp_crypto_policy_set_aes_cm_256_hmac_sha1_80(rtcp)
elif crypto_policy == self.AES_CM_256_HMAC_SHA1_32:
lib.srtp_crypto_policy_set_aes_cm_256_hmac_sha1_32(rtp)
# RTCP use an 80-bit auth tag.
lib.srtp_crypto_policy_set_aes_cm_256_hmac_sha1_80(rtcp)
elif crypto_policy == self.AES_GCM_128_16:
lib.srtp_crypto_policy_set_aes_gcm_128_16_auth(rtp)
lib.srtp_crypto_policy_set_aes_gcm_128_16_auth(rtcp)
elif crypto_policy == self.AES_GCM_256_16:
lib.srtp_crypto_policy_set_aes_gcm_256_16_auth(rtp)
lib.srtp_crypto_policy_set_aes_gcm_256_16_auth(rtcp)
else:
raise ValueError("Unknown crypto policy")

Check warning on line 155 in src/pylibsrtp/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/pylibsrtp/__init__.py#L155

Added line #L155 was not covered by tests

@property
def allow_repeat_tx(self) -> bool:
"""
Expand All @@ -114,6 +165,13 @@
def allow_repeat_tx(self, allow_repeat_tx: bool) -> None:
self._policy.allow_repeat_tx = 1 if allow_repeat_tx else 0

@property
def crypto_policy(self) -> int:
"""
The crypto policy.
"""
return self._crypto_policy

Check warning on line 173 in src/pylibsrtp/__init__.py

View check run for this annotation

Codecov / codecov/patch

src/pylibsrtp/__init__.py#L173

Added line #L173 was not covered by tests

@property
def key(self) -> Optional[bytes]:
"""
Expand All @@ -132,9 +190,12 @@
return

# Check the key is acceptable then assign it.
expected_length = lib.srtp_profile_get_master_key_length(
self._srtp_profile
) + lib.srtp_profile_get_master_salt_length(self._srtp_profile)
if self._srtp_profile is not None:
expected_length = lib.srtp_profile_get_master_key_length(
self._srtp_profile
) + lib.srtp_profile_get_master_salt_length(self._srtp_profile)
else:
expected_length = self._policy.rtp.cipher_key_len
if not isinstance(key, bytes):
raise TypeError("key must be bytes")
if len(key) < expected_length:
Expand Down
110 changes: 103 additions & 7 deletions tests/test_session.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import dataclasses
import logging
import secrets
from typing import Optional
from unittest import TestCase

from pylibsrtp import Error, Policy, Session

logger = logging.getLogger(__name__)

RTP = (
b"\x80\x08\x00\x00" # version, packet type, sequence number
b"\x00\x00\x00\x00" # timestamp
Expand All @@ -15,12 +18,25 @@
)


@dataclasses.dataclass
class Profile:
key_length: int
protected_rtp_length: int
protected_rtcp_length: int
srtp_profile: int
def __init__(self,
key_length: int,
protected_rtp_length: int,
protected_rtcp_length: int,
srtp_profile: Optional[int] = None,
crypto_policy: Optional[int] = None):
self.key_length = key_length
self.protected_rtp_length = protected_rtp_length
self.protected_rtcp_length = protected_rtcp_length
self.srtp_profile = srtp_profile
self.crypto_policy = crypto_policy

def __repr__(self):
return (f'Profile(key_length={self.key_length}, '
f'protected_rtp_length={self.protected_rtp_length}, '
f'protected_rtcp_length={self.protected_rtcp_length}, '
f'srtp_profile={self.srtp_profile}, '
f'crypto_policy={self.crypto_policy})')


# AES-GCM may not be supported depending on how libsrtp2 was built.
Expand All @@ -30,17 +46,72 @@ class Profile:
protected_rtp_length=182,
protected_rtcp_length=42,
srtp_profile=Policy.SRTP_PROFILE_AES128_CM_SHA1_80,
crypto_policy=None,
),
Profile(
key_length=30,
protected_rtp_length=176,
protected_rtcp_length=42,
srtp_profile=Policy.SRTP_PROFILE_AES128_CM_SHA1_32,
crypto_policy=None,
),
Profile(
key_length=30,
protected_rtp_length=182,
protected_rtcp_length=42,
srtp_profile=None,
crypto_policy=Policy.AES_CM_128_HMAC_SHA1_80,
),
Profile(
key_length=30,
protected_rtp_length=176,
protected_rtcp_length=42,
srtp_profile=None,
crypto_policy=Policy.AES_CM_128_HMAC_SHA1_32,
),
Profile(
key_length=46,
protected_rtp_length=182,
protected_rtcp_length=42,
srtp_profile=None,
crypto_policy=Policy.AES_CM_256_HMAC_SHA1_80,
),
Profile(
key_length=46,
protected_rtp_length=176,
protected_rtcp_length=42,
srtp_profile=None,
crypto_policy=Policy.AES_CM_256_HMAC_SHA1_32,
),
]

try:
Session(Policy(key=b'\x00' * 38, crypto_policy=Policy.AES_CM_192_HMAC_SHA1_80))
except Error:
logger.warning("AES-192 is not supported by this libsrtp.")
pass
else:
SRTP_PROFILES += [
Profile(
key_length=38,
protected_rtp_length=182,
protected_rtcp_length=42,
srtp_profile=None,
crypto_policy=Policy.AES_CM_192_HMAC_SHA1_80,
),
Profile(
key_length=38,
protected_rtp_length=176,
protected_rtcp_length=42,
srtp_profile=None,
crypto_policy=Policy.AES_CM_192_HMAC_SHA1_32,
),
]

try:
Policy(srtp_profile=Policy.SRTP_PROFILE_AEAD_AES_128_GCM)
except Error:
logger.warning("GCM is not supported by this libsrtp.")
pass
else:
SRTP_PROFILES += [
Expand All @@ -49,12 +120,28 @@ class Profile:
protected_rtp_length=188,
protected_rtcp_length=48,
srtp_profile=Policy.SRTP_PROFILE_AEAD_AES_128_GCM,
crypto_policy=None,
),
Profile(
key_length=44,
protected_rtp_length=188,
protected_rtcp_length=48,
srtp_profile=Policy.SRTP_PROFILE_AEAD_AES_256_GCM,
crypto_policy=None,
),
Profile(
key_length=28,
protected_rtp_length=188,
protected_rtcp_length=48,
srtp_profile=None,
crypto_policy=Policy.AES_GCM_128_16,
),
Profile(
key_length=44,
protected_rtp_length=188,
protected_rtcp_length=48,
srtp_profile=None,
crypto_policy=Policy.AES_GCM_256_16,
),
]

Expand Down Expand Up @@ -108,7 +195,8 @@ def test_srtp_policy(self):
# Valid user-specified profiles.
for profile in SRTP_PROFILES:
with self.subTest(profile=profile):
policy = Policy(srtp_profile=profile.srtp_profile)
policy = Policy(srtp_profile=profile.srtp_profile,
crypto_policy=profile.crypto_policy)
self.assertEqual(policy.srtp_profile, profile.srtp_profile)

# Invalid profile.
Expand Down Expand Up @@ -158,6 +246,7 @@ def test_add_remove_stream(self):
srtp_profile=profile.srtp_profile,
ssrc_type=Policy.SSRC_SPECIFIC,
ssrc_value=12345,
crypto_policy=profile.crypto_policy,
)
)
protected = tx_session.protect(RTP)
Expand All @@ -171,6 +260,7 @@ def test_add_remove_stream(self):
srtp_profile=profile.srtp_profile,
ssrc_type=Policy.SSRC_SPECIFIC,
ssrc_value=12345,
crypto_policy=profile.crypto_policy,
)
)
unprotected = rx_session.unprotect(protected)
Expand All @@ -195,6 +285,7 @@ def test_rtp_any_ssrc(self):
key=key,
srtp_profile=profile.srtp_profile,
ssrc_type=Policy.SSRC_ANY_OUTBOUND,
crypto_policy=profile.crypto_policy,
)
)
protected = tx_session.protect(RTP)
Expand All @@ -216,6 +307,7 @@ def test_rtp_any_ssrc(self):
key=key,
srtp_profile=profile.srtp_profile,
ssrc_type=Policy.SSRC_ANY_INBOUND,
crypto_policy=profile.crypto_policy,
)
)
unprotected = rx_session.unprotect(protected)
Expand All @@ -232,6 +324,7 @@ def test_rtcp_any_ssrc(self):
key=key,
srtp_profile=profile.srtp_profile,
ssrc_type=Policy.SSRC_ANY_OUTBOUND,
crypto_policy=profile.crypto_policy,
)
)
protected = tx_session.protect_rtcp(RTCP)
Expand All @@ -253,6 +346,7 @@ def test_rtcp_any_ssrc(self):
key=key,
srtp_profile=profile.srtp_profile,
ssrc_type=Policy.SSRC_ANY_INBOUND,
crypto_policy=profile.crypto_policy,
)
)
unprotected = rx_session.unprotect_rtcp(protected)
Expand All @@ -270,6 +364,7 @@ def test_rtp_specific_ssrc(self):
srtp_profile=profile.srtp_profile,
ssrc_type=Policy.SSRC_SPECIFIC,
ssrc_value=12345,
crypto_policy=profile.crypto_policy,
)
)
protected = tx_session.protect(RTP)
Expand All @@ -282,6 +377,7 @@ def test_rtp_specific_ssrc(self):
srtp_profile=profile.srtp_profile,
ssrc_type=Policy.SSRC_SPECIFIC,
ssrc_value=12345,
crypto_policy=profile.crypto_policy,
)
)
unprotected = rx_session.unprotect(protected)
Expand Down
Loading