diff --git a/.github/workflows/libsrtp__build.yml b/.github/workflows/libsrtp__build.yml new file mode 100644 index 0000000000..0e97bc48cf --- /dev/null +++ b/.github/workflows/libsrtp__build.yml @@ -0,0 +1,58 @@ +name: "libsrtp: build-tests" + +on: + push: + branches: + - master + pull_request: + types: [opened, synchronize, reopened, labeled] + +jobs: + build_libsrtp: + if: contains(github.event.pull_request.labels.*.name, 'libsrtp') || github.event_name == 'push' + name: libsrtp build + strategy: + fail-fast: false + matrix: + idf_ver: ["release-v5.4", "release-v5.5"] + app: + - { name: "get_started", path: "components/libsrtp/examples/get_started" } + - { name: "test_apps", path: "components/libsrtp/test_apps" } + runs-on: ubuntu-22.04 + container: espressif/idf:${{ matrix.idf_ver }} + steps: + - name: Checkout esp-protocols + uses: actions/checkout@v4 + with: + submodules: recursive + - name: Build ${{ matrix.app.name }} with IDF-${{ matrix.idf_ver }} + shell: bash + run: | + . ${IDF_PATH}/export.sh + python -m pip install idf-build-apps + python ./ci/build_apps.py -c ${{ matrix.app.path }} -m components/libsrtp/.build-test-rules.yml + + host_test_libsrtp: + if: contains(github.event.pull_request.labels.*.name, 'libsrtp') || github.event_name == 'push' + name: libsrtp host test + strategy: + fail-fast: false + matrix: + idf_ver: ["release-v5.4", "release-v5.5"] + runs-on: ubuntu-22.04 + container: espressif/idf:${{ matrix.idf_ver }} + steps: + - name: Checkout esp-protocols + uses: actions/checkout@v4 + with: + submodules: recursive + - name: Build + run host_test with IDF-${{ matrix.idf_ver }} + shell: bash + run: | + . ${IDF_PATH}/export.sh + python -m pip install idf-build-apps + python ./ci/build_apps.py -c components/libsrtp/host_test -m components/libsrtp/.build-test-rules.yml + cd components/libsrtp/host_test + for dir in $(ls -d build_linux_* 2>/dev/null); do + "./$dir/host_test_libsrtp.elf" + done diff --git a/.gitmodules b/.gitmodules index d1d5c9c874..9d3fb1d2e5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "components/libwebsockets/libwebsockets"] path = components/libwebsockets/libwebsockets url = https://github.com/warmcat/libwebsockets.git +[submodule "components/libsrtp/libsrtp"] + path = components/libsrtp/libsrtp + url = https://github.com/cisco/libsrtp.git diff --git a/ci/check_copyright_ignore.txt b/ci/check_copyright_ignore.txt index 1cd8798f5b..d094b200f0 100644 --- a/ci/check_copyright_ignore.txt +++ b/ci/check_copyright_ignore.txt @@ -1 +1,2 @@ components/mosquitto/examples/serverless_mqtt/components/libjuice/port/juice_random.c +components/libsrtp/port/crypto_kernel.c diff --git a/components/libsrtp/.build-test-rules.yml b/components/libsrtp/.build-test-rules.yml new file mode 100644 index 0000000000..846d727189 --- /dev/null +++ b/components/libsrtp/.build-test-rules.yml @@ -0,0 +1,42 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 +# +# libsrtp requires ESP-IDF 5.4+ (idf_component.yml `idf: ">=5.4"`). The +# disable rules below skip the build on older IDF releases so the repo-wide +# CI matrix (v5.1 / v5.2 / v5.3 / v5.4 / v5.5 / latest) doesn't try them. +# +# IDF v6+ is also disabled for now: libsrtp 2.x's mbedTLS adapters +# (aes_gcm_mbedtls.c, aes_icm_mbedtls.c, hmac_mbedtls.c) include the +# classic / headers which moved under the +# TF-PSA-Crypto split in mbedTLS 4.x (shipped by IDF v6+). Re-enable once +# libsrtp adapts or we ship a port-side compatibility shim. + +components/libsrtp/examples/get_started: + enable: + - if: IDF_TARGET in ["esp32", "esp32c3", "esp32s3", "esp32c5", "esp32c6", "esp32p4"] + reason: "Build sanity on representative Xtensa + RISC-V targets." + disable: + - if: IDF_VERSION_MAJOR <= 5 and IDF_VERSION_MINOR < 4 + reason: "libsrtp requires ESP-IDF 5.4+." + - if: IDF_VERSION_MAJOR >= 6 + reason: "libsrtp 2.x mbedTLS adapters not yet ported to mbedTLS 4.x (TF-PSA-Crypto split)." + +components/libsrtp/test_apps: + enable: + - if: IDF_TARGET in ["esp32", "esp32c3"] + reason: "Sufficient to exercise one Xtensa and one RISC-V target." + disable: + - if: IDF_VERSION_MAJOR <= 5 and IDF_VERSION_MINOR < 4 + reason: "libsrtp requires ESP-IDF 5.4+." + - if: IDF_VERSION_MAJOR >= 6 + reason: "libsrtp 2.x mbedTLS adapters not yet ported to mbedTLS 4.x (TF-PSA-Crypto split)." + +components/libsrtp/host_test: + enable: + - if: IDF_TARGET == "linux" + reason: "Linux host build runs the SRTP roundtrip smoke test." + disable: + - if: IDF_VERSION_MAJOR <= 5 and IDF_VERSION_MINOR < 4 + reason: "libsrtp requires ESP-IDF 5.4+." + - if: IDF_VERSION_MAJOR >= 6 + reason: "libsrtp 2.x mbedTLS adapters not yet ported to mbedTLS 4.x (TF-PSA-Crypto split)." diff --git a/components/libsrtp/.cz.yaml b/components/libsrtp/.cz.yaml new file mode 100644 index 0000000000..af01be63fc --- /dev/null +++ b/components/libsrtp/.cz.yaml @@ -0,0 +1,8 @@ +--- +commitizen: + bump_message: 'bump(libsrtp): $current_version -> $new_version' + pre_bump_hooks: python ../../ci/changelog.py libsrtp + tag_format: libsrtp-v$version + version: 2.8.0 + version_files: + - idf_component.yml diff --git a/components/libsrtp/CMakeLists.txt b/components/libsrtp/CMakeLists.txt new file mode 100644 index 0000000000..b6023bbb9f --- /dev/null +++ b/components/libsrtp/CMakeLists.txt @@ -0,0 +1,79 @@ +# SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD +# +# SPDX-License-Identifier: Apache-2.0 + +set(SRTP_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/libsrtp") + +# Vendored port-side source — replaces the upstream crypto/kernel/crypto_kernel.c +# (which we exclude from SRCS below). The only delta vs upstream is opting out +# of the AES-ICM-192 cipher registration when GCM is enabled, to save binary +# size — AES-CM-128 + AES-GCM cover all WebRTC SRTP suites in use. +# +# Bundled into port/ rather than applied as a `git apply` patch at configure +# time, so the wrapper works cleanly when installed via the IDF Component +# Registry (the registry tarball has no .git dir for `git apply` to operate +# on). Re-port from upstream when bumping the libsrtp submodule. +set(SRTP_PORT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/port") + +idf_component_register( + SRCS "${SRTP_ROOT}/srtp/srtp.c" + "${SRTP_ROOT}/crypto/cipher/aes_gcm_mbedtls.c" + "${SRTP_ROOT}/crypto/cipher/aes_icm_mbedtls.c" + "${SRTP_ROOT}/crypto/cipher/aes_icm.c" + "${SRTP_ROOT}/crypto/cipher/aes.c" + "${SRTP_ROOT}/crypto/cipher/cipher.c" + "${SRTP_ROOT}/crypto/cipher/cipher_test_cases.c" + "${SRTP_ROOT}/crypto/cipher/null_cipher.c" + "${SRTP_ROOT}/crypto/hash/auth.c" + "${SRTP_ROOT}/crypto/hash/hmac.c" + "${SRTP_ROOT}/crypto/hash/hmac_mbedtls.c" + "${SRTP_ROOT}/crypto/hash/auth_test_cases.c" + "${SRTP_ROOT}/crypto/hash/null_auth.c" + "${SRTP_ROOT}/crypto/hash/sha1.c" + "${SRTP_PORT_DIR}/crypto_kernel.c" # replaces ${SRTP_ROOT}/crypto/kernel/crypto_kernel.c + "${SRTP_ROOT}/crypto/kernel/alloc.c" + "${SRTP_ROOT}/crypto/kernel/err.c" + "${SRTP_ROOT}/crypto/kernel/key.c" + "${SRTP_ROOT}/crypto/math/datatypes.c" + "${SRTP_ROOT}/crypto/replay/rdb.c" + "${SRTP_ROOT}/crypto/replay/rdbx.c" + + PRIV_INCLUDE_DIRS + "${SRTP_ROOT}/crypto/include" + "${CMAKE_CURRENT_SOURCE_DIR}/port" + INCLUDE_DIRS + "${SRTP_ROOT}/include" # upstream API headers (srtp.h, ekt.h, ...) + "${CMAKE_CURRENT_SOURCE_DIR}/include" # srtp2/ namespace shim so consumers can still + + REQUIRES "mbedtls" +) + +# Fully suppress (not just downgrade) warnings from third-party libsrtp +# sources. The repo-wide CI pipeline runs idf-build-apps with strict +# warning checking, so -Wno-error=* alone (which keeps the warning +# printed) trips the matrix on otherwise-valid builds. We can't fix the +# warnings upstream, so silence them here. Apple clang on IDF's Linux +# target does not recognise -Wno-conflicting-types, so probe first. +include(CheckCCompilerFlag) +check_c_compiler_flag("-Wno-conflicting-types" SRTP2_HAS_NO_CONFLICTING_TYPES) + +target_compile_options(${COMPONENT_LIB} PRIVATE + -Wno-format + -Wno-unused-function + -Wno-unused-variable + -Wno-unused-but-set-variable + -Wno-sign-compare + -Wno-type-limits + -Wno-pointer-sign + -Wno-implicit-function-declaration + -Wno-strict-prototypes + -Wno-old-style-definition + -Wno-incompatible-pointer-types +) +if(SRTP2_HAS_NO_CONFLICTING_TYPES) + target_compile_options(${COMPONENT_LIB} PRIVATE -Wno-conflicting-types) +endif() + +target_compile_definitions(${COMPONENT_LIB} PUBLIC + -DHAVE_CONFIG_H +) diff --git a/components/libsrtp/Kconfig b/components/libsrtp/Kconfig new file mode 100644 index 0000000000..df736b99eb --- /dev/null +++ b/components/libsrtp/Kconfig @@ -0,0 +1,14 @@ +menu "libsrtp (libsrtp)" + + # The ESP-IDF port wires libsrtp's crypto exclusively through mbedTLS + # (AES-CM, AES-GCM, HMAC-SHA1). No Kconfig knob — the alternative + # backends (OpenSSL, NSS, WolfSSL) aren't built in this component. + + config SRTP2_LOG_RTP_TRACE + bool "Enable verbose RTP trace logging" + default n + help + Enables libsrtp's internal debug-trace logging at runtime + (extra binary size; meant only for development). + +endmenu diff --git a/components/libsrtp/LICENSE b/components/libsrtp/LICENSE new file mode 100644 index 0000000000..def753ae6d --- /dev/null +++ b/components/libsrtp/LICENSE @@ -0,0 +1,187 @@ +This component is dual-licensed: + + - Apache-2.0 — the ESP-IDF wrapper code in this component (port/, include/, + Kconfig, CMakeLists.txt, host_test/, examples/). Full text below. + + - BSD-3-Clause — the bundled libsrtp upstream sources under libsrtp/. + See libsrtp/LICENSE for the full text. + +Third-party attribution is in NOTICE. + +================================================================================ + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. diff --git a/components/libsrtp/NOTICE b/components/libsrtp/NOTICE new file mode 100644 index 0000000000..9e6d5056d7 --- /dev/null +++ b/components/libsrtp/NOTICE @@ -0,0 +1,17 @@ +libsrtp +Copyright 2024-2026 Espressif Systems (Shanghai) CO LTD + +This product includes software developed by the following third parties: + +================================================================================ + +libsrtp +https://github.com/cisco/libsrtp +Copyright (c) 2001-2017, Cisco Systems, Inc. +Licensed under the BSD 3-Clause License (see libsrtp/LICENSE). + +The Secure Real-time Transport Protocol (SRTP) and Secure RTCP (SRTCP) +implementation. Bundled as a submodule, pinned at upstream release tag +v2.8.0 (commit 24b3bf8). + +================================================================================ diff --git a/components/libsrtp/README.md b/components/libsrtp/README.md new file mode 100644 index 0000000000..8cbbd63440 --- /dev/null +++ b/components/libsrtp/README.md @@ -0,0 +1,46 @@ +# libsrtp + +[![Component Registry](https://components.espressif.com/components/espressif/libsrtp/badge.svg)](https://components.espressif.com/components/espressif/libsrtp) + +[libsrtp](https://github.com/cisco/libsrtp) (Cisco) port for ESP-IDF using the mbedtls crypto backend with AES-GCM. + +Used for SRTP/SRTCP packet protection in WebRTC and other RTP-based protocols on ESP32-series chips. + +> **Not the same as `espressif/esp_libsrtp`.** The existing `espressif/esp_libsrtp` component is a pre-built closed-source libSRTP binary distributed via `esp-adf-libs` (Custom license). `libsrtp` is an open-source wrapper around upstream [`cisco/libsrtp` 2.x](https://github.com/cisco/libsrtp) — built from source as part of your project, BSD-3-Clause upstream + Apache-2.0 port code. Pick `libsrtp` when you need the source-built, open-source path (e.g. for WebRTC integrations); pick `esp_libsrtp` when you're already on the ADF binary stack. + +## Features + +- libsrtp [`v2.8.0`](https://github.com/cisco/libsrtp/releases/tag/v2.8.0) (commit `24b3bf8`) +- mbedtls crypto backend (AES-CM, AES-GCM, HMAC-SHA1) — IDF's mbedTLS routes AES through the on-chip AES peripheral when `CONFIG_MBEDTLS_HARDWARE_AES=y` (default) +- AEAD profiles: `AEAD_AES_128_GCM`, `AEAD_AES_256_GCM` +- SRTP profiles: `AES_CM_128_HMAC_SHA1_80`, `AES_CM_128_HMAC_SHA1_32` + +## Add to a project + +```bash +idf.py add-dependency "espressif/libsrtp^2.8.0" +``` + +Or in your project's `main/idf_component.yml`: + +```yaml +dependencies: + espressif/libsrtp: "^2.8.0" +``` + +Requires ESP-IDF ≥ 5.4. + +## Tests + +- `test_apps/` — embedded Unity smoke test (init/shutdown + version). Run with + `pytest_libsrtp.py` against `esp32` / `esp32c3`. +- `host_test/` — IDF Linux-target binary that performs an AES-GCM-128 + protect/unprotect roundtrip. Run with `pytest_libsrtp_linux.py`. + +## Source + +This wrapper bundles [cisco/libsrtp](https://github.com/cisco/libsrtp) as a git submodule pinned at the upstream release tag. All SRTP cryptographic implementation is upstream; this repo adds only the ESP-IDF build glue (`CMakeLists.txt`, `port/config.h`) plus one small port-side delta in `port/crypto_kernel.c` (replaces the upstream `crypto/kernel/crypto_kernel.c`) to opt out of the AES-ICM-192 cipher registration when GCM is enabled. Re-port from upstream when bumping the libsrtp submodule. + +## License + +`Apache-2.0 AND BSD-3-Clause` — the ESP-IDF port glue under this repo (`CMakeLists.txt`, `port/`, `Kconfig`, build scripts) is Apache-2.0 (see `LICENSE`); the bundled `libsrtp/` submodule remains under upstream's BSD-3-Clause (see `libsrtp/LICENSE`). diff --git a/components/libsrtp/examples/get_started/CMakeLists.txt b/components/libsrtp/examples/get_started/CMakeLists.txt new file mode 100644 index 0000000000..1c84c41e2a --- /dev/null +++ b/components/libsrtp/examples/get_started/CMakeLists.txt @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD +# +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.16) +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(libsrtp_get_started) diff --git a/components/libsrtp/examples/get_started/README.md b/components/libsrtp/examples/get_started/README.md new file mode 100644 index 0000000000..a08cbd14a1 --- /dev/null +++ b/components/libsrtp/examples/get_started/README.md @@ -0,0 +1,18 @@ +# libsrtp — get_started + +Minimal sanity-check application for [`libsrtp`](https://github.com/espressif/esp-protocols/tree/master/components/libsrtp). Initializes libsrtp via `srtp_init()` and shuts it down. Used by the repo's CI to verify the component compiles and links across supported targets; also useful as a copy-paste starting point. + +## Build + +```bash +idf.py set-target esp32p4 # or esp32, esp32s3, esp32c3, esp32c6 +idf.py build +idf.py -p flash monitor +``` + +The example consumes the parent component via `override_path: ../../..` in `main/idf_component.yml`, so it always tracks the in-tree source. Replace with a registry version to consume the published component instead: + +```yaml +dependencies: + espressif/libsrtp: "^2.8.0" +``` diff --git a/components/libsrtp/examples/get_started/main/CMakeLists.txt b/components/libsrtp/examples/get_started/main/CMakeLists.txt new file mode 100644 index 0000000000..c810b37d2d --- /dev/null +++ b/components/libsrtp/examples/get_started/main/CMakeLists.txt @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD +# +# SPDX-License-Identifier: Apache-2.0 + +idf_component_register( + SRCS "main.c" + INCLUDE_DIRS "." + REQUIRES libsrtp +) diff --git a/components/libsrtp/examples/get_started/main/idf_component.yml b/components/libsrtp/examples/get_started/main/idf_component.yml new file mode 100644 index 0000000000..4662cdd745 --- /dev/null +++ b/components/libsrtp/examples/get_started/main/idf_component.yml @@ -0,0 +1,7 @@ +## Build-check app for libsrtp. +## During CI / local build, override_path points at the parent repo so we +## consume the in-tree component instead of the registry version. +dependencies: + espressif/libsrtp: + version: "^2.8.0" + override_path: ../../.. diff --git a/components/libsrtp/examples/get_started/main/main.c b/components/libsrtp/examples/get_started/main/main.c new file mode 100644 index 0000000000..73cc062e1a --- /dev/null +++ b/components/libsrtp/examples/get_started/main/main.c @@ -0,0 +1,27 @@ +/* + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * Minimal sanity-check app for libsrtp: initializes and shuts down libsrtp. + * Used by .github/workflows/build.yml to verify the component compiles and + * links across supported ESP-IDF targets. + */ + +#include +#include "esp_log.h" +#include "srtp2/srtp.h" + +static const char *TAG = "libsrtp_check"; + +void app_main(void) +{ + srtp_err_status_t status = srtp_init(); + if (status == srtp_err_status_ok) { + ESP_LOGI(TAG, "srtp_init() OK"); + } else { + ESP_LOGE(TAG, "srtp_init() failed: %d", status); + } + srtp_shutdown(); + ESP_LOGI(TAG, "libsrtp build check complete"); +} diff --git a/components/libsrtp/host_test/CMakeLists.txt b/components/libsrtp/host_test/CMakeLists.txt new file mode 100644 index 0000000000..0fd9f992f3 --- /dev/null +++ b/components/libsrtp/host_test/CMakeLists.txt @@ -0,0 +1,10 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# +# SPDX-License-Identifier: Apache-2.0 +cmake_minimum_required(VERSION 3.16) + +set(COMPONENTS main) +set(EXTRA_COMPONENT_DIRS "..") + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(host_test_libsrtp) diff --git a/components/libsrtp/host_test/main/CMakeLists.txt b/components/libsrtp/host_test/main/CMakeLists.txt new file mode 100644 index 0000000000..202de24947 --- /dev/null +++ b/components/libsrtp/host_test/main/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# +# SPDX-License-Identifier: Apache-2.0 +idf_component_register( + SRCS "host_test_main.c" + INCLUDE_DIRS "." + REQUIRES libsrtp +) diff --git a/components/libsrtp/host_test/main/host_test_main.c b/components/libsrtp/host_test/main/host_test_main.c new file mode 100644 index 0000000000..55e98ee44b --- /dev/null +++ b/components/libsrtp/host_test/main/host_test_main.c @@ -0,0 +1,101 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * Host (IDF Linux target) smoke + crypto-roundtrip test for libsrtp. + * + * - srtp_init() / srtp_shutdown() + * - srtp_create() with AES-GCM-128 policy + * - srtp_protect() + srtp_unprotect() roundtrip; assert plaintext is recovered + * + * Deeper coverage lives in libsrtp's own test/srtp_driver.c; we exercise + * the protect/unprotect happy path here as a sanity-check that the + * ESP-IDF Linux-target build produces a working library. + * + * Both CHECK() (libsrtp return-status) and EXPECT() (boolean condition) + * bail out immediately on failure — continuing past a context/init failure + * would either mask the original error or crash on uninitialised state. + */ +#include +#include +#include + +#include "srtp2/srtp.h" + +#define CHECK(_e) \ + do { \ + srtp_err_status_t _s = (_e); \ + if (_s != srtp_err_status_ok) { \ + fprintf(stderr, "FAIL: %s -> %d\n", #_e, (int)_s); \ + exit(1); \ + } \ + } while (0) + +#define EXPECT(_cond, _fmt, ...) \ + do { \ + if (!(_cond)) { \ + fprintf(stderr, "FAIL: " _fmt "\n", ##__VA_ARGS__); \ + exit(1); \ + } \ + } while (0) + +static int run_smoke(void) +{ + printf("libsrtp host_test: libsrtp version %s\n", srtp_get_version_string()); + + CHECK(srtp_init()); + + /* Build a sender + receiver context against the same AES-GCM-128 policy. */ + static const uint8_t key[SRTP_AES_GCM_128_KEY_LEN_WSALT] = { 0 }; + + srtp_policy_t policy_tx = { 0 }; + srtp_crypto_policy_set_aes_gcm_128_8_auth(&policy_tx.rtp); + srtp_crypto_policy_set_aes_gcm_128_8_auth(&policy_tx.rtcp); + policy_tx.ssrc.type = ssrc_any_outbound; + policy_tx.key = (uint8_t *)key; + policy_tx.window_size = 128; + + srtp_policy_t policy_rx = policy_tx; + policy_rx.ssrc.type = ssrc_any_inbound; + + srtp_t sender = NULL; + srtp_t receiver = NULL; + CHECK(srtp_create(&sender, &policy_tx)); + CHECK(srtp_create(&receiver, &policy_rx)); + + /* Minimal RTP-shaped buffer: + * 12B header (V=2, PT=0, seq=1, ts=0, ssrc=0xCAFEBABE) + * payload "hello srtp" + */ + uint8_t pkt[12 + 10 + SRTP_MAX_TRAILER_LEN] = { + 0x80, 0x00, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0xCA, 0xFE, 0xBA, 0xBE, + 'h', 'e', 'l', 'l', 'o', ' ', 's', 'r', 't', 'p', + }; + int protected_len = 12 + 10; + + CHECK(srtp_protect(sender, pkt, &protected_len)); + EXPECT(protected_len > 12 + 10, + "protect did not expand the packet (got len=%d)", protected_len); + + int unprotected_len = protected_len; + CHECK(srtp_unprotect(receiver, pkt, &unprotected_len)); + EXPECT(unprotected_len == 12 + 10, + "unprotect returned wrong len (got %d, want %d)", unprotected_len, 12 + 10); + EXPECT(memcmp(pkt + 12, "hello srtp", 10) == 0, + "recovered payload mismatch"); + + srtp_dealloc(sender); + srtp_dealloc(receiver); + CHECK(srtp_shutdown()); + + printf("libsrtp host_test: PASS\n"); + return 0; +} + +void app_main(void) +{ + exit(run_smoke()); +} diff --git a/components/libsrtp/host_test/main/idf_component.yml b/components/libsrtp/host_test/main/idf_component.yml new file mode 100644 index 0000000000..114348232a --- /dev/null +++ b/components/libsrtp/host_test/main/idf_component.yml @@ -0,0 +1,4 @@ +dependencies: + libsrtp: + version: "*" + override_path: "../.." diff --git a/components/libsrtp/host_test/pytest_libsrtp_linux.py b/components/libsrtp/host_test/pytest_libsrtp_linux.py new file mode 100644 index 0000000000..231ab7f700 --- /dev/null +++ b/components/libsrtp/host_test/pytest_libsrtp_linux.py @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 +import glob +from pathlib import Path + +import pytest +from pytest_embedded import Dut +from pytest_embedded_idf.utils import idf_parametrize + + +@pytest.mark.host_test +@pytest.mark.skipif( + not bool(glob.glob(f'{Path(__file__).parent.absolute()}/build*/')), + reason='Skip the idf version that not build', +) +@idf_parametrize('target', ['linux'], indirect=['target']) +def test_libsrtp_linux(dut: Dut) -> None: + dut.expect_exact('libsrtp host_test: PASS', timeout=30) diff --git a/components/libsrtp/host_test/sdkconfig.defaults b/components/libsrtp/host_test/sdkconfig.defaults new file mode 100644 index 0000000000..efe1c5f660 --- /dev/null +++ b/components/libsrtp/host_test/sdkconfig.defaults @@ -0,0 +1,2 @@ +CONFIG_IDF_TARGET="linux" +CONFIG_COMPILER_OPTIMIZATION_DEBUG=y diff --git a/components/libsrtp/idf_component.yml b/components/libsrtp/idf_component.yml new file mode 100644 index 0000000000..9d23f9cbcf --- /dev/null +++ b/components/libsrtp/idf_component.yml @@ -0,0 +1,35 @@ +version: "2.8.0" +description: "libsrtp (Cisco) wrapper for ESP-IDF — mbedTLS crypto backend with AES-GCM + AES-CM + HMAC-SHA1. SRTP/SRTCP packet protection for WebRTC and other RTP-based protocols." +url: https://github.com/espressif/esp-protocols/tree/master/components/libsrtp +repository: https://github.com/espressif/esp-protocols.git +issues: https://github.com/espressif/esp-protocols/issues +documentation: https://github.com/espressif/esp-protocols/blob/master/components/libsrtp/README.md +license: "Apache-2.0 AND BSD-3-Clause" +tags: + - libsrtp + - srtp + - webrtc + - mbedtls +dependencies: + # Upper bound is intentional. libsrtp v2.x's mbedTLS adapters use the + # legacy / headers; ESP-IDF v6 ships + # mbedTLS 4 which reorganised those into the TF-PSA-Crypto split. + # Bump the bound once the component tracks a libsrtp release that + # speaks mbedTLS 4 (see cisco/libsrtp#812 for context). + idf: ">=5.4,<6" + +# SBOM manifest for the wrapped upstream — see sbom_libsrtp.yml. +sbom: + manifests: + - path: sbom_libsrtp.yml + dest: libsrtp + +# Files excluded from the registry tarball (saves bytes, avoids confusion). +files: + exclude: + - "libsrtp/test/**" # upstream tests are linked at host_test/ build time + - "libsrtp/doc/**" + - "libsrtp/**/.git*" + - "host_test/build*" + - "test_apps/build*" + - "examples/*/build*" diff --git a/components/libsrtp/include/srtp2/srtp.h b/components/libsrtp/include/srtp2/srtp.h new file mode 100644 index 0000000000..c0535c7dc8 --- /dev/null +++ b/components/libsrtp/include/srtp2/srtp.h @@ -0,0 +1,11 @@ +/* + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * Thin shim that re-exposes upstream cisco/libsrtp's public API header + * under the `srtp2/` install-path namespace, so downstream consumers + * can use the canonical `#include ` form without us + * vendoring a copy of the upstream header in this repo. + */ +#include diff --git a/components/libsrtp/libsrtp b/components/libsrtp/libsrtp new file mode 160000 index 0000000000..24b3bf8f19 --- /dev/null +++ b/components/libsrtp/libsrtp @@ -0,0 +1 @@ +Subproject commit 24b3bf8f19b6f5ab4cd2bcceb4f4064efca86fd5 diff --git a/components/libsrtp/port/config.h b/components/libsrtp/port/config.h new file mode 100644 index 0000000000..d2c5601b66 --- /dev/null +++ b/components/libsrtp/port/config.h @@ -0,0 +1,86 @@ +/* + * SPDX-FileCopyrightText: 2024-2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * ESP-IDF build configuration for libsrtp. Define values are chosen to + * match the ESP-IDF/newlib environment; format follows libsrtp's + * upstream config.h.in template (BSD-3-Clause). + */ +#ifndef CONFIG_H +#define CONFIG_H + +#include +#include // For PRIx64 / PRIu64 macros (needed on Apple clang / Linux target) +#include // For size_t definition + +/* + * CPU architecture hint. libsrtp uses this only to select a fast endian-flip + * path in a few places; the C fallback is correct on all targets. ESP-IDF's + * supported targets are Xtensa (esp32 / s2 / s3) and RISC-V (c3 / c5 / c6 / + * p4 / h2), so CPU_CISC would be wrong here. Set CPU_RISC for the embedded + * targets and leave it unset (= C fallback) on the IDF Linux host target. + */ +#if defined(__XTENSA__) || defined(__riscv) +#define CPU_RISC 1 +#endif + +/* Report errors to stdout. */ +#define ERR_REPORTING_STDOUT 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_ARPA_INET_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_NETINET_IN_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STDLIB_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_STRING_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_SOCKET_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TYPES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 + +/* Define to 1 if you want to use external crypto. */ +#define SRTP_CRYPTO_MBEDTLS 1 + +/* Define package info */ +#define PACKAGE_NAME "libsrtp" +#define PACKAGE_VERSION "2.8.0" +#define PACKAGE_STRING PACKAGE_NAME " " PACKAGE_VERSION + +/* The size of `unsigned long', as computed by sizeof. */ +#define SIZEOF_UNSIGNED_LONG 4 + +/* The size of `unsigned long long', as computed by sizeof. */ +#define SIZEOF_UNSIGNED_LONG_LONG 8 + +/* Force SRTP to use system types */ +#define HAVE_INTTYPES_H 1 +#define HAVE_UINT64_T 1 +#define HAVE_UINT32_T 1 +#define HAVE_UINT16_T 1 +#define HAVE_UINT8_T 1 +#define HAVE_INT64_T 1 +#define HAVE_INT32_T 1 +#define HAVE_INT16_T 1 +#define HAVE_INT8_T 1 + +/* Prevent SRTP from redefining types */ +#define INTEGERS_H + +#define GCM 1 +#define MBEDTLS 1 + +#endif /* CONFIG_H */ diff --git a/components/libsrtp/port/crypto_kernel.c b/components/libsrtp/port/crypto_kernel.c new file mode 100644 index 0000000000..fb37eab8be --- /dev/null +++ b/components/libsrtp/port/crypto_kernel.c @@ -0,0 +1,570 @@ +/* + * crypto_kernel.c + * + * header for the cryptographic kernel + * + * David A. McGrew + * Cisco Systems, Inc. + */ +/* + * + * Copyright(c) 2001-2017 Cisco Systems, Inc. + * 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 the Cisco Systems, Inc. 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 HOLDERS 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. + * + */ + +#ifdef HAVE_CONFIG_H +#include +#endif + +#if defined(__has_include) && __has_include("soc/soc_caps.h") +#include "soc/soc_caps.h" +#endif + +#include "alloc.h" + +#include "crypto_kernel.h" +#include "cipher_types.h" + +/* the debug module for the crypto_kernel */ + +srtp_debug_module_t srtp_mod_crypto_kernel = { + 0, /* debugging is off by default */ + "crypto kernel" /* printable name for module */ +}; + +/* crypto_kernel is a global variable, the only one of its datatype */ + +static srtp_crypto_kernel_t crypto_kernel = { + srtp_crypto_kernel_state_insecure, /* start off in insecure state */ + NULL, /* no cipher types yet */ + NULL, /* no auth types yet */ + NULL /* no debug modules yet */ +}; + +#define MAX_RNG_TRIALS 25 + +srtp_err_status_t srtp_crypto_kernel_init(void) +{ + srtp_err_status_t status; + + /* check the security state */ + if (crypto_kernel.state == srtp_crypto_kernel_state_secure) { + /* + * we're already in the secure state, but we've been asked to + * re-initialize, so we just re-run the self-tests and then return + */ + return srtp_crypto_kernel_status(); + } + + /* initialize error reporting system */ + status = srtp_err_reporting_init(); + if (status) { + return status; + } + + /* load debug modules */ + status = srtp_crypto_kernel_load_debug_module(&srtp_mod_crypto_kernel); + if (status) { + return status; + } + status = srtp_crypto_kernel_load_debug_module(&srtp_mod_auth); + if (status) { + return status; + } + status = srtp_crypto_kernel_load_debug_module(&srtp_mod_cipher); + if (status) { + return status; + } + status = srtp_crypto_kernel_load_debug_module(&srtp_mod_alloc); + if (status) { + return status; + } + + /* load cipher types */ + status = srtp_crypto_kernel_load_cipher_type(&srtp_null_cipher, + SRTP_NULL_CIPHER); + if (status) { + return status; + } + status = srtp_crypto_kernel_load_cipher_type(&srtp_aes_icm_128, + SRTP_AES_ICM_128); + if (status) { + return status; + } + status = srtp_crypto_kernel_load_cipher_type(&srtp_aes_icm_256, + SRTP_AES_ICM_256); + if (status) { + return status; + } + status = srtp_crypto_kernel_load_debug_module(&srtp_mod_aes_icm); + if (status) { + return status; + } +#ifdef GCM + /* AES-ICM-192: only the original ESP32 has SoC AES-192 support + * (SOC_AES_SUPPORT_AES_192). On every later SoC the mbedtls port returns + * MBEDTLS_ERR_PLATFORM_FEATURE_UNSUPPORTED for 192-bit keys (no software + * fallback in IDF's mbedtls), so letting libsrtp claim this cipher would + * surface as a test failure during the cipher self-tests. */ +#if defined(SOC_AES_SUPPORT_AES_192) && SOC_AES_SUPPORT_AES_192 + status = srtp_crypto_kernel_load_cipher_type(&srtp_aes_icm_192, + SRTP_AES_ICM_192); + if (status) { + return status; + } +#endif + status = srtp_crypto_kernel_load_cipher_type(&srtp_aes_gcm_128, + SRTP_AES_GCM_128); + if (status) { + return status; + } + status = srtp_crypto_kernel_load_cipher_type(&srtp_aes_gcm_256, + SRTP_AES_GCM_256); + if (status) { + return status; + } + status = srtp_crypto_kernel_load_debug_module(&srtp_mod_aes_gcm); + if (status) { + return status; + } +#endif + + /* load auth func types */ + status = srtp_crypto_kernel_load_auth_type(&srtp_null_auth, SRTP_NULL_AUTH); + if (status) { + return status; + } + status = srtp_crypto_kernel_load_auth_type(&srtp_hmac, SRTP_HMAC_SHA1); + if (status) { + return status; + } + status = srtp_crypto_kernel_load_debug_module(&srtp_mod_hmac); + if (status) { + return status; + } + + /* change state to secure */ + crypto_kernel.state = srtp_crypto_kernel_state_secure; + + return srtp_err_status_ok; +} + +srtp_err_status_t srtp_crypto_kernel_status(void) +{ + srtp_err_status_t status; + srtp_kernel_cipher_type_t *ctype = crypto_kernel.cipher_type_list; + srtp_kernel_auth_type_t *atype = crypto_kernel.auth_type_list; + + /* for each cipher type, describe and test */ + while (ctype != NULL) { + srtp_err_report(srtp_err_level_info, "cipher: %s\n", + ctype->cipher_type->description); + srtp_err_report(srtp_err_level_info, " self-test: "); + status = srtp_cipher_type_self_test(ctype->cipher_type); + if (status) { + srtp_err_report(srtp_err_level_error, "failed with error code %d\n", + status); + exit(status); + } + srtp_err_report(srtp_err_level_info, "passed\n"); + ctype = ctype->next; + } + + /* for each auth type, describe and test */ + while (atype != NULL) { + srtp_err_report(srtp_err_level_info, "auth func: %s\n", + atype->auth_type->description); + srtp_err_report(srtp_err_level_info, " self-test: "); + status = srtp_auth_type_self_test(atype->auth_type); + if (status) { + srtp_err_report(srtp_err_level_error, "failed with error code %d\n", + status); + exit(status); + } + srtp_err_report(srtp_err_level_info, "passed\n"); + atype = atype->next; + } + + srtp_crypto_kernel_list_debug_modules(); + + return srtp_err_status_ok; +} + +srtp_err_status_t srtp_crypto_kernel_list_debug_modules(void) +{ + srtp_kernel_debug_module_t *dm = crypto_kernel.debug_module_list; + + /* describe each debug module */ + srtp_err_report(srtp_err_level_info, "debug modules loaded:\n"); + while (dm != NULL) { + srtp_err_report(srtp_err_level_info, " %s ", dm->mod->name); + if (dm->mod->on) { + srtp_err_report(srtp_err_level_info, "(on)\n"); + } else { + srtp_err_report(srtp_err_level_info, "(off)\n"); + } + dm = dm->next; + } + + return srtp_err_status_ok; +} + +srtp_err_status_t srtp_crypto_kernel_shutdown(void) +{ + /* + * free dynamic memory used in crypto_kernel at present + */ + + /* walk down cipher type list, freeing memory */ + while (crypto_kernel.cipher_type_list != NULL) { + srtp_kernel_cipher_type_t *ctype = crypto_kernel.cipher_type_list; + crypto_kernel.cipher_type_list = ctype->next; + debug_print(srtp_mod_crypto_kernel, "freeing memory for cipher %s", + ctype->cipher_type->description); + srtp_crypto_free(ctype); + } + + /* walk down authentication module list, freeing memory */ + while (crypto_kernel.auth_type_list != NULL) { + srtp_kernel_auth_type_t *atype = crypto_kernel.auth_type_list; + crypto_kernel.auth_type_list = atype->next; + debug_print(srtp_mod_crypto_kernel, + "freeing memory for authentication %s", + atype->auth_type->description); + srtp_crypto_free(atype); + } + + /* walk down debug module list, freeing memory */ + while (crypto_kernel.debug_module_list != NULL) { + srtp_kernel_debug_module_t *kdm = crypto_kernel.debug_module_list; + crypto_kernel.debug_module_list = kdm->next; + debug_print(srtp_mod_crypto_kernel, + "freeing memory for debug module %s", kdm->mod->name); + srtp_crypto_free(kdm); + } + + /* return to insecure state */ + crypto_kernel.state = srtp_crypto_kernel_state_insecure; + + return srtp_err_status_ok; +} + +static inline srtp_err_status_t srtp_crypto_kernel_do_load_cipher_type( + const srtp_cipher_type_t *new_ct, + srtp_cipher_type_id_t id, + int replace) +{ + srtp_kernel_cipher_type_t *ctype; + srtp_kernel_cipher_type_t *new_ctype = NULL; + srtp_err_status_t status; + + /* defensive coding */ + if (new_ct == NULL) { + return srtp_err_status_bad_param; + } + + if (new_ct->id != id) { + return srtp_err_status_bad_param; + } + + /* check cipher type by running self-test */ + status = srtp_cipher_type_self_test(new_ct); + if (status) { + return status; + } + + /* walk down list, checking if this type is in the list already */ + ctype = crypto_kernel.cipher_type_list; + while (ctype != NULL) { + if (id == ctype->id) { + if (!replace) { + return srtp_err_status_bad_param; + } + status = + srtp_cipher_type_test(new_ct, ctype->cipher_type->test_data); + if (status) { + return status; + } + new_ctype = ctype; + break; + } else if (new_ct == ctype->cipher_type) { + return srtp_err_status_bad_param; + } + ctype = ctype->next; + } + + /* if not found, put new_ct at the head of the list */ + if (ctype == NULL) { + /* allocate memory */ + new_ctype = (srtp_kernel_cipher_type_t *)srtp_crypto_alloc( + sizeof(srtp_kernel_cipher_type_t)); + if (new_ctype == NULL) { + return srtp_err_status_alloc_fail; + } + new_ctype->next = crypto_kernel.cipher_type_list; + + /* set head of list to new cipher type */ + crypto_kernel.cipher_type_list = new_ctype; + } + + /* set fields */ + new_ctype->cipher_type = new_ct; + new_ctype->id = id; + + return srtp_err_status_ok; +} + +srtp_err_status_t srtp_crypto_kernel_load_cipher_type( + const srtp_cipher_type_t *new_ct, + srtp_cipher_type_id_t id) +{ + return srtp_crypto_kernel_do_load_cipher_type(new_ct, id, 0); +} + +srtp_err_status_t srtp_replace_cipher_type(const srtp_cipher_type_t *new_ct, + srtp_cipher_type_id_t id) +{ + return srtp_crypto_kernel_do_load_cipher_type(new_ct, id, 1); +} + +srtp_err_status_t srtp_crypto_kernel_do_load_auth_type( + const srtp_auth_type_t *new_at, + srtp_auth_type_id_t id, + int replace) +{ + srtp_kernel_auth_type_t *atype; + srtp_kernel_auth_type_t *new_atype = NULL; + srtp_err_status_t status; + + /* defensive coding */ + if (new_at == NULL) { + return srtp_err_status_bad_param; + } + + if (new_at->id != id) { + return srtp_err_status_bad_param; + } + + /* check auth type by running self-test */ + status = srtp_auth_type_self_test(new_at); + if (status) { + return status; + } + + /* walk down list, checking if this type is in the list already */ + atype = crypto_kernel.auth_type_list; + while (atype != NULL) { + if (id == atype->id) { + if (!replace) { + return srtp_err_status_bad_param; + } + status = srtp_auth_type_test(new_at, atype->auth_type->test_data); + if (status) { + return status; + } + new_atype = atype; + break; + } else if (new_at == atype->auth_type) { + return srtp_err_status_bad_param; + } + atype = atype->next; + } + + /* if not found, put new_at at the head of the list */ + if (atype == NULL) { + /* allocate memory */ + new_atype = (srtp_kernel_auth_type_t *)srtp_crypto_alloc( + sizeof(srtp_kernel_auth_type_t)); + if (new_atype == NULL) { + return srtp_err_status_alloc_fail; + } + + new_atype->next = crypto_kernel.auth_type_list; + /* set head of list to new auth type */ + crypto_kernel.auth_type_list = new_atype; + } + + /* set fields */ + new_atype->auth_type = new_at; + new_atype->id = id; + + return srtp_err_status_ok; +} + +srtp_err_status_t srtp_crypto_kernel_load_auth_type( + const srtp_auth_type_t *new_at, + srtp_auth_type_id_t id) +{ + return srtp_crypto_kernel_do_load_auth_type(new_at, id, 0); +} + +srtp_err_status_t srtp_replace_auth_type(const srtp_auth_type_t *new_at, + srtp_auth_type_id_t id) +{ + return srtp_crypto_kernel_do_load_auth_type(new_at, id, 1); +} + +const srtp_cipher_type_t *srtp_crypto_kernel_get_cipher_type( + srtp_cipher_type_id_t id) +{ + srtp_kernel_cipher_type_t *ctype; + + /* walk down list, looking for id */ + ctype = crypto_kernel.cipher_type_list; + while (ctype != NULL) { + if (id == ctype->id) { + return ctype->cipher_type; + } + ctype = ctype->next; + } + + /* haven't found the right one, indicate failure by returning NULL */ + return NULL; +} + +srtp_err_status_t srtp_crypto_kernel_alloc_cipher(srtp_cipher_type_id_t id, + srtp_cipher_pointer_t *cp, + int key_len, + int tag_len) +{ + const srtp_cipher_type_t *ct; + + /* + * if the crypto_kernel is not yet initialized, we refuse to allocate + * any ciphers - this is a bit extra-paranoid + */ + if (crypto_kernel.state != srtp_crypto_kernel_state_secure) { + return srtp_err_status_init_fail; + } + + ct = srtp_crypto_kernel_get_cipher_type(id); + if (!ct) { + return srtp_err_status_fail; + } + + return ((ct)->alloc(cp, key_len, tag_len)); +} + +const srtp_auth_type_t *srtp_crypto_kernel_get_auth_type(srtp_auth_type_id_t id) +{ + srtp_kernel_auth_type_t *atype; + + /* walk down list, looking for id */ + atype = crypto_kernel.auth_type_list; + while (atype != NULL) { + if (id == atype->id) { + return atype->auth_type; + } + atype = atype->next; + } + + /* haven't found the right one, indicate failure by returning NULL */ + return NULL; +} + +srtp_err_status_t srtp_crypto_kernel_alloc_auth(srtp_auth_type_id_t id, + srtp_auth_pointer_t *ap, + int key_len, + int tag_len) +{ + const srtp_auth_type_t *at; + + /* + * if the crypto_kernel is not yet initialized, we refuse to allocate + * any auth functions - this is a bit extra-paranoid + */ + if (crypto_kernel.state != srtp_crypto_kernel_state_secure) { + return srtp_err_status_init_fail; + } + + at = srtp_crypto_kernel_get_auth_type(id); + if (!at) { + return srtp_err_status_fail; + } + + return ((at)->alloc(ap, key_len, tag_len)); +} + +srtp_err_status_t srtp_crypto_kernel_load_debug_module( + srtp_debug_module_t *new_dm) +{ + srtp_kernel_debug_module_t *kdm, *new; + + /* defensive coding */ + if (new_dm == NULL || new_dm->name == NULL) { + return srtp_err_status_bad_param; + } + + /* walk down list, checking if this type is in the list already */ + kdm = crypto_kernel.debug_module_list; + while (kdm != NULL) { + if (strncmp(new_dm->name, kdm->mod->name, 64) == 0) { + return srtp_err_status_bad_param; + } + kdm = kdm->next; + } + + /* put new_dm at the head of the list */ + /* allocate memory */ + new = (srtp_kernel_debug_module_t *)srtp_crypto_alloc( + sizeof(srtp_kernel_debug_module_t)); + if (new == NULL) { + return srtp_err_status_alloc_fail; + } + + /* set fields */ + new->mod = new_dm; + new->next = crypto_kernel.debug_module_list; + + /* set head of list to new cipher type */ + crypto_kernel.debug_module_list = new; + + return srtp_err_status_ok; +} + +srtp_err_status_t srtp_crypto_kernel_set_debug_module(const char *name, int on) +{ + srtp_kernel_debug_module_t *kdm; + + /* walk down list, checking if this type is in the list already */ + kdm = crypto_kernel.debug_module_list; + while (kdm != NULL) { + if (strncmp(name, kdm->mod->name, 64) == 0) { + kdm->mod->on = on; + return srtp_err_status_ok; + } + kdm = kdm->next; + } + + return srtp_err_status_fail; +} diff --git a/components/libsrtp/sbom.yml b/components/libsrtp/sbom.yml new file mode 100644 index 0000000000..cf08e35670 --- /dev/null +++ b/components/libsrtp/sbom.yml @@ -0,0 +1,6 @@ +name: 'libsrtp' +supplier: 'Organization: Espressif Systems (Shanghai) CO LTD' +originator: 'Organization: Espressif Systems (Shanghai) CO LTD' +description: ESP-IDF wrapper for libsrtp (Cisco) using mbedtls crypto backend with AES-GCM. Used for SRTP/SRTCP packet protection in WebRTC and other RTP-based protocols. +virtpackages: + - sbom_libsrtp.yml diff --git a/components/libsrtp/sbom_libsrtp.yml b/components/libsrtp/sbom_libsrtp.yml new file mode 100644 index 0000000000..b9c9ddf27a --- /dev/null +++ b/components/libsrtp/sbom_libsrtp.yml @@ -0,0 +1,10 @@ +name: libsrtp +version: 2.8.0 +cpe: cpe:2.3:a:cisco:libsrtp:{}:*:*:*:*:*:*:* +supplier: 'Organization: Cisco Systems, Inc. ' +description: Library for the Secure Real-time Transport Protocol (SRTP, RFC 3711) and Secure RTCP (SRTCP). Submodule pinned at upstream release tag v2.8.0 (commit 24b3bf8). One small ESP-IDF specific delta lives in port/crypto_kernel.c — it opts out of the AES-ICM-192 cipher registration when GCM is enabled (saves binary size; AES-CM-128 and AES-GCM cover all WebRTC SRTP suites). +url: https://github.com/cisco/libsrtp +hash: 24b3bf8f19b6f5ab4cd2bcceb4f4064efca86fd5 +cve-exclude-list: + - cve: CVE-2023-31222 + reason: Resolved in 2.6.0; current pin (v2.8.0) is well past. diff --git a/components/libsrtp/test_apps/CMakeLists.txt b/components/libsrtp/test_apps/CMakeLists.txt new file mode 100644 index 0000000000..a50dbe83e9 --- /dev/null +++ b/components/libsrtp/test_apps/CMakeLists.txt @@ -0,0 +1,11 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# +# SPDX-License-Identifier: Apache-2.0 +cmake_minimum_required(VERSION 3.16) + +# Pull the parent component via override_path so the test exercises +# THIS working tree, not whatever's in the registry / managed_components. +set(EXTRA_COMPONENT_DIRS "..") + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(test_app_libsrtp) diff --git a/components/libsrtp/test_apps/main/CMakeLists.txt b/components/libsrtp/test_apps/main/CMakeLists.txt new file mode 100644 index 0000000000..e5ba3cb3ad --- /dev/null +++ b/components/libsrtp/test_apps/main/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# +# SPDX-License-Identifier: Apache-2.0 +idf_component_register( + SRCS "test_libsrtp_main.c" + INCLUDE_DIRS "." + REQUIRES unity libsrtp +) diff --git a/components/libsrtp/test_apps/main/idf_component.yml b/components/libsrtp/test_apps/main/idf_component.yml new file mode 100644 index 0000000000..114348232a --- /dev/null +++ b/components/libsrtp/test_apps/main/idf_component.yml @@ -0,0 +1,4 @@ +dependencies: + libsrtp: + version: "*" + override_path: "../.." diff --git a/components/libsrtp/test_apps/main/test_libsrtp_main.c b/components/libsrtp/test_apps/main/test_libsrtp_main.c new file mode 100644 index 0000000000..0e58f719a3 --- /dev/null +++ b/components/libsrtp/test_apps/main/test_libsrtp_main.c @@ -0,0 +1,33 @@ +/* + * SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + * + * Embedded smoke test for libsrtp. Validates that the component + * links cleanly on the chosen target and exposes the libsrtp public + * API. Deeper coverage lives in host_test/ (runs against the IDF + * Linux target and reuses upstream libsrtp's own test suite). + */ +#include +#include "unity.h" +#include "srtp2/srtp.h" + +TEST_CASE("srtp_init then srtp_shutdown succeed", "[srtp2]") +{ + TEST_ASSERT_EQUAL(srtp_err_status_ok, srtp_init()); + TEST_ASSERT_EQUAL(srtp_err_status_ok, srtp_shutdown()); +} + +TEST_CASE("srtp_get_version_string returns non-empty", "[srtp2]") +{ + const char *v = srtp_get_version_string(); + TEST_ASSERT_NOT_NULL(v); + TEST_ASSERT_GREATER_THAN(0, strlen(v)); +} + +void app_main(void) +{ + UNITY_BEGIN(); + unity_run_all_tests(); + UNITY_END(); +} diff --git a/components/libsrtp/test_apps/pytest_libsrtp.py b/components/libsrtp/test_apps/pytest_libsrtp.py new file mode 100644 index 0000000000..ff3be84130 --- /dev/null +++ b/components/libsrtp/test_apps/pytest_libsrtp.py @@ -0,0 +1,9 @@ +# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: Apache-2.0 +import pytest + + +@pytest.mark.generic +@pytest.mark.parametrize("target", ["esp32", "esp32c3"], indirect=True) +def test_libsrtp_smoke(dut): + dut.expect_unity_test_output(timeout=30) diff --git a/components/libsrtp/test_apps/sdkconfig.defaults b/components/libsrtp/test_apps/sdkconfig.defaults new file mode 100644 index 0000000000..e6511b9c3b --- /dev/null +++ b/components/libsrtp/test_apps/sdkconfig.defaults @@ -0,0 +1,3 @@ +# Speed up CI run + small binary +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_FREERTOS_UNICORE=y