././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1743942621.1756337 pylibsrtp-0.12.0/0000755000175100001660000000000014774471735013250 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1743942615.0 pylibsrtp-0.12.0/LICENSE0000644000175100001660000000274114774471727014262 0ustar00runnerdockerCopyright (c) Jeremy Lainé. 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. * Neither the name of pylibsrtp nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 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 HOLDER 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. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1743942615.0 pylibsrtp-0.12.0/MANIFEST.in0000644000175100001660000000017514774471727015012 0ustar00runnerdockerinclude LICENSE recursive-include docs *.py *.rst Makefile recursive-include src/_cffi_src *.py recursive-include tests *.py ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1743942621.1756337 pylibsrtp-0.12.0/PKG-INFO0000644000175100001660000000776714774471735014366 0ustar00runnerdockerMetadata-Version: 2.4 Name: pylibsrtp Version: 0.12.0 Summary: Python wrapper around the libsrtp library Author-email: Jeremy Lainé License: BSD-3-Clause Project-URL: homepage, https://github.com/aiortc/pylibsrtp Project-URL: documentation, https://pylibsrtp.readthedocs.io/ Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Topic :: Communications :: Telephony Classifier: Topic :: Security :: Cryptography Requires-Python: >=3.9 Description-Content-Type: text/x-rst License-File: LICENSE Requires-Dist: cffi>=1.0.0 Provides-Extra: dev Requires-Dist: coverage[toml]>=7.2.2; extra == "dev" Dynamic: license-file pylibsrtp ========= .. image:: https://img.shields.io/pypi/l/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp :alt: License .. image:: https://img.shields.io/pypi/v/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp :alt: Version .. image:: https://img.shields.io/pypi/pyversions/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp :alt: Python versions .. image:: https://github.com/aiortc/pylibsrtp/workflows/tests/badge.svg :target: https://github.com/aiortc/pylibsrtp/actions :alt: Tests .. image:: https://img.shields.io/codecov/c/github/aiortc/pylibsrtp.svg :target: https://codecov.io/gh/aiortc/pylibsrtp :alt: Coverage .. image:: https://readthedocs.org/projects/pylibsrtp/badge/?version=latest :target: https://pylibsrtp.readthedocs.io/ :alt: Documentation What is ``pylibsrtp``? ---------------------- ``pylibsrtp`` is a Python wrapper around `libsrtp`_, making it possible to encrypt and decrypt Secure Real-time Transport Protocol (SRTP) packets from Python code. SRTP is a profile of the Real-time Transport Protocol (RTP) which provides confidentiality, message authentication, and replay protection. It is defined by `RFC 3711`_. You can install ``pylibsrtp`` with ``pip``: .. code-block:: console $ pip install pylibsrtp To learn more about ``pylibsrtp`` please `read the documentation`_. .. _libsrtp: https://github.com/cisco/libsrtp .. _RFC 3711: https://tools.ietf.org/html/rfc3711 .. _read the documentation: https://pylibsrtp.readthedocs.io/en/stable/ Example ------- .. code:: python #!/usr/bin/env python from pylibsrtp import Policy, Session key = (b'\x00' * 30) rtp = b'\x80\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + (b'\xd4' * 160) # protect RTP tx_policy = Policy(key=key, ssrc_type=Policy.SSRC_ANY_OUTBOUND) tx_session = Session(policy=tx_policy) srtp = tx_session.protect(rtp) # unprotect RTP rx_policy = Policy(key=key, ssrc_type=Policy.SSRC_ANY_INBOUND) rx_session = Session(policy=rx_policy) rtp2 = rx_session.unprotect(srtp) # check roundtrip worked! assert rtp2 == rtp Building pylibsrtp ------------------ If you wish to build pylibsrtp yourself, you will need libsrtp version 2.0 or better. Linux ..... On Debian/Ubuntu run: .. code-block:: console $ apt install libsrtp2-dev On Fedora/CentOS run: .. code-block:: console $ dnf install libsrtp-devel macOS ..... On macOS run: .. code-block:: console $ brew install srtp You will need to set some environment variables to link against libsrtp: .. code-block:: console export CFLAGS=-I$(brew --prefix openssl)/include -I$(brew --prefix srtp)/include export LDFLAGS=-L$(brew --prefix openssl)/lib -L$(brew --prefix srtp)/lib License ------- ``pylibsrtp`` is released under the `BSD license`_. .. _BSD license: https://pylibsrtp.readthedocs.io/en/stable/license.html ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1743942615.0 pylibsrtp-0.12.0/README.rst0000644000175100001660000000560114774471727014742 0ustar00runnerdockerpylibsrtp ========= .. image:: https://img.shields.io/pypi/l/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp :alt: License .. image:: https://img.shields.io/pypi/v/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp :alt: Version .. image:: https://img.shields.io/pypi/pyversions/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp :alt: Python versions .. image:: https://github.com/aiortc/pylibsrtp/workflows/tests/badge.svg :target: https://github.com/aiortc/pylibsrtp/actions :alt: Tests .. image:: https://img.shields.io/codecov/c/github/aiortc/pylibsrtp.svg :target: https://codecov.io/gh/aiortc/pylibsrtp :alt: Coverage .. image:: https://readthedocs.org/projects/pylibsrtp/badge/?version=latest :target: https://pylibsrtp.readthedocs.io/ :alt: Documentation What is ``pylibsrtp``? ---------------------- ``pylibsrtp`` is a Python wrapper around `libsrtp`_, making it possible to encrypt and decrypt Secure Real-time Transport Protocol (SRTP) packets from Python code. SRTP is a profile of the Real-time Transport Protocol (RTP) which provides confidentiality, message authentication, and replay protection. It is defined by `RFC 3711`_. You can install ``pylibsrtp`` with ``pip``: .. code-block:: console $ pip install pylibsrtp To learn more about ``pylibsrtp`` please `read the documentation`_. .. _libsrtp: https://github.com/cisco/libsrtp .. _RFC 3711: https://tools.ietf.org/html/rfc3711 .. _read the documentation: https://pylibsrtp.readthedocs.io/en/stable/ Example ------- .. code:: python #!/usr/bin/env python from pylibsrtp import Policy, Session key = (b'\x00' * 30) rtp = b'\x80\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + (b'\xd4' * 160) # protect RTP tx_policy = Policy(key=key, ssrc_type=Policy.SSRC_ANY_OUTBOUND) tx_session = Session(policy=tx_policy) srtp = tx_session.protect(rtp) # unprotect RTP rx_policy = Policy(key=key, ssrc_type=Policy.SSRC_ANY_INBOUND) rx_session = Session(policy=rx_policy) rtp2 = rx_session.unprotect(srtp) # check roundtrip worked! assert rtp2 == rtp Building pylibsrtp ------------------ If you wish to build pylibsrtp yourself, you will need libsrtp version 2.0 or better. Linux ..... On Debian/Ubuntu run: .. code-block:: console $ apt install libsrtp2-dev On Fedora/CentOS run: .. code-block:: console $ dnf install libsrtp-devel macOS ..... On macOS run: .. code-block:: console $ brew install srtp You will need to set some environment variables to link against libsrtp: .. code-block:: console export CFLAGS=-I$(brew --prefix openssl)/include -I$(brew --prefix srtp)/include export LDFLAGS=-L$(brew --prefix openssl)/lib -L$(brew --prefix srtp)/lib License ------- ``pylibsrtp`` is released under the `BSD license`_. .. _BSD license: https://pylibsrtp.readthedocs.io/en/stable/license.html ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1743942621.1736336 pylibsrtp-0.12.0/docs/0000755000175100001660000000000014774471735014200 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1743942615.0 pylibsrtp-0.12.0/docs/Makefile0000644000175100001660000000113614774471727015642 0ustar00runnerdocker# Minimal makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build SPHINXPROJ = pylibsrtp SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1743942615.0 pylibsrtp-0.12.0/docs/api.rst0000644000175100001660000000024514774471727015505 0ustar00runnerdockerAPI Reference ============= .. automodule:: pylibsrtp .. autoclass:: Error .. autoclass:: Policy :members: .. autoclass:: Session :members: ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1743942615.0 pylibsrtp-0.12.0/docs/conf.py0000644000175100001660000000422514774471727015503 0ustar00runnerdocker# Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Project information ----------------------------------------------------- project = "pylibsrtp" author = "Jeremy Lainé" copyright = author # -- General configuration ------------------------------------------------ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = ["sphinx.ext.autodoc"] # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. language = "en" # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = "alabaster" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = { "description": "Python wrapper around the libsrtp library.", "github_button": True, "github_user": "aiortc", "github_repo": "pylibsrtp", } # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] # Custom sidebar templates, must be a dictionary that maps document names # to template names. # # This is required for the alabaster theme # refs: http://alabaster.readthedocs.io/en/latest/installation.html#sidebars html_sidebars = { "**": [ "relations.html", # needs 'show_related': True theme option to display "searchbox.html", ] } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1743942615.0 pylibsrtp-0.12.0/docs/index.rst0000644000175100001660000000237514774471727016051 0ustar00runnerdockerpylibsrtp ========= .. image:: https://img.shields.io/pypi/l/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp :alt: License .. image:: https://img.shields.io/pypi/v/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp :alt: Version .. image:: https://img.shields.io/pypi/pyversions/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp :alt: Python versions .. image:: https://github.com/aiortc/pylibsrtp/workflows/tests/badge.svg :target: https://github.com/aiortc/pylibsrtp/actions :alt: Tests .. image:: https://img.shields.io/codecov/c/github/aiortc/pylibsrtp.svg :target: https://codecov.io/gh/aiortc/pylibsrtp :alt: Coverage ``pylibsrtp`` is a Python wrapper around `libsrtp`_, making it possible to encrypt and decrypt Secure Real-time Transport Protocol (SRTP) packets from Python code. SRTP is a profile of the Real-time Transport Protocol (RTP) which provides confidentiality, message authentication, and replay protection. It is defined by `RFC 3711`_. You can install ``pylibsrtp`` with ``pip``: .. code-block:: console $ pip install pylibsrtp .. _libsrtp: https://github.com/cisco/libsrtp .. _RFC 3711: https://tools.ietf.org/html/rfc3711 .. toctree:: :maxdepth: 2 api license ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1743942615.0 pylibsrtp-0.12.0/docs/license.rst0000644000175100001660000000006014774471727016351 0ustar00runnerdockerLicense ------- .. literalinclude:: ../LICENSE ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1743942615.0 pylibsrtp-0.12.0/pyproject.toml0000644000175100001660000000272514774471727016173 0ustar00runnerdocker[build-system] requires = ["cffi>=1.0.0", "setuptools", "wheel"] build-backend = "setuptools.build_meta" [project] name = "pylibsrtp" description = "Python wrapper around the libsrtp library" readme = "README.rst" requires-python = ">=3.9" license = { text = "BSD-3-Clause" } authors = [ { name = "Jeremy Lainé", email = "jeremy.laine@m4x.org" }, ] classifiers = [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Communications :: Telephony", "Topic :: Security :: Cryptography", ] dependencies = ["cffi>=1.0.0"] dynamic = ["version"] [project.optional-dependencies] dev = [ "coverage[toml]>=7.2.2", ] [project.urls] homepage = "https://github.com/aiortc/pylibsrtp" documentation = "https://pylibsrtp.readthedocs.io/" [tool.coverage.run] source = ["pylibsrtp"] [tool.ruff.lint] select = [ "E", # pycodestyle "F", # Pyflakes "W", # pycodestyle "I", # isort ] [tool.setuptools.dynamic] version = {attr = "pylibsrtp.__version__"} [tool.setuptools.packages.find] exclude = ["_cffi_src"] where = ["src"] ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1743942621.1756337 pylibsrtp-0.12.0/setup.cfg0000644000175100001660000000004614774471735015071 0ustar00runnerdocker[egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1743942615.0 pylibsrtp-0.12.0/setup.py0000644000175100001660000000063214774471727014764 0ustar00runnerdockerimport setuptools from wheel.bdist_wheel import bdist_wheel class bdist_wheel_abi3(bdist_wheel): def get_tag(self): python, abi, plat = super().get_tag() if python.startswith("cp"): return "cp39", "abi3", plat return python, abi, plat setuptools.setup( cffi_modules=["src/_cffi_src/build_srtp.py:ffibuilder"], cmdclass={"bdist_wheel": bdist_wheel_abi3}, ) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1743942621.1716335 pylibsrtp-0.12.0/src/0000755000175100001660000000000014774471735014037 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1743942621.1736336 pylibsrtp-0.12.0/src/_cffi_src/0000755000175100001660000000000014774471735015754 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1743942615.0 pylibsrtp-0.12.0/src/_cffi_src/build_srtp.py0000644000175100001660000000452414774471727020503 0ustar00runnerdockerimport sys from cffi import FFI libraries = ["srtp2"] if sys.platform == "win32": libraries += ["libcrypto", "advapi32", "crypt32", "gdi32", "user32", "ws2_32"] else: libraries += ["crypto"] ffibuilder = FFI() ffibuilder.set_source( "pylibsrtp._binding", "#include ", libraries=libraries ) ffibuilder.cdef( """ typedef enum { srtp_err_status_ok = 0, ... } srtp_err_status_t; typedef enum { srtp_profile_aes128_cm_sha1_80 = 1, srtp_profile_aes128_cm_sha1_32 = 2, srtp_profile_aead_aes_128_gcm = 7, srtp_profile_aead_aes_256_gcm = 8, ... } srtp_profile_t; typedef enum { ssrc_undefined = 0, ssrc_specific = 1, ssrc_any_inbound = 2, ssrc_any_outbound = 3 } srtp_ssrc_type_t; typedef struct srtp_crypto_policy_t { ...; } srtp_crypto_policy_t; typedef struct { srtp_ssrc_type_t type; unsigned int value; } srtp_ssrc_t; typedef struct srtp_ctx_t_ srtp_ctx_t; typedef srtp_ctx_t *srtp_t; typedef struct srtp_policy_t { srtp_ssrc_t ssrc; srtp_crypto_policy_t rtp; srtp_crypto_policy_t rtcp; unsigned char *key; unsigned long window_size; int allow_repeat_tx; ...; } srtp_policy_t; srtp_err_status_t srtp_init(void); srtp_err_status_t srtp_create(srtp_t *session, const srtp_policy_t *policy); srtp_err_status_t srtp_dealloc(srtp_t s); void srtp_crypto_policy_set_rtp_default(srtp_crypto_policy_t *p); void srtp_crypto_policy_set_rtcp_default(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); srtp_err_status_t srtp_crypto_policy_set_from_profile_for_rtcp( srtp_crypto_policy_t *policy, srtp_profile_t profile); unsigned int srtp_profile_get_master_key_length(srtp_profile_t profile); unsigned int srtp_profile_get_master_salt_length(srtp_profile_t profile); srtp_err_status_t srtp_add_stream(srtp_t session, const srtp_policy_t *policy); srtp_err_status_t srtp_remove_stream(srtp_t session, unsigned int ssrc); srtp_err_status_t srtp_protect(srtp_t ctx, void *rtp_hdr, int *len_ptr); srtp_err_status_t srtp_protect_rtcp(srtp_t ctx, void *rtcp_hdr, int *pkt_octet_len); srtp_err_status_t srtp_unprotect(srtp_t ctx, void *srtp_hdr, int *len_ptr); srtp_err_status_t srtp_unprotect_rtcp(srtp_t ctx, void *srtcp_hdr, int *pkt_octet_len); """ ) if __name__ == "__main__": ffibuilder.compile(verbose=True) ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1743942621.1736336 pylibsrtp-0.12.0/src/pylibsrtp/0000755000175100001660000000000014774471735016067 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1743942615.0 pylibsrtp-0.12.0/src/pylibsrtp/__init__.py0000644000175100001660000001764514774471727020216 0ustar00runnerdockerfrom socket import htonl from typing import Optional from ._binding import ffi, lib __all__ = ["Error", "Policy", "Session", "__version__"] __version__ = "0.12.0" ERRORS = [ "nothing to report", "unspecified failure", "unsupported parameter", "couldn't allocate memory", "couldn't deallocate properly", "couldn't initialize", "can't process as much data as requested", "authentication failure", "cipher failure", "replay check failed (bad index)", "replay check failed (index too old)", "algorithm failed test routine", "unsupported operation", "no appropriate context found", "unable to perform desired validation", "can't use key any more", "error in use of socket", "error in use POSIX signals", "nonce check failed", "couldn't read data", "couldn't write data", "error parsing data", "error encoding data", "error while using semaphores", "error while using pfkey", "error MKI present in packet is invalid", "packet index is too old to consider", "packet index advanced, reset needed", ] # SRTP_MAX_TAG_LEN + SRTP_MAX_MKI_LEN SRTP_MAX_TRAILER_LEN = 16 + 128 # SRTP_SRCTP_INDEX_LEN + SRTP_MAX_TAG_LEN + SRTP_MAX_MKI_LEN SRTP_MAX_SRTCP_TRAILER_LEN = 4 + 16 + 128 class Error(Exception): """ Error that occurred making a `libsrtp` API call. """ pass def _srtp_assert(rc): if rc != lib.srtp_err_status_ok: raise Error(ERRORS[rc]) class Policy: """ Policy for single SRTP stream. """ #: 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 SRTP_PROFILE_AES128_CM_SHA1_32 = lib.srtp_profile_aes128_cm_sha1_32 #: AES-128 GCM mode SRTP_PROFILE_AEAD_AES_128_GCM = lib.srtp_profile_aead_aes_128_gcm #: AES-256 GCM mode SRTP_PROFILE_AEAD_AES_256_GCM = lib.srtp_profile_aead_aes_256_gcm #: Indicates an undefined SSRC type SSRC_UNDEFINED = lib.ssrc_undefined #: Indicates a specific SSRC value SSRC_SPECIFIC = lib.ssrc_specific #: Indicates any inbound SSRC value SSRC_ANY_INBOUND = lib.ssrc_any_inbound #: Indicates any inbound SSRC value SSRC_ANY_OUTBOUND = lib.ssrc_any_outbound def __init__( self, key: Optional[bytes] = None, ssrc_type: int = SSRC_UNDEFINED, ssrc_value: int = 0, srtp_profile: int = SRTP_PROFILE_AES128_CM_SHA1_80, ) -> None: self._policy = ffi.new("srtp_policy_t *") self._srtp_profile = srtp_profile _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 ) ) self.key = key self.ssrc_type = ssrc_type self.ssrc_value = ssrc_value @property def allow_repeat_tx(self) -> bool: """ Whether retransmissions of packets with the same sequence number are allowed. """ return self._policy.allow_repeat_tx == 1 @allow_repeat_tx.setter def allow_repeat_tx(self, allow_repeat_tx: bool) -> None: self._policy.allow_repeat_tx = 1 if allow_repeat_tx else 0 @property def key(self) -> Optional[bytes]: """ The SRTP master key + master salt. """ if self.__cdata is None: return None return ffi.buffer(self.__cdata) @key.setter def key(self, key: Optional[bytes]) -> None: if key is None: # Clear the key. self.__cdata = None self._policy.key = ffi.NULL 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 not isinstance(key, bytes): raise TypeError("key must be bytes") if len(key) < expected_length: raise ValueError("key must contain at least %d bytes" % expected_length) self.__cdata = ffi.new("unsigned char[]", len(key)) self.__cdata[0 : len(key)] = key self._policy.key = self.__cdata @property def srtp_profile(self) -> int: """ The SRTP profile. """ return self._srtp_profile @property def ssrc_type(self) -> int: """ The SSRC type. """ return self._policy.ssrc.type @ssrc_type.setter def ssrc_type(self, ssrc_type: int) -> None: self._policy.ssrc.type = ssrc_type @property def ssrc_value(self) -> int: """ The SSRC value, if it is not a wildcard. """ return self._policy.ssrc.value @ssrc_value.setter def ssrc_value(self, ssrc_value: int) -> None: self._policy.ssrc.value = ssrc_value @property def window_size(self) -> int: """ The window size to use for replay protection. """ return self._policy.window_size @window_size.setter def window_size(self, window_size: int) -> None: self._policy.window_size = window_size class Session: """ SRTP session, which may comprise several streams. If `policy` is not specified, streams should be added later using the :func:`add_stream` method. """ def __init__(self, policy: Optional[Policy] = None) -> None: srtp = ffi.new("srtp_t *") if policy is None: _policy = ffi.NULL else: _policy = policy._policy _srtp_assert(lib.srtp_create(srtp, _policy)) self._cdata = ffi.new("char[]", 1500) self._buffer = ffi.buffer(self._cdata) self._srtp = ffi.gc(srtp, lambda x: lib.srtp_dealloc(x[0])) def add_stream(self, policy: Policy) -> None: """ Add a stream to the SRTP session, applying the given `policy` to the stream. :param policy: :class:`Policy` """ _srtp_assert(lib.srtp_add_stream(self._srtp[0], policy._policy)) def remove_stream(self, ssrc: int) -> None: """ Remove the stream with the given `ssrc` from the SRTP session. :param ssrc: :class:`int` """ _srtp_assert(lib.srtp_remove_stream(self._srtp[0], htonl(ssrc))) def protect(self, packet: bytes) -> bytes: """ Apply SRTP protection to the RTP `packet`. :param packet: :class:`bytes` :rtype: :class:`bytes` """ return self.__process(packet, lib.srtp_protect, SRTP_MAX_TRAILER_LEN) def protect_rtcp(self, packet: bytes) -> bytes: """ Apply SRTCP protection to the RTCP `packet`. :param packet: :class:`bytes` :rtype: :class:`bytes` """ return self.__process(packet, lib.srtp_protect_rtcp, SRTP_MAX_SRTCP_TRAILER_LEN) def unprotect(self, packet: bytes) -> bytes: """ Verify SRTP protection of the SRTP packet. :param packet: :class:`bytes` :rtype: :class:`bytes` """ return self.__process(packet, lib.srtp_unprotect) def unprotect_rtcp(self, packet: bytes) -> bytes: """ Verify SRTCP protection of the SRTCP packet. :param packet: :class:`bytes` :rtype: :class:`bytes` """ return self.__process(packet, lib.srtp_unprotect_rtcp) def __process(self, data, func, trailer=0): if not isinstance(data, bytes): raise TypeError("packet must be bytes") if len(data) > len(self._cdata) - trailer: raise ValueError("packet is too long") len_p = ffi.new("int *") len_p[0] = len(data) self._buffer[0 : len(data)] = data _srtp_assert(func(self._srtp[0], self._cdata, len_p)) return self._buffer[0 : len_p[0]] lib.srtp_init() ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1743942615.0 pylibsrtp-0.12.0/src/pylibsrtp/py.typed0000644000175100001660000000000714774471727017564 0ustar00runnerdockerMarker ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1743942621.1756337 pylibsrtp-0.12.0/src/pylibsrtp.egg-info/0000755000175100001660000000000014774471735017561 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1743942621.0 pylibsrtp-0.12.0/src/pylibsrtp.egg-info/PKG-INFO0000644000175100001660000000776714774471735020677 0ustar00runnerdockerMetadata-Version: 2.4 Name: pylibsrtp Version: 0.12.0 Summary: Python wrapper around the libsrtp library Author-email: Jeremy Lainé License: BSD-3-Clause Project-URL: homepage, https://github.com/aiortc/pylibsrtp Project-URL: documentation, https://pylibsrtp.readthedocs.io/ Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Programming Language :: Python :: 3.12 Classifier: Programming Language :: Python :: 3.13 Classifier: Topic :: Communications :: Telephony Classifier: Topic :: Security :: Cryptography Requires-Python: >=3.9 Description-Content-Type: text/x-rst License-File: LICENSE Requires-Dist: cffi>=1.0.0 Provides-Extra: dev Requires-Dist: coverage[toml]>=7.2.2; extra == "dev" Dynamic: license-file pylibsrtp ========= .. image:: https://img.shields.io/pypi/l/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp :alt: License .. image:: https://img.shields.io/pypi/v/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp :alt: Version .. image:: https://img.shields.io/pypi/pyversions/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp :alt: Python versions .. image:: https://github.com/aiortc/pylibsrtp/workflows/tests/badge.svg :target: https://github.com/aiortc/pylibsrtp/actions :alt: Tests .. image:: https://img.shields.io/codecov/c/github/aiortc/pylibsrtp.svg :target: https://codecov.io/gh/aiortc/pylibsrtp :alt: Coverage .. image:: https://readthedocs.org/projects/pylibsrtp/badge/?version=latest :target: https://pylibsrtp.readthedocs.io/ :alt: Documentation What is ``pylibsrtp``? ---------------------- ``pylibsrtp`` is a Python wrapper around `libsrtp`_, making it possible to encrypt and decrypt Secure Real-time Transport Protocol (SRTP) packets from Python code. SRTP is a profile of the Real-time Transport Protocol (RTP) which provides confidentiality, message authentication, and replay protection. It is defined by `RFC 3711`_. You can install ``pylibsrtp`` with ``pip``: .. code-block:: console $ pip install pylibsrtp To learn more about ``pylibsrtp`` please `read the documentation`_. .. _libsrtp: https://github.com/cisco/libsrtp .. _RFC 3711: https://tools.ietf.org/html/rfc3711 .. _read the documentation: https://pylibsrtp.readthedocs.io/en/stable/ Example ------- .. code:: python #!/usr/bin/env python from pylibsrtp import Policy, Session key = (b'\x00' * 30) rtp = b'\x80\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' + (b'\xd4' * 160) # protect RTP tx_policy = Policy(key=key, ssrc_type=Policy.SSRC_ANY_OUTBOUND) tx_session = Session(policy=tx_policy) srtp = tx_session.protect(rtp) # unprotect RTP rx_policy = Policy(key=key, ssrc_type=Policy.SSRC_ANY_INBOUND) rx_session = Session(policy=rx_policy) rtp2 = rx_session.unprotect(srtp) # check roundtrip worked! assert rtp2 == rtp Building pylibsrtp ------------------ If you wish to build pylibsrtp yourself, you will need libsrtp version 2.0 or better. Linux ..... On Debian/Ubuntu run: .. code-block:: console $ apt install libsrtp2-dev On Fedora/CentOS run: .. code-block:: console $ dnf install libsrtp-devel macOS ..... On macOS run: .. code-block:: console $ brew install srtp You will need to set some environment variables to link against libsrtp: .. code-block:: console export CFLAGS=-I$(brew --prefix openssl)/include -I$(brew --prefix srtp)/include export LDFLAGS=-L$(brew --prefix openssl)/lib -L$(brew --prefix srtp)/lib License ------- ``pylibsrtp`` is released under the `BSD license`_. .. _BSD license: https://pylibsrtp.readthedocs.io/en/stable/license.html ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1743942621.0 pylibsrtp-0.12.0/src/pylibsrtp.egg-info/SOURCES.txt0000644000175100001660000000065314774471735021451 0ustar00runnerdockerLICENSE MANIFEST.in README.rst pyproject.toml setup.py docs/Makefile docs/api.rst docs/conf.py docs/index.rst docs/license.rst src/_cffi_src/build_srtp.py src/pylibsrtp/__init__.py src/pylibsrtp/py.typed src/pylibsrtp.egg-info/PKG-INFO src/pylibsrtp.egg-info/SOURCES.txt src/pylibsrtp.egg-info/dependency_links.txt src/pylibsrtp.egg-info/requires.txt src/pylibsrtp.egg-info/top_level.txt tests/__init__.py tests/test_session.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1743942621.0 pylibsrtp-0.12.0/src/pylibsrtp.egg-info/dependency_links.txt0000644000175100001660000000000114774471735023627 0ustar00runnerdocker ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1743942621.0 pylibsrtp-0.12.0/src/pylibsrtp.egg-info/requires.txt0000644000175100001660000000005114774471735022155 0ustar00runnerdockercffi>=1.0.0 [dev] coverage[toml]>=7.2.2 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1743942621.0 pylibsrtp-0.12.0/src/pylibsrtp.egg-info/top_level.txt0000644000175100001660000000001214774471735022304 0ustar00runnerdockerpylibsrtp ././@PaxHeader0000000000000000000000000000003400000000000010212 xustar0028 mtime=1743942621.1756337 pylibsrtp-0.12.0/tests/0000755000175100001660000000000014774471735014412 5ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1743942615.0 pylibsrtp-0.12.0/tests/__init__.py0000644000175100001660000000000014774471727016512 0ustar00runnerdocker././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1743942615.0 pylibsrtp-0.12.0/tests/test_session.py0000644000175100001660000002321714774471727017514 0ustar00runnerdockerimport dataclasses import secrets from unittest import TestCase import pylibsrtp from pylibsrtp import Error, Policy, Session RTP = ( b"\x80\x08\x00\x00" # version, packet type, sequence number b"\x00\x00\x00\x00" # timestamp b"\x00\x00\x30\x39" # ssrc: 12345 ) + (b"\xd4" * 160) RTCP = ( b"\x80\xc8\x00\x06\xf3\xcb\x20\x01\x83\xab\x03\xa1\xeb\x02\x0b\x3a" b"\x00\x00\x94\x20\x00\x00\x00\x9e\x00\x00\x9b\x88" ) @dataclasses.dataclass class Profile: key_length: int protected_rtp_length: int protected_rtcp_length: int srtp_profile: int # AES-GCM may not be supported depending on how libsrtp2 was built. SRTP_PROFILES = [ Profile( key_length=30, protected_rtp_length=182, protected_rtcp_length=42, srtp_profile=Policy.SRTP_PROFILE_AES128_CM_SHA1_80, ), Profile( key_length=30, protected_rtp_length=176, protected_rtcp_length=42, srtp_profile=Policy.SRTP_PROFILE_AES128_CM_SHA1_32, ), ] try: Policy(srtp_profile=Policy.SRTP_PROFILE_AEAD_AES_128_GCM) except Error: pass else: SRTP_PROFILES += [ Profile( key_length=28, protected_rtp_length=188, protected_rtcp_length=48, srtp_profile=Policy.SRTP_PROFILE_AEAD_AES_128_GCM, ), Profile( key_length=44, protected_rtp_length=188, protected_rtcp_length=48, srtp_profile=Policy.SRTP_PROFILE_AEAD_AES_256_GCM, ), ] class PolicyTest(TestCase): def test_allow_repeat_tx(self): policy = Policy() self.assertEqual(policy.allow_repeat_tx, False) policy.allow_repeat_tx = True self.assertEqual(policy.allow_repeat_tx, True) policy.allow_repeat_tx = False self.assertEqual(policy.allow_repeat_tx, False) policy.allow_repeat_tx = 1 self.assertEqual(policy.allow_repeat_tx, True) policy.allow_repeat_tx = 0 self.assertEqual(policy.allow_repeat_tx, False) def test_key(self): key = secrets.token_bytes(30) policy = Policy() self.assertEqual(policy.key, None) policy.key = key self.assertEqual(policy.key, key) policy.key = None self.assertEqual(policy.key, None) # Key is not bytes. with self.assertRaises(TypeError) as cm: policy.key = 1234 self.assertEqual(policy.key, None) self.assertEqual(str(cm.exception), "key must be bytes") # Key is too short. with self.assertRaises(ValueError) as cm: policy.key = b"0" self.assertEqual(policy.key, None) self.assertEqual(str(cm.exception), "key must contain at least 30 bytes") def test_srtp_policy(self): # Default profile. policy = Policy() self.assertEqual(policy.srtp_profile, Policy.SRTP_PROFILE_AES128_CM_SHA1_80) # Valid user-specified profiles. for profile in SRTP_PROFILES: with self.subTest(profile=profile): policy = Policy(srtp_profile=profile.srtp_profile) self.assertEqual(policy.srtp_profile, profile.srtp_profile) # Invalid profile. with self.assertRaises(Error) as cm: Policy(srtp_profile=0) self.assertEqual(str(cm.exception), "unsupported parameter") def test_ssrc_type(self): policy = Policy() self.assertEqual(policy.ssrc_type, Policy.SSRC_UNDEFINED) policy.ssrc_type = Policy.SSRC_ANY_INBOUND self.assertEqual(policy.ssrc_type, Policy.SSRC_ANY_INBOUND) def test_ssrc_value(self): policy = Policy() self.assertEqual(policy.ssrc_value, 0) policy.ssrc_value = 12345 self.assertEqual(policy.ssrc_value, 12345) def test_window_size(self): policy = Policy() self.assertEqual(policy.window_size, 0) policy.window_size = 1024 self.assertEqual(policy.window_size, 1024) class SessionTest(TestCase): def test_no_key(self): policy = Policy(ssrc_type=Policy.SSRC_ANY_OUTBOUND) with self.assertRaises(Error) as cm: Session(policy=policy) self.assertEqual(str(cm.exception), "unsupported parameter") def test_add_remove_stream(self): for profile in SRTP_PROFILES: with self.subTest(profile=profile): key = secrets.token_bytes(profile.key_length) # protect RTP tx_session = Session( policy=Policy( key=key, srtp_profile=profile.srtp_profile, ssrc_type=Policy.SSRC_SPECIFIC, ssrc_value=12345, ) ) protected = tx_session.protect(RTP) self.assertEqual(len(protected), profile.protected_rtp_length) # add stream and unprotect RTP rx_session = Session() rx_session.add_stream( Policy( key=key, srtp_profile=profile.srtp_profile, ssrc_type=Policy.SSRC_SPECIFIC, ssrc_value=12345, ) ) unprotected = rx_session.unprotect(protected) self.assertEqual(unprotected, RTP) # remove stream rx_session.remove_stream(12345) # try removing stream again with self.assertRaises(Error) as cm: rx_session.remove_stream(12345) self.assertEqual(str(cm.exception), "no appropriate context found") def test_rtp_any_ssrc(self): for profile in SRTP_PROFILES: with self.subTest(profile=profile): key = secrets.token_bytes(profile.key_length) # protect RTP tx_session = Session( policy=Policy( key=key, srtp_profile=profile.srtp_profile, ssrc_type=Policy.SSRC_ANY_OUTBOUND, ) ) protected = tx_session.protect(RTP) self.assertEqual(len(protected), profile.protected_rtp_length) # bad type with self.assertRaises(TypeError) as cm: tx_session.protect(4567) self.assertEqual(str(cm.exception), "packet must be bytes") # bad length with self.assertRaises(ValueError) as cm: tx_session.protect(b"0" * (1501 - pylibsrtp.SRTP_MAX_TRAILER_LEN)) self.assertEqual(str(cm.exception), "packet is too long") # unprotect RTP rx_session = Session( policy=Policy( key=key, srtp_profile=profile.srtp_profile, ssrc_type=Policy.SSRC_ANY_INBOUND, ) ) unprotected = rx_session.unprotect(protected) self.assertEqual(unprotected, RTP) def test_rtcp_any_ssrc(self): for profile in SRTP_PROFILES: with self.subTest(profile=profile): key = secrets.token_bytes(profile.key_length) # protect RCTP tx_session = Session( policy=Policy( key=key, srtp_profile=profile.srtp_profile, ssrc_type=Policy.SSRC_ANY_OUTBOUND, ) ) protected = tx_session.protect_rtcp(RTCP) self.assertEqual(len(protected), profile.protected_rtcp_length) # bad type with self.assertRaises(TypeError) as cm: tx_session.protect_rtcp(4567) self.assertEqual(str(cm.exception), "packet must be bytes") # bad length with self.assertRaises(ValueError) as cm: tx_session.protect_rtcp( b"0" * (1501 - pylibsrtp.SRTP_MAX_SRTCP_TRAILER_LEN) ) self.assertEqual(str(cm.exception), "packet is too long") # unprotect RTCP rx_session = Session( policy=Policy( key=key, srtp_profile=profile.srtp_profile, ssrc_type=Policy.SSRC_ANY_INBOUND, ) ) unprotected = rx_session.unprotect_rtcp(protected) self.assertEqual(unprotected, RTCP) def test_rtp_specific_ssrc(self): for profile in SRTP_PROFILES: with self.subTest(profile=profile): key = secrets.token_bytes(profile.key_length) # protect RTP tx_session = Session( policy=Policy( key=key, srtp_profile=profile.srtp_profile, ssrc_type=Policy.SSRC_SPECIFIC, ssrc_value=12345, ) ) protected = tx_session.protect(RTP) self.assertEqual(len(protected), profile.protected_rtp_length) # unprotect RTP rx_session = Session( policy=Policy( key=key, srtp_profile=profile.srtp_profile, ssrc_type=Policy.SSRC_SPECIFIC, ssrc_value=12345, ) ) unprotected = rx_session.unprotect(protected) self.assertEqual(unprotected, RTP)