pylibsrtp-0.8.0/0000755000175100001730000000000014362777600014364 5ustar runnerdocker00000000000000pylibsrtp-0.8.0/LICENSE0000644000175100001730000000274614362777572015412 0ustar runnerdocker00000000000000Copyright (c) 2018 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. pylibsrtp-0.8.0/MANIFEST.in0000644000175100001730000000017514362777572016135 0ustar runnerdocker00000000000000include LICENSE recursive-include docs *.py *.rst Makefile recursive-include src/_cffi_src *.py recursive-include tests *.py pylibsrtp-0.8.0/PKG-INFO0000644000175100001730000001066714362777600015473 0ustar runnerdocker00000000000000Metadata-Version: 1.1 Name: pylibsrtp Version: 0.8.0 Summary: Python wrapper around the libsrtp library Home-page: https://github.com/aiortc/pylibsrtp Author: Jeremy Lainé Author-email: jeremy.laine@m4x.org License: BSD Description: pylibsrtp ========= |rtd| |pypi-v| |pypi-pyversions| |pypi-l| |pypi-wheel| |tests| |codecov| .. |rtd| image:: https://readthedocs.org/projects/pylibsrtp/badge/?version=latest :target: https://pylibsrtp.readthedocs.io/ .. |pypi-v| image:: https://img.shields.io/pypi/v/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp .. |pypi-pyversions| image:: https://img.shields.io/pypi/pyversions/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp .. |pypi-l| image:: https://img.shields.io/pypi/l/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp .. |pypi-wheel| image:: https://img.shields.io/pypi/wheel/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp .. |tests| image:: https://github.com/aiortc/pylibsrtp/workflows/tests/badge.svg :target: https://github.com/aiortc/pylibsrtp/actions .. |codecov| image:: https://img.shields.io/codecov/c/github/aiortc/pylibsrtp.svg :target: https://codecov.io/gh/aiortc/pylibsrtp 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. On Debian/Ubuntu run: .. code-block:: console $ apt install libsrtp2-dev On Fedora/CentOS run: .. code-block:: console $ dnf install libsrtp-devel On OS X run: .. code-block:: console $ brew install srtp License ------- ``pylibsrtp`` is released under the `BSD license`_. .. _BSD license: https://pylibsrtp.readthedocs.io/en/stable/license.html Platform: UNKNOWN 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.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Topic :: Communications :: Telephony Classifier: Topic :: Security :: Cryptography pylibsrtp-0.8.0/README.rst0000644000175100001730000000536614362777572016075 0ustar runnerdocker00000000000000pylibsrtp ========= |rtd| |pypi-v| |pypi-pyversions| |pypi-l| |pypi-wheel| |tests| |codecov| .. |rtd| image:: https://readthedocs.org/projects/pylibsrtp/badge/?version=latest :target: https://pylibsrtp.readthedocs.io/ .. |pypi-v| image:: https://img.shields.io/pypi/v/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp .. |pypi-pyversions| image:: https://img.shields.io/pypi/pyversions/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp .. |pypi-l| image:: https://img.shields.io/pypi/l/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp .. |pypi-wheel| image:: https://img.shields.io/pypi/wheel/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp .. |tests| image:: https://github.com/aiortc/pylibsrtp/workflows/tests/badge.svg :target: https://github.com/aiortc/pylibsrtp/actions .. |codecov| image:: https://img.shields.io/codecov/c/github/aiortc/pylibsrtp.svg :target: https://codecov.io/gh/aiortc/pylibsrtp 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. On Debian/Ubuntu run: .. code-block:: console $ apt install libsrtp2-dev On Fedora/CentOS run: .. code-block:: console $ dnf install libsrtp-devel On OS X run: .. code-block:: console $ brew install srtp License ------- ``pylibsrtp`` is released under the `BSD license`_. .. _BSD license: https://pylibsrtp.readthedocs.io/en/stable/license.html pylibsrtp-0.8.0/docs/0000755000175100001730000000000014362777600015314 5ustar runnerdocker00000000000000pylibsrtp-0.8.0/docs/Makefile0000644000175100001730000000113614362777572016765 0ustar runnerdocker00000000000000# 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)pylibsrtp-0.8.0/docs/api.rst0000644000175100001730000000024514362777572016630 0ustar runnerdocker00000000000000API Reference ============= .. automodule:: pylibsrtp .. autoclass:: Error .. autoclass:: Policy :members: .. autoclass:: Session :members: pylibsrtp-0.8.0/docs/conf.py0000644000175100001730000001254414362777572016631 0ustar runnerdocker00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # pylibsrtp documentation build configuration file, created by # sphinx-quickstart on Thu Feb 8 17:22:14 2018. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import os, sys # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. sys.path.insert(0, os.path.abspath('..')) # Mock out binding class MockLib: ssrc_undefined = 0 ssrc_specific = 1 ssrc_any_inbound = 2 ssrc_any_outbound = 3 def srtp_init(self): pass class MockBinding: ffi = None lib = MockLib() sys.modules.update({'pylibsrtp._binding': MockBinding()}) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. # # needs_sphinx = '1.0' # 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 suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] source_suffix = '.rst' # The master toctree document. master_doc = 'index' # General information about the project. project = 'pylibsrtp' copyright = u'2018, Jeremy Lainé' author = u'Jeremy Lainé' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '' # The full version, including alpha/beta/rc tags. release = '' # 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 = None # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # -- 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 = { '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', ] } # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. htmlhelp_basename = 'pylibsrtpdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # # 'preamble': '', # Latex figure (float) alignment # # 'figure_align': 'htbp', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, 'pylibsrtp.tex', 'pylibsrtp Documentation', u'Jeremy Lainé', 'manual'), ] # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ (master_doc, 'pylibsrtp', 'pylibsrtp Documentation', [author], 1) ] # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'pylibsrtp', 'pylibsrtp Documentation', author, 'pylibsrtp', 'One line description of project.', 'Miscellaneous'), ] pylibsrtp-0.8.0/docs/index.rst0000644000175100001730000000263514362777572017173 0ustar runnerdocker00000000000000pylibsrtp ========= |pypi-v| |pypi-pyversions| |pypi-l| |pypi-wheel| |tests| |codecov| .. |pypi-v| image:: https://img.shields.io/pypi/v/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp .. |pypi-pyversions| image:: https://img.shields.io/pypi/pyversions/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp .. |pypi-l| image:: https://img.shields.io/pypi/l/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp .. |pypi-wheel| image:: https://img.shields.io/pypi/wheel/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp .. |tests| image:: https://github.com/aiortc/pylibsrtp/workflows/tests/badge.svg :target: https://github.com/aiortc/pylibsrtp/actions .. |codecov| image:: https://img.shields.io/codecov/c/github/aiortc/pylibsrtp.svg :target: https://codecov.io/gh/aiortc/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 .. _libsrtp: https://github.com/cisco/libsrtp .. _RFC 3711: https://tools.ietf.org/html/rfc3711 .. toctree:: :maxdepth: 2 api license pylibsrtp-0.8.0/docs/license.rst0000644000175100001730000000006014362777572017474 0ustar runnerdocker00000000000000License ------- .. literalinclude:: ../LICENSE pylibsrtp-0.8.0/pyproject.toml0000644000175100001730000000010114362777572017300 0ustar runnerdocker00000000000000[build-system] requires = ["cffi>=1.0.0", "setuptools", "wheel"] pylibsrtp-0.8.0/setup.cfg0000644000175100001730000000016714362777600016211 0ustar runnerdocker00000000000000[coverage:run] source = pylibsrtp [flake8] ignore = E203 max-line-length = 100 [egg_info] tag_build = tag_date = 0 pylibsrtp-0.8.0/setup.py0000644000175100001730000000320214362777572016103 0ustar runnerdocker00000000000000import os.path import sys import setuptools root_dir = os.path.abspath(os.path.dirname(__file__)) about = {} about_file = os.path.join(root_dir, "src", "pylibsrtp", "about.py") with open(about_file, encoding="utf-8") as fp: exec(fp.read(), about) readme_file = os.path.join(root_dir, "README.rst") with open(readme_file, encoding="utf-8") as f: long_description = f.read() if os.environ.get("READTHEDOCS") == "True": cffi_modules = [] else: cffi_modules = ["src/_cffi_src/build_srtp.py:ffibuilder"] setuptools.setup( name=about["__title__"], version=about["__version__"], description=about["__summary__"], long_description=long_description, url=about["__uri__"], author=about["__author__"], author_email=about["__email__"], license=about["__license__"], 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.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Topic :: Communications :: Telephony", "Topic :: Security :: Cryptography", ], cffi_modules=cffi_modules, package_dir={"": "src"}, package_data={"pylibsrtp": ["py.typed"]}, packages=["pylibsrtp"], install_requires=["cffi>=1.0.0"], setup_requires=["cffi>=1.0.0"], ) pylibsrtp-0.8.0/src/0000755000175100001730000000000014362777600015153 5ustar runnerdocker00000000000000pylibsrtp-0.8.0/src/_cffi_src/0000755000175100001730000000000014362777600017070 5ustar runnerdocker00000000000000pylibsrtp-0.8.0/src/_cffi_src/build_srtp.py0000644000175100001730000000437314362777572021630 0ustar runnerdocker00000000000000import sys from cffi import FFI libraries = ["srtp2"] if sys.platform == "win32": libraries += ["ws2_32"] 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) pylibsrtp-0.8.0/src/pylibsrtp/0000755000175100001730000000000014362777600017203 5ustar runnerdocker00000000000000pylibsrtp-0.8.0/src/pylibsrtp/__init__.py0000644000175100001730000001743114362777572021332 0ustar runnerdocker00000000000000from socket import htonl from typing import Optional from ._binding import ffi, lib from .about import __version__ __all__ = ["Error", "Policy", "Session", "__version__"] 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_TRAILER_LEN = 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_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() pylibsrtp-0.8.0/src/pylibsrtp/about.py0000644000175100001730000000035414362777572020701 0ustar runnerdocker00000000000000__author__ = "Jeremy Lainé" __email__ = "jeremy.laine@m4x.org" __license__ = "BSD" __summary__ = "Python wrapper around the libsrtp library" __title__ = "pylibsrtp" __uri__ = "https://github.com/aiortc/pylibsrtp" __version__ = "0.8.0" pylibsrtp-0.8.0/src/pylibsrtp/py.typed0000644000175100001730000000000714362777572020707 0ustar runnerdocker00000000000000Marker pylibsrtp-0.8.0/src/pylibsrtp.egg-info/0000755000175100001730000000000014362777600020675 5ustar runnerdocker00000000000000pylibsrtp-0.8.0/src/pylibsrtp.egg-info/PKG-INFO0000644000175100001730000001066714362777577022021 0ustar runnerdocker00000000000000Metadata-Version: 1.1 Name: pylibsrtp Version: 0.8.0 Summary: Python wrapper around the libsrtp library Home-page: https://github.com/aiortc/pylibsrtp Author: Jeremy Lainé Author-email: jeremy.laine@m4x.org License: BSD Description: pylibsrtp ========= |rtd| |pypi-v| |pypi-pyversions| |pypi-l| |pypi-wheel| |tests| |codecov| .. |rtd| image:: https://readthedocs.org/projects/pylibsrtp/badge/?version=latest :target: https://pylibsrtp.readthedocs.io/ .. |pypi-v| image:: https://img.shields.io/pypi/v/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp .. |pypi-pyversions| image:: https://img.shields.io/pypi/pyversions/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp .. |pypi-l| image:: https://img.shields.io/pypi/l/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp .. |pypi-wheel| image:: https://img.shields.io/pypi/wheel/pylibsrtp.svg :target: https://pypi.python.org/pypi/pylibsrtp .. |tests| image:: https://github.com/aiortc/pylibsrtp/workflows/tests/badge.svg :target: https://github.com/aiortc/pylibsrtp/actions .. |codecov| image:: https://img.shields.io/codecov/c/github/aiortc/pylibsrtp.svg :target: https://codecov.io/gh/aiortc/pylibsrtp 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. On Debian/Ubuntu run: .. code-block:: console $ apt install libsrtp2-dev On Fedora/CentOS run: .. code-block:: console $ dnf install libsrtp-devel On OS X run: .. code-block:: console $ brew install srtp License ------- ``pylibsrtp`` is released under the `BSD license`_. .. _BSD license: https://pylibsrtp.readthedocs.io/en/stable/license.html Platform: UNKNOWN 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.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Topic :: Communications :: Telephony Classifier: Topic :: Security :: Cryptography pylibsrtp-0.8.0/src/pylibsrtp.egg-info/SOURCES.txt0000644000175100001730000000071414362777600022563 0ustar runnerdocker00000000000000LICENSE MANIFEST.in README.rst pyproject.toml setup.cfg 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/about.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.pypylibsrtp-0.8.0/src/pylibsrtp.egg-info/dependency_links.txt0000644000175100001730000000000114362777577024760 0ustar runnerdocker00000000000000 pylibsrtp-0.8.0/src/pylibsrtp.egg-info/requires.txt0000644000175100001730000000001414362777577023305 0ustar runnerdocker00000000000000cffi>=1.0.0 pylibsrtp-0.8.0/src/pylibsrtp.egg-info/top_level.txt0000644000175100001730000000001214362777577023435 0ustar runnerdocker00000000000000pylibsrtp pylibsrtp-0.8.0/tests/0000755000175100001730000000000014362777600015526 5ustar runnerdocker00000000000000pylibsrtp-0.8.0/tests/__init__.py0000644000175100001730000000000014362777572017635 0ustar runnerdocker00000000000000pylibsrtp-0.8.0/tests/test_session.py0000644000175100001730000002300414362777572020631 0ustar runnerdocker00000000000000import dataclasses import secrets from unittest import TestCase 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" * 1500) 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" * 1500) 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)