pax_global_header00006660000000000000000000000064151123535050014512gustar00rootroot0000000000000052 comment=1d50bcfcff6f3509ac14de49b07cd7fb3de0eae2 srtp-3.0.9/000077500000000000000000000000001511235350500125135ustar00rootroot00000000000000srtp-3.0.9/.github/000077500000000000000000000000001511235350500140535ustar00rootroot00000000000000srtp-3.0.9/.github/.gitignore000066400000000000000000000001561511235350500160450ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2023 The Pion community # SPDX-License-Identifier: MIT .goassets srtp-3.0.9/.github/fetch-scripts.sh000077500000000000000000000016001511235350500171650ustar00rootroot00000000000000#!/bin/sh # # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # # SPDX-FileCopyrightText: 2023 The Pion community # SPDX-License-Identifier: MIT set -eu SCRIPT_PATH="$(realpath "$(dirname "$0")")" GOASSETS_PATH="${SCRIPT_PATH}/.goassets" GOASSETS_REF=${GOASSETS_REF:-master} if [ -d "${GOASSETS_PATH}" ]; then if ! git -C "${GOASSETS_PATH}" diff --exit-code; then echo "${GOASSETS_PATH} has uncommitted changes" >&2 exit 1 fi git -C "${GOASSETS_PATH}" fetch origin git -C "${GOASSETS_PATH}" checkout ${GOASSETS_REF} git -C "${GOASSETS_PATH}" reset --hard origin/${GOASSETS_REF} else git clone -b ${GOASSETS_REF} https://github.com/pion/.goassets.git "${GOASSETS_PATH}" fi srtp-3.0.9/.github/install-hooks.sh000077500000000000000000000012421511235350500172000ustar00rootroot00000000000000#!/bin/sh # # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # # SPDX-FileCopyrightText: 2023 The Pion community # SPDX-License-Identifier: MIT SCRIPT_PATH="$(realpath "$(dirname "$0")")" . ${SCRIPT_PATH}/fetch-scripts.sh cp "${GOASSETS_PATH}/hooks/commit-msg.sh" "${SCRIPT_PATH}/../.git/hooks/commit-msg" cp "${GOASSETS_PATH}/hooks/pre-commit.sh" "${SCRIPT_PATH}/../.git/hooks/pre-commit" cp "${GOASSETS_PATH}/hooks/pre-push.sh" "${SCRIPT_PATH}/../.git/hooks/pre-push" srtp-3.0.9/.github/workflows/000077500000000000000000000000001511235350500161105ustar00rootroot00000000000000srtp-3.0.9/.github/workflows/api.yaml000066400000000000000000000011141511235350500175420ustar00rootroot00000000000000# # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # If this repository should have package specific CI config, # remove the repository name from .goassets/.github/workflows/assets-sync.yml. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # # SPDX-FileCopyrightText: 2023 The Pion community # SPDX-License-Identifier: MIT name: API on: pull_request: jobs: check: uses: pion/.goassets/.github/workflows/api.reusable.yml@master srtp-3.0.9/.github/workflows/codeql-analysis.yml000066400000000000000000000013201511235350500217170ustar00rootroot00000000000000# # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # If this repository should have package specific CI config, # remove the repository name from .goassets/.github/workflows/assets-sync.yml. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # # SPDX-FileCopyrightText: 2023 The Pion community # SPDX-License-Identifier: MIT name: CodeQL on: workflow_dispatch: schedule: - cron: '23 5 * * 0' pull_request: branches: - master paths: - '**.go' jobs: analyze: uses: pion/.goassets/.github/workflows/codeql-analysis.reusable.yml@master srtp-3.0.9/.github/workflows/fuzz.yaml000066400000000000000000000013421511235350500177720ustar00rootroot00000000000000# # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # If this repository should have package specific CI config, # remove the repository name from .goassets/.github/workflows/assets-sync.yml. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # # SPDX-FileCopyrightText: 2023 The Pion community # SPDX-License-Identifier: MIT name: Fuzz on: push: branches: - master schedule: - cron: "0 */8 * * *" jobs: fuzz: uses: pion/.goassets/.github/workflows/fuzz.reusable.yml@master with: go-version: "1.25" # auto-update/latest-go-version fuzz-time: "60s" srtp-3.0.9/.github/workflows/lint.yaml000066400000000000000000000011151511235350500177400ustar00rootroot00000000000000# # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # If this repository should have package specific CI config, # remove the repository name from .goassets/.github/workflows/assets-sync.yml. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # # SPDX-FileCopyrightText: 2023 The Pion community # SPDX-License-Identifier: MIT name: Lint on: pull_request: jobs: lint: uses: pion/.goassets/.github/workflows/lint.reusable.yml@master srtp-3.0.9/.github/workflows/release.yml000066400000000000000000000012501511235350500202510ustar00rootroot00000000000000# # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # If this repository should have package specific CI config, # remove the repository name from .goassets/.github/workflows/assets-sync.yml. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # # SPDX-FileCopyrightText: 2023 The Pion community # SPDX-License-Identifier: MIT name: Release on: push: tags: - 'v*' jobs: release: uses: pion/.goassets/.github/workflows/release.reusable.yml@master with: go-version: "1.25" # auto-update/latest-go-version srtp-3.0.9/.github/workflows/renovate-go-sum-fix.yaml000066400000000000000000000012671511235350500226160ustar00rootroot00000000000000# # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # If this repository should have package specific CI config, # remove the repository name from .goassets/.github/workflows/assets-sync.yml. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # # SPDX-FileCopyrightText: 2023 The Pion community # SPDX-License-Identifier: MIT name: Fix go.sum on: push: branches: - renovate/* jobs: fix: uses: pion/.goassets/.github/workflows/renovate-go-sum-fix.reusable.yml@master secrets: token: ${{ secrets.PIONBOT_PRIVATE_KEY }} srtp-3.0.9/.github/workflows/reuse.yml000066400000000000000000000011511511235350500177540ustar00rootroot00000000000000# # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # If this repository should have package specific CI config, # remove the repository name from .goassets/.github/workflows/assets-sync.yml. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # # SPDX-FileCopyrightText: 2023 The Pion community # SPDX-License-Identifier: MIT name: REUSE Compliance Check on: push: pull_request: jobs: lint: uses: pion/.goassets/.github/workflows/reuse.reusable.yml@master srtp-3.0.9/.github/workflows/test.yaml000066400000000000000000000033271511235350500177600ustar00rootroot00000000000000# # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # If this repository should have package specific CI config, # remove the repository name from .goassets/.github/workflows/assets-sync.yml. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # # SPDX-FileCopyrightText: 2023 The Pion community # SPDX-License-Identifier: MIT name: Test on: push: branches: - master pull_request: jobs: test: uses: pion/.goassets/.github/workflows/test.reusable.yml@master strategy: matrix: go: ["1.25", "1.24"] # auto-update/supported-go-version-list fail-fast: false with: go-version: ${{ matrix.go }} secrets: inherit test-i386: uses: pion/.goassets/.github/workflows/test-i386.reusable.yml@master strategy: matrix: go: ["1.25", "1.24"] # auto-update/supported-go-version-list fail-fast: false with: go-version: ${{ matrix.go }} test-windows: uses: pion/.goassets/.github/workflows/test-windows.reusable.yml@master strategy: matrix: go: ["1.25", "1.24"] # auto-update/supported-go-version-list fail-fast: false with: go-version: ${{ matrix.go }} test-macos: uses: pion/.goassets/.github/workflows/test-macos.reusable.yml@master strategy: matrix: go: ["1.25", "1.24"] # auto-update/supported-go-version-list fail-fast: false with: go-version: ${{ matrix.go }} test-wasm: uses: pion/.goassets/.github/workflows/test-wasm.reusable.yml@master with: go-version: "1.25" # auto-update/latest-go-version secrets: inherit srtp-3.0.9/.github/workflows/tidy-check.yaml000066400000000000000000000013021511235350500210140ustar00rootroot00000000000000# # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # If this repository should have package specific CI config, # remove the repository name from .goassets/.github/workflows/assets-sync.yml. # # If you want to update the shared CI config, send a PR to # https://github.com/pion/.goassets instead of this repository. # # SPDX-FileCopyrightText: 2023 The Pion community # SPDX-License-Identifier: MIT name: Go mod tidy on: pull_request: push: branches: - master jobs: tidy: uses: pion/.goassets/.github/workflows/tidy-check.reusable.yml@master with: go-version: "1.25" # auto-update/latest-go-version srtp-3.0.9/.gitignore000066400000000000000000000006321511235350500145040ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2023 The Pion community # SPDX-License-Identifier: MIT ### JetBrains IDE ### ##################### .idea/ ### Emacs Temporary Files ### ############################# *~ ### Folders ### ############### bin/ vendor/ node_modules/ ### Files ### ############# *.ivf *.ogg tags cover.out *.sw[poe] *.wasm examples/sfu-ws/cert.pem examples/sfu-ws/key.pem wasm_exec.js srtp-3.0.9/.golangci.yml000066400000000000000000000202661511235350500151050ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2023 The Pion community # SPDX-License-Identifier: MIT version: "2" linters: enable: - asciicheck # Simple linter to check that your code does not contain non-ASCII identifiers - bidichk # Checks for dangerous unicode character sequences - bodyclose # checks whether HTTP response body is closed successfully - containedctx # containedctx is a linter that detects struct contained context.Context field - contextcheck # check the function whether use a non-inherited context - cyclop # checks function and package cyclomatic complexity - decorder # check declaration order and count of types, constants, variables and functions - dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) - dupl # Tool for code clone detection - durationcheck # check for two durations multiplied together - err113 # Golang linter to check the errors handling expressions - errcheck # Errcheck is a program for checking for unchecked errors in go programs. These unchecked errors can be critical bugs in some cases - errchkjson # Checks types passed to the json encoding functions. Reports unsupported types and optionally reports occations, where the check for the returned error can be omitted. - errname # Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`. - errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13. - exhaustive # check exhaustiveness of enum switch statements - forbidigo # Forbids identifiers - forcetypeassert # finds forced type assertions - gochecknoglobals # Checks that no globals are present in Go code - gocognit # Computes and checks the cognitive complexity of functions - goconst # Finds repeated strings that could be replaced by a constant - gocritic # The most opinionated Go source code linter - gocyclo # Computes and checks the cyclomatic complexity of functions - godot # Check if comments end in a period - godox # Tool for detection of FIXME, TODO and other comment keywords - goheader # Checks is file header matches to pattern - gomoddirectives # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod. - goprintffuncname # Checks that printf-like functions are named with `f` at the end - gosec # Inspects source code for security problems - govet # Vet examines Go source code and reports suspicious constructs, such as Printf calls whose arguments do not align with the format string - grouper # An analyzer to analyze expression groups. - importas # Enforces consistent import aliases - ineffassign # Detects when assignments to existing variables are not used - lll # Reports long lines - maintidx # maintidx measures the maintainability index of each function. - makezero # Finds slice declarations with non-zero initial length - misspell # Finds commonly misspelled English words in comments - nakedret # Finds naked returns in functions greater than a specified function length - nestif # Reports deeply nested if statements - nilerr # Finds the code that returns nil even if it checks that the error is not nil. - nilnil # Checks that there is no simultaneous return of `nil` error and an invalid value. - nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity - noctx # noctx finds sending http request without context.Context - predeclared # find code that shadows one of Go's predeclared identifiers - revive # golint replacement, finds style mistakes - staticcheck # Staticcheck is a go vet on steroids, applying a ton of static analysis checks - tagliatelle # Checks the struct tags. - thelper # thelper detects golang test helpers without t.Helper() call and checks the consistency of test helpers - unconvert # Remove unnecessary type conversions - unparam # Reports unused function parameters - unused # Checks Go code for unused constants, variables, functions and types - varnamelen # checks that the length of a variable's name matches its scope - wastedassign # wastedassign finds wasted assignment statements - whitespace # Tool for detection of leading and trailing whitespace disable: - depguard # Go linter that checks if package imports are in a list of acceptable packages - funlen # Tool for detection of long functions - gochecknoinits # Checks that no init functions are present in Go code - gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. - interfacebloat # A linter that checks length of interface. - ireturn # Accept Interfaces, Return Concrete Types - mnd # An analyzer to detect magic numbers - nolintlint # Reports ill-formed or insufficient nolint directives - paralleltest # paralleltest detects missing usage of t.Parallel() method in your Go test - prealloc # Finds slice declarations that could potentially be preallocated - promlinter # Check Prometheus metrics naming via promlint - rowserrcheck # checks whether Err of rows is checked successfully - sqlclosecheck # Checks that sql.Rows and sql.Stmt are closed. - testpackage # linter that makes you use a separate _test package - tparallel # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes - wrapcheck # Checks that errors returned from external packages are wrapped - wsl # Whitespace Linter - Forces you to use empty lines! settings: staticcheck: checks: - all - -QF1008 # "could remove embedded field", to keep it explicit! - -QF1003 # "could use tagged switch on enum", Cases conflicts with exhaustive! exhaustive: default-signifies-exhaustive: true forbidigo: forbid: - pattern: ^fmt.Print(f|ln)?$ - pattern: ^log.(Panic|Fatal|Print)(f|ln)?$ - pattern: ^os.Exit$ - pattern: ^panic$ - pattern: ^print(ln)?$ - pattern: ^testing.T.(Error|Errorf|Fatal|Fatalf|Fail|FailNow)$ pkg: ^testing$ msg: use testify/assert instead analyze-types: true gomodguard: blocked: modules: - github.com/pkg/errors: recommendations: - errors govet: enable: - shadow revive: rules: # Prefer 'any' type alias over 'interface{}' for Go 1.18+ compatibility - name: use-any severity: warning disabled: false misspell: locale: US varnamelen: max-distance: 12 min-name-length: 2 ignore-type-assert-ok: true ignore-map-index-ok: true ignore-chan-recv-ok: true ignore-decls: - i int - n int - w io.Writer - r io.Reader - b []byte exclusions: generated: lax rules: - linters: - forbidigo - gocognit path: (examples|main\.go) - linters: - gocognit path: _test\.go - linters: - forbidigo path: cmd formatters: enable: - gci # Gci control golang package import order and make it always deterministic. - gofmt # Gofmt checks whether code was gofmt-ed. By default this tool runs with -s option to check for code simplification - gofumpt # Gofumpt checks whether code was gofumpt-ed. - goimports # Goimports does everything that gofmt does. Additionally it checks unused imports exclusions: generated: lax srtp-3.0.9/.goreleaser.yml000066400000000000000000000001711511235350500154430ustar00rootroot00000000000000# SPDX-FileCopyrightText: 2023 The Pion community # SPDX-License-Identifier: MIT builds: - skip: true srtp-3.0.9/.reuse/000077500000000000000000000000001511235350500137145ustar00rootroot00000000000000srtp-3.0.9/.reuse/dep5000066400000000000000000000011141511235350500144710ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: Pion Source: https://github.com/pion/ Files: README.md DESIGN.md **/README.md AUTHORS.txt renovate.json go.mod go.sum **/go.mod **/go.sum .eslintrc.json package.json examples.json sfu-ws/flutter/.gitignore sfu-ws/flutter/pubspec.yaml c-data-channels/webrtc.h examples/examples.json yarn.lock Copyright: 2023 The Pion community License: MIT Files: testdata/seed/* testdata/fuzz/* **/testdata/fuzz/* api/*.txt Copyright: 2023 The Pion community License: CC0-1.0 srtp-3.0.9/LICENSE000066400000000000000000000021051511235350500135160ustar00rootroot00000000000000MIT License Copyright (c) 2023 The Pion community Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. srtp-3.0.9/LICENSES/000077500000000000000000000000001511235350500137205ustar00rootroot00000000000000srtp-3.0.9/LICENSES/MIT.txt000066400000000000000000000020661511235350500151160ustar00rootroot00000000000000MIT License Copyright (c) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. srtp-3.0.9/README.md000066400000000000000000000044771511235350500140060ustar00rootroot00000000000000


Pion SRTP

A Go implementation of SRTP

Pion SRTP Sourcegraph Widget join us on Discord Follow us on Bluesky
GitHub Workflow Status Go Reference Coverage Status Go Report Card License: MIT


### Roadmap The library is used as a part of our WebRTC implementation. Please refer to that [roadmap](https://github.com/pion/webrtc/issues/9) to track our major milestones. ### Community Pion has an active community on the [Discord](https://discord.gg/PngbdqpFbt). Follow the [Pion Bluesky](https://bsky.app/profile/pion.ly) or [Pion Twitter](https://twitter.com/_pion) for project updates and important WebRTC news. We are always looking to support **your projects**. Please reach out if you have something to build! If you need commercial support or don't want to use public methods you can contact us at [team@pion.ly](mailto:team@pion.ly) ### Contributing Check out the [contributing wiki](https://github.com/pion/webrtc/wiki/Contributing) to join the group of amazing people making this project possible ### License MIT License - see [LICENSE](LICENSE) for full text srtp-3.0.9/codecov.yml000066400000000000000000000007151511235350500146630ustar00rootroot00000000000000# # DO NOT EDIT THIS FILE # # It is automatically copied from https://github.com/pion/.goassets repository. # # SPDX-FileCopyrightText: 2023 The Pion community # SPDX-License-Identifier: MIT coverage: status: project: default: # Allow decreasing 2% of total coverage to avoid noise. threshold: 2% patch: default: target: 70% only_pulls: true ignore: - "examples/*" - "examples/**/*" srtp-3.0.9/context.go000066400000000000000000000275501511235350500145370ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "bytes" "fmt" "github.com/pion/transport/v3/replaydetector" ) const ( labelSRTPEncryption = 0x00 labelSRTPAuthenticationTag = 0x01 labelSRTPSalt = 0x02 labelSRTCPEncryption = 0x03 labelSRTCPAuthenticationTag = 0x04 labelSRTCPSalt = 0x05 maxSequenceNumber = 65535 maxROC = (1 << 32) - 1 seqNumMedian = 1 << 15 seqNumMax = 1 << 16 ) // Encrypt/Decrypt state for a single SRTP SSRC. type srtpSSRCState struct { ssrc uint32 rolloverHasProcessed bool index uint64 replayDetector replaydetector.ReplayDetector } // Encrypt/Decrypt state for a single SRTCP SSRC. type srtcpSSRCState struct { srtcpIndex uint32 ssrc uint32 replayDetector replaydetector.ReplayDetector } // RCCMode is the mode of Roll-over Counter Carrying Transform from RFC 4771. type RCCMode int const ( // RCCModeNone is the default mode. RCCModeNone RCCMode = iota // RCCMode1 is RCCm1 mode from RFC 4771. In this mode ROC and truncated auth tag is sent every R-th packet, // and no auth tag in other ones. This mode is not supported by pion/srtp. RCCMode1 // RCCMode2 is RCCm2 mode from RFC 4771. In this mode ROC and truncated auth tag is sent every R-th packet, // and full auth tag in other ones. This mode is supported for AES-CM and NULL profiles only. RCCMode2 // RCCMode3 is RCCm3 mode from RFC 4771. In this mode ROC is sent every R-th packet (without truncated auth tag), // and no auth tag in other ones. This mode is supported for AES-GCM profiles only. RCCMode3 ) // CryptexMode is the mode of Cryptex support for SRTP packets from RFC 9335. type CryptexMode int const ( // CryptexModeDisabled (default) disables Cryptex support. Received Cryptex SRTP packets with encrypted // CSRCs and header extensions will be rejected with an error. CryptexModeDisabled CryptexMode = 0 // CryptexModeEnabled enables Cryptex support when SRTP packets are encrypted. Received SRTP packets // with unencrypted CSRCs and header extensions will be accepted and decrypted. CryptexModeEnabled CryptexMode = 1 // CryptexModeRequired enables Cryptex support when SRTP packets are encrypted. Received SRTP packets // with unencrypted CSRCs and header extensions will be rejected with an error. CryptexModeRequired CryptexMode = 2 ) // Context represents a SRTP cryptographic context. // Context can only be used for one-way operations. // it must either used ONLY for encryption or ONLY for decryption. // Note that Context does not provide any concurrency protection: // access to a Context from multiple goroutines requires external // synchronization. type Context struct { cipher srtpCipher srtpSSRCStates map[uint32]*srtpSSRCState srtcpSSRCStates map[uint32]*srtcpSSRCState newSRTCPReplayDetector func() replaydetector.ReplayDetector newSRTPReplayDetector func() replaydetector.ReplayDetector profile ProtectionProfile // Master Key Identifier used for encrypting RTP/RTCP packets. Set to nil if MKI is not enabled. sendMKI []byte // Master Key Identifier to cipher mapping. Used for decrypting packets. Empty if MKI is not enabled. mkis map[string]srtpCipher encryptSRTP bool encryptSRTCP bool rccMode RCCMode rocTransmitRate uint16 authTagRTPLen *int cryptexMode CryptexMode } // CreateContext creates a new SRTP Context. // // CreateContext receives variable number of ContextOption-s. // Passing multiple options which set the same parameter let the last one valid. // Following example create SRTP Context with replay protection with window size of 256. // // decCtx, err := srtp.CreateContext(key, salt, profile, srtp.SRTPReplayProtection(256)) func CreateContext( masterKey, masterSalt []byte, profile ProtectionProfile, opts ...ContextOption, ) (c *Context, err error) { c = &Context{ srtpSSRCStates: map[uint32]*srtpSSRCState{}, srtcpSSRCStates: map[uint32]*srtcpSSRCState{}, profile: profile, mkis: map[string]srtpCipher{}, } for _, o := range append( []ContextOption{ // Default options SRTPNoReplayProtection(), SRTCPNoReplayProtection(), SRTPEncryption(), SRTCPEncryption(), }, opts..., // User specified options ) { if errOpt := o(c); errOpt != nil { return nil, errOpt } } if err = c.checkRCCMode(); err != nil { return nil, err } if c.authTagRTPLen != nil { var authKeyLen int authKeyLen, err = c.profile.AuthKeyLen() if err != nil { return nil, err } if *c.authTagRTPLen > authKeyLen { return nil, errTooLongSRTPAuthTag } } c.cipher, err = c.createCipher(c.sendMKI, masterKey, masterSalt, c.encryptSRTP, c.encryptSRTCP) if err != nil { return nil, err } if len(c.sendMKI) != 0 { c.mkis[string(c.sendMKI)] = c.cipher } return c, nil } // AddCipherForMKI adds new MKI with associated masker key and salt. // Context must be created with MasterKeyIndicator option // to enable MKI support. MKI must be unique and have the same length as the one used for creating Context. // Operation is not thread-safe, you need to provide synchronization with decrypting packets. func (c *Context) AddCipherForMKI(mki, masterKey, masterSalt []byte) error { if len(c.mkis) == 0 { return errMKIIsNotEnabled } if len(mki) == 0 || len(mki) != len(c.sendMKI) { return errInvalidMKILength } if _, ok := c.mkis[string(mki)]; ok { return errMKIAlreadyInUse } cipher, err := c.createCipher(mki, masterKey, masterSalt, c.encryptSRTP, c.encryptSRTCP) if err != nil { return err } c.mkis[string(mki)] = cipher return nil } func (c *Context) createCipher(mki, masterKey, masterSalt []byte, encryptSRTP, encryptSRTCP bool) (srtpCipher, error) { keyLen, err := c.profile.KeyLen() if err != nil { return nil, err } saltLen, err := c.profile.SaltLen() if err != nil { return nil, err } if masterKeyLen := len(masterKey); masterKeyLen != keyLen { return nil, fmt.Errorf("%w expected(%d) actual(%d)", errShortSrtpMasterKey, keyLen, masterKey) } else if masterSaltLen := len(masterSalt); masterSaltLen != saltLen { return nil, fmt.Errorf("%w expected(%d) actual(%d)", errShortSrtpMasterSalt, saltLen, masterSaltLen) } profileWithArgs := protectionProfileWithArgs{ ProtectionProfile: c.profile, authTagRTPLen: c.authTagRTPLen, } useCryptex := c.cryptexMode != CryptexModeDisabled && encryptSRTP switch c.profile { case ProtectionProfileAeadAes128Gcm, ProtectionProfileAeadAes256Gcm: return newSrtpCipherAeadAesGcm(profileWithArgs, masterKey, masterSalt, mki, encryptSRTP, encryptSRTCP, useCryptex) case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes128CmHmacSha1_80, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_80: return newSrtpCipherAesCmHmacSha1(profileWithArgs, masterKey, masterSalt, mki, encryptSRTP, encryptSRTCP, useCryptex) case ProtectionProfileNullHmacSha1_32, ProtectionProfileNullHmacSha1_80: return newSrtpCipherAesCmHmacSha1(profileWithArgs, masterKey, masterSalt, mki, false, false, false) default: return nil, fmt.Errorf("%w: %#v", errNoSuchSRTPProfile, c.profile) } } // RemoveMKI removes one of MKIs. You cannot remove last MKI and one used for encrypting RTP/RTCP packets. // Operation is not thread-safe, you need to provide synchronization with decrypting packets. func (c *Context) RemoveMKI(mki []byte) error { if _, ok := c.mkis[string(mki)]; !ok { return ErrMKINotFound } if bytes.Equal(mki, c.sendMKI) { return errMKIAlreadyInUse } delete(c.mkis, string(mki)) return nil } // SetSendMKI switches MKI and cipher used for encrypting RTP/RTCP packets. // Operation is not thread-safe, you need to provide synchronization with encrypting packets. func (c *Context) SetSendMKI(mki []byte) error { cipher, ok := c.mkis[string(mki)] if !ok { return ErrMKINotFound } c.sendMKI = mki c.cipher = cipher return nil } // https://tools.ietf.org/html/rfc3550#appendix-A.1 func (s *srtpSSRCState) nextRolloverCount(sequenceNumber uint16) (roc uint32, diff int64, overflow bool) { seq := int32(sequenceNumber) localRoc := uint32(s.index >> 16) //nolint:gosec // G115 localSeq := int32(s.index & (seqNumMax - 1)) //nolint:gosec // G115 guessRoc := localRoc var difference int32 if s.rolloverHasProcessed { //nolint:nestif // When localROC is equal to 0, and entering seq-localSeq > seqNumMedian // judgment, it will cause guessRoc calculation error if s.index > seqNumMedian { if localSeq < seqNumMedian { if seq-localSeq > seqNumMedian { guessRoc = localRoc - 1 difference = seq - localSeq - seqNumMax } else { guessRoc = localRoc difference = seq - localSeq } } else { if localSeq-seqNumMedian > seq { guessRoc = localRoc + 1 difference = seq - localSeq + seqNumMax } else { guessRoc = localRoc difference = seq - localSeq } } } else { // localRoc is equal to 0 difference = seq - localSeq } } return guessRoc, int64(difference), (guessRoc == 0 && localRoc == maxROC) } func (s *srtpSSRCState) updateRolloverCount(sequenceNumber uint16, difference int64, hasRemoteRoc bool, remoteRoc uint32, ) { switch { case hasRemoteRoc: s.index = (uint64(remoteRoc) << 16) | uint64(sequenceNumber) s.rolloverHasProcessed = true case !s.rolloverHasProcessed: s.index |= uint64(sequenceNumber) s.rolloverHasProcessed = true case difference > 0: s.index += uint64(difference) } } func (c *Context) getSRTPSSRCState(ssrc uint32) *srtpSSRCState { s, ok := c.srtpSSRCStates[ssrc] if ok { return s } s = &srtpSSRCState{ ssrc: ssrc, replayDetector: c.newSRTPReplayDetector(), } c.srtpSSRCStates[ssrc] = s return s } func (c *Context) getSRTCPSSRCState(ssrc uint32) *srtcpSSRCState { s, ok := c.srtcpSSRCStates[ssrc] if ok { return s } s = &srtcpSSRCState{ ssrc: ssrc, replayDetector: c.newSRTCPReplayDetector(), } c.srtcpSSRCStates[ssrc] = s return s } // ROC returns SRTP rollover counter value of specified SSRC. func (c *Context) ROC(ssrc uint32) (uint32, bool) { s, ok := c.srtpSSRCStates[ssrc] if !ok { return 0, false } return uint32(s.index >> 16), true //nolint:gosec // G115 } // SetROC sets SRTP rollover counter value of specified SSRC. func (c *Context) SetROC(ssrc uint32, roc uint32) { s := c.getSRTPSSRCState(ssrc) s.index = uint64(roc) << 16 s.rolloverHasProcessed = false } // Index returns SRTCP index value of specified SSRC. func (c *Context) Index(ssrc uint32) (uint32, bool) { s, ok := c.srtcpSSRCStates[ssrc] if !ok { return 0, false } return s.srtcpIndex, true } // SetIndex sets SRTCP index value of specified SSRC. func (c *Context) SetIndex(ssrc uint32, index uint32) { s := c.getSRTCPSSRCState(ssrc) s.srtcpIndex = index % (maxSRTCPIndex + 1) } //nolint:cyclop func (c *Context) checkRCCMode() error { if c.rccMode == RCCModeNone { return nil } if c.rocTransmitRate == 0 { return errZeroRocTransmitRate } switch c.profile { case ProtectionProfileAeadAes128Gcm, ProtectionProfileAeadAes256Gcm: // AEAD profiles support RCCMode3 only if c.rccMode != RCCMode3 { return errUnsupportedRccMode } case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileNullHmacSha1_32: if c.authTagRTPLen == nil { // ROC completely replaces auth tag for _32 profiles. If you really want to use 4-byte // SRTP auth tag with RCC, use SRTPAuthenticationTagLength(4) option. return errTooShortSRTPAuthTag } fallthrough // Checks below are common for _32 and _80 profiles. case ProtectionProfileAes128CmHmacSha1_80, ProtectionProfileAes256CmHmacSha1_80, ProtectionProfileNullHmacSha1_80: // AES-CM and NULL profiles support RCCMode2 only if c.rccMode != RCCMode2 { return errUnsupportedRccMode } if c.authTagRTPLen != nil && *c.authTagRTPLen < 4 { return errTooShortSRTPAuthTag } default: return errUnsupportedRccMode } return nil } srtp-3.0.9/context_test.go000066400000000000000000000145441511235350500155750ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "testing" "github.com/stretchr/testify/assert" ) func TestContextROC(t *testing.T) { c, err := CreateContext(make([]byte, 16), make([]byte, 14), profileCTR) assert.NoError(t, err) _, ok := c.ROC(123) assert.False(t, ok, "ROC must return false for unused SSRC") c.SetROC(123, 100) roc, ok := c.ROC(123) assert.True(t, ok, "ROC must return true for used SSRC") assert.Equal(t, roc, uint32(100)) } func TestContextIndex(t *testing.T) { c, err := CreateContext(make([]byte, 16), make([]byte, 14), profileCTR) assert.NoError(t, err) _, ok := c.Index(123) assert.False(t, ok, "Index must return false for unused SSRC") c.SetIndex(123, 100) index, ok := c.Index(123) assert.True(t, ok, "Index must return true for used SSRC") assert.Equal(t, index, uint32(100)) } func TestContextWithoutMKI(t *testing.T) { ctx, err := CreateContext(make([]byte, 16), make([]byte, 14), profileCTR) assert.NoError(t, err) err = ctx.AddCipherForMKI(nil, make([]byte, 16), make([]byte, 14)) assert.Error(t, err) err = ctx.AddCipherForMKI(make([]byte, 0), make([]byte, 16), make([]byte, 14)) assert.Error(t, err) err = ctx.AddCipherForMKI(make([]byte, 4), make([]byte, 16), make([]byte, 14)) assert.Error(t, err) err = ctx.SetSendMKI(nil) assert.Error(t, err) err = ctx.SetSendMKI(make([]byte, 0)) assert.Error(t, err) err = ctx.RemoveMKI(nil) assert.Error(t, err) err = ctx.RemoveMKI(make([]byte, 0)) assert.Error(t, err) err = ctx.RemoveMKI(make([]byte, 2)) assert.Error(t, err) } func TestAddMKIToContextWithMKI(t *testing.T) { mki1 := []byte{1, 2, 3, 4} mki2 := []byte{2, 3, 4, 5} ctx, err := CreateContext(make([]byte, 16), make([]byte, 14), profileCTR, MasterKeyIndicator(mki1)) assert.NoError(t, err) err = ctx.AddCipherForMKI(mki2, make([]byte, 16), make([]byte, 14)) assert.NoError(t, err) err = ctx.AddCipherForMKI(nil, make([]byte, 16), make([]byte, 14)) assert.Error(t, err) err = ctx.AddCipherForMKI(make([]byte, 0), make([]byte, 16), make([]byte, 14)) assert.Error(t, err) err = ctx.AddCipherForMKI(make([]byte, 3), make([]byte, 16), make([]byte, 14)) assert.Error(t, err) err = ctx.AddCipherForMKI(mki1, make([]byte, 16), make([]byte, 14)) assert.Error(t, err) err = ctx.AddCipherForMKI(mki2, make([]byte, 16), make([]byte, 14)) assert.Error(t, err) } func TestContextSetSendMKI(t *testing.T) { mki1 := []byte{1, 2, 3, 4} mki2 := []byte{2, 3, 4, 5} ctx, err := CreateContext(make([]byte, 16), make([]byte, 14), profileCTR, MasterKeyIndicator(mki1)) assert.NoError(t, err) err = ctx.AddCipherForMKI(mki2, make([]byte, 16), make([]byte, 14)) assert.NoError(t, err) err = ctx.SetSendMKI(mki1) assert.NoError(t, err) err = ctx.SetSendMKI(mki2) assert.NoError(t, err) err = ctx.SetSendMKI(make([]byte, 4)) assert.Error(t, err) } func TestContextRemoveMKI(t *testing.T) { mki1 := []byte{1, 2, 3, 4} mki2 := []byte{2, 3, 4, 5} mki3 := []byte{3, 4, 5, 6} ctx, err := CreateContext(make([]byte, 16), make([]byte, 14), profileCTR, MasterKeyIndicator(mki1)) assert.NoError(t, err) err = ctx.AddCipherForMKI(mki2, make([]byte, 16), make([]byte, 14)) assert.NoError(t, err) err = ctx.AddCipherForMKI(mki3, make([]byte, 16), make([]byte, 14)) assert.NoError(t, err) err = ctx.RemoveMKI(make([]byte, 4)) assert.Error(t, err) err = ctx.RemoveMKI(mki1) assert.Error(t, err) err = ctx.SetSendMKI(mki3) assert.NoError(t, err) err = ctx.RemoveMKI(mki1) assert.NoError(t, err) err = ctx.RemoveMKI(mki2) assert.NoError(t, err) err = ctx.RemoveMKI(mki3) assert.Error(t, err) } func TestInvalidContextOptions(t *testing.T) { profiles := []ProtectionProfile{ ProtectionProfileAes128CmHmacSha1_80, ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_80, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileNullHmacSha1_80, ProtectionProfileNullHmacSha1_32, ProtectionProfileAeadAes128Gcm, ProtectionProfileAeadAes256Gcm, } for _, profile := range profiles { t.Run(profile.String(), func(t *testing.T) { keyLen, err := profile.KeyLen() assert.NoError(t, err) saltLen, err := profile.SaltLen() assert.NoError(t, err) authTagLen, err := profile.AuthTagRTPLen() assert.NoError(t, err) authKeyLen, err := profile.AuthKeyLen() assert.NoError(t, err) masterKey := make([]byte, keyLen) masterSalt := make([]byte, saltLen) aeadAuthTagLen, err := profile.AEADAuthTagLen() assert.NoError(t, err) t.Run("InvalidRCCContextOptions", func(t *testing.T) { authTagLenOpt := SRTPAuthenticationTagLength(authTagLen) _, err = CreateContext(masterKey, masterSalt, profile, RolloverCounterCarryingTransform(RCCMode1, 0), authTagLenOpt) assert.ErrorIs(t, err, errZeroRocTransmitRate) _, err = CreateContext(masterKey, masterSalt, profile, RolloverCounterCarryingTransform(RCCMode2, 0), authTagLenOpt) assert.ErrorIs(t, err, errZeroRocTransmitRate) _, err = CreateContext(masterKey, masterSalt, profile, RolloverCounterCarryingTransform(RCCMode3, 0), authTagLenOpt) assert.ErrorIs(t, err, errZeroRocTransmitRate) _, err = CreateContext(masterKey, masterSalt, profile, RolloverCounterCarryingTransform(RCCMode1, 10), authTagLenOpt) assert.ErrorIs(t, err, errUnsupportedRccMode) if aeadAuthTagLen == 0 { // AES-CM _, err = CreateContext(masterKey, masterSalt, profile, RolloverCounterCarryingTransform(RCCMode3, 10), authTagLenOpt) assert.ErrorIs(t, err, errUnsupportedRccMode) if authTagLen == 4 { _, err = CreateContext(masterKey, masterSalt, profile, RolloverCounterCarryingTransform(RCCMode2, 10)) assert.ErrorIs(t, err, errTooShortSRTPAuthTag) } for n := 0; n < 4; n++ { _, err = CreateContext(masterKey, masterSalt, profile, RolloverCounterCarryingTransform(RCCMode2, 10), SRTPAuthenticationTagLength(n)) assert.ErrorIs(t, err, errTooShortSRTPAuthTag) } } else { // AEAD _, err = CreateContext(masterKey, masterSalt, profile, RolloverCounterCarryingTransform(RCCMode2, 10), authTagLenOpt) assert.ErrorIs(t, err, errUnsupportedRccMode) } }) t.Run("InvalidSRTPAuthTagLen", func(t *testing.T) { _, err = CreateContext(masterKey, masterSalt, profile, SRTPAuthenticationTagLength(authKeyLen+1)) assert.ErrorIs(t, err, errTooLongSRTPAuthTag) }) }) } } srtp-3.0.9/crypto.go000066400000000000000000000022241511235350500143620ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "crypto/cipher" "sync" "github.com/pion/transport/v3/utils/xor" ) // incrementCTR increments a big-endian integer of arbitrary size. func incrementCTR(ctr []byte) { for i := len(ctr) - 1; i >= 0; i-- { ctr[i]++ if ctr[i] != 0 { break } } } var xorBufferPool = sync.Pool{ // nolint:gochecknoglobals New: func() any { return make([]byte, 1500) }, } // xorBytesCTR performs CTR encryption and decryption. // It is equivalent to cipher.NewCTR followed by XORKeyStream. func xorBytesCTR(block cipher.Block, iv []byte, dst, src []byte) error { if len(iv) != block.BlockSize() { return errBadIVLength } xorBuf := xorBufferPool.Get() defer xorBufferPool.Put(xorBuf) buffer, ok := xorBuf.([]byte) if !ok { return errFailedTypeAssertion } ctr := buffer[:len(iv)] copy(ctr, iv) bs := block.BlockSize() stream := buffer[len(iv) : len(iv)+bs] i := 0 for i < len(src) { block.Encrypt(stream, ctr) incrementCTR(ctr) n := xor.XorBytes(dst[i:], src[i:], stream) if n == 0 { break } i += n } return nil } srtp-3.0.9/crypto_test.go000066400000000000000000000032221511235350500154200ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "crypto/aes" "crypto/cipher" "math/rand" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func xorBytesCTRReference(block cipher.Block, iv []byte, dst, src []byte) { stream := cipher.NewCTR(block, iv) stream.XORKeyStream(dst, src) } func TestXorBytesCTR(t *testing.T) { for keysize := 16; keysize < 64; keysize *= 2 { key := make([]byte, keysize) _, err := rand.Read(key) //nolint: gosec,staticcheck require.NoError(t, err) block, err := aes.NewCipher(key) require.NoError(t, err) iv := make([]byte, block.BlockSize()) for i := 0; i < 1500; i++ { src := make([]byte, i) dst := make([]byte, i) reference := make([]byte, i) _, err = rand.Read(iv) //nolint: gosec,staticcheck require.NoError(t, err) _, err = rand.Read(src) //nolint: gosec,staticcheck require.NoError(t, err) assert.NoError(t, xorBytesCTR(block, iv, dst, src)) xorBytesCTRReference(block, iv, reference, src) require.Equal(t, dst, reference) // test overlap assert.NoError(t, xorBytesCTR(block, iv, dst, dst)) xorBytesCTRReference(block, iv, reference, reference) require.Equal(t, dst, reference) } } } func TestXorBytesCTRInvalidIvLength(t *testing.T) { key := make([]byte, 16) block, err := aes.NewCipher(key) require.NoError(t, err) src := make([]byte, 1024) dst := make([]byte, 1024) test := func(iv []byte) { assert.Error(t, errBadIVLength, xorBytesCTR(block, iv, dst, src)) } test(make([]byte, block.BlockSize()-1)) test(make([]byte, block.BlockSize()+1)) } srtp-3.0.9/errors.go000066400000000000000000000056661511235350500143730ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "errors" "fmt" ) var ( // ErrFailedToVerifyAuthTag is returned when decryption fails due to invalid authentication tag. ErrFailedToVerifyAuthTag = errors.New("failed to verify auth tag") // ErrMKINotFound is returned when decryption fails due to unknown MKI value in packet. ErrMKINotFound = errors.New("MKI not found") errDuplicated = errors.New("duplicated packet") errShortSrtpMasterKey = errors.New("SRTP master key is not long enough") errShortSrtpMasterSalt = errors.New("SRTP master salt is not long enough") errNoSuchSRTPProfile = errors.New("no such SRTP Profile") errNonZeroKDRNotSupported = errors.New("indexOverKdr > 0 is not supported yet") errExporterWrongLabel = errors.New("exporter called with wrong label") errNoConfig = errors.New("no config provided") errNoConn = errors.New("no conn provided") errTooShortRTP = errors.New("packet is too short to be RTP packet") errTooShortRTCP = errors.New("packet is too short to be RTCP packet") errPayloadDiffers = errors.New("payload differs") errStartedChannelUsedIncorrectly = errors.New("started channel used incorrectly, should only be closed") errBadIVLength = errors.New("bad iv length in xorBytesCTR") errExceededMaxPackets = errors.New("exceeded the maximum number of packets") errMKIAlreadyInUse = errors.New("MKI already in use") errMKIIsNotEnabled = errors.New("MKI is not enabled") errInvalidMKILength = errors.New("invalid MKI length") errTooLongSRTPAuthTag = errors.New("SRTP auth tag is too long") errTooShortSRTPAuthTag = errors.New("SRTP auth tag is too short") errStreamNotInited = errors.New("stream has not been inited, unable to close") errStreamAlreadyClosed = errors.New("stream is already closed") errStreamAlreadyInited = errors.New("stream is already inited") errFailedTypeAssertion = errors.New("failed to cast child") errZeroRocTransmitRate = errors.New("ROC transmit rate is zero") errUnsupportedRccMode = errors.New("unsupported RCC mode") errUnsupportedHeaderExtension = errors.New("unsupported header extension") errHeaderLengthMismatch = errors.New("header length mismatch") errUnencryptedHeaderExtAndCSRCs = errors.New("unencrypted header extensions and CSRCs are not allowed") errCryptexDisabled = errors.New("cryptex is disabled") ) type duplicatedError struct { Proto string // srtp or srtcp SSRC uint32 Index uint32 // sequence number or index } func (e *duplicatedError) Error() string { return fmt.Sprintf("%s ssrc=%d index=%d: %v", e.Proto, e.SSRC, e.Index, errDuplicated) } func (e *duplicatedError) Unwrap() error { return errDuplicated } srtp-3.0.9/go.mod000066400000000000000000000007001511235350500136160ustar00rootroot00000000000000module github.com/pion/srtp/v3 go 1.21 require ( github.com/pion/logging v0.2.4 github.com/pion/rtcp v1.2.16 github.com/pion/rtp v1.8.25 github.com/pion/transport/v3 v3.1.1 github.com/stretchr/testify v1.11.1 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pion/randutil v0.1.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/sys v0.29.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) srtp-3.0.9/go.sum000066400000000000000000000037401511235350500136520ustar00rootroot00000000000000github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= github.com/pion/rtcp v1.2.16 h1:fk1B1dNW4hsI78XUCljZJlC4kZOPk67mNRuQ0fcEkSo= github.com/pion/rtcp v1.2.16/go.mod h1:/as7VKfYbs5NIb4h6muQ35kQF/J0ZVNz2Z3xKoCBYOo= github.com/pion/rtp v1.8.25 h1:b8+y44GNbwOJTYWuVan7SglX/hMlicVCAtL50ztyZHw= github.com/pion/rtp v1.8.25/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM= github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkYOM= github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= srtp-3.0.9/key_derivation.go000066400000000000000000000043401511235350500160570ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "crypto/aes" "encoding/binary" ) func aesCmKeyDerivation(label byte, masterKey, masterSalt []byte, indexOverKdr int, outLen int) ([]byte, error) { if indexOverKdr != 0 { // 24-bit "index DIV kdr" must be xored to prf input. return nil, errNonZeroKDRNotSupported } // https://tools.ietf.org/html/rfc3711#appendix-B.3 // The input block for AES-CM is generated by exclusive-oring the master salt with the // concatenation of the encryption key label 0x00 with (index DIV kdr), // - index is 'rollover count' and DIV is 'divided by' nMasterSalt := len(masterSalt) prfIn := make([]byte, 16) copy(prfIn[:nMasterSalt], masterSalt) prfIn[7] ^= label // The resulting value is then AES encrypted using the master key to get the cipher key. block, err := aes.NewCipher(masterKey) if err != nil { return nil, err } nBlockSize := block.BlockSize() out := make([]byte, ((outLen+nBlockSize-1)/nBlockSize)*nBlockSize) var i uint16 for n := 0; n < outLen; n += nBlockSize { binary.BigEndian.PutUint16(prfIn[len(prfIn)-2:], i) block.Encrypt(out[n:n+nBlockSize], prfIn) i++ } return out[:outLen], nil } // Generate IV https://tools.ietf.org/html/rfc3711#section-4.1.1 // where the 128-bit integer value IV SHALL be defined by the SSRC, the // SRTP packet index i, and the SRTP session salting key k_s, as below. // - ROC = a 32-bit unsigned rollover counter (ROC), which records how many // - times the 16-bit RTP sequence number has been reset to zero after // - passing through 65,535 // i = 2^16 * ROC + SEQ // IV = (salt*2 ^ 16) | (ssrc*2 ^ 64) | (i*2 ^ 16). func generateCounter( sequenceNumber uint16, rolloverCounter uint32, ssrc uint32, sessionSalt []byte, ) (counter [16]byte) { copy(counter[:], sessionSalt) counter[4] ^= byte(ssrc >> 24) counter[5] ^= byte(ssrc >> 16) counter[6] ^= byte(ssrc >> 8) counter[7] ^= byte(ssrc) counter[8] ^= byte(rolloverCounter >> 24) counter[9] ^= byte(rolloverCounter >> 16) counter[10] ^= byte(rolloverCounter >> 8) counter[11] ^= byte(rolloverCounter) counter[12] ^= byte(sequenceNumber >> 8) counter[13] ^= byte(sequenceNumber) return counter } srtp-3.0.9/key_derivation_test.go000066400000000000000000000106721511235350500171230ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "testing" "github.com/stretchr/testify/assert" ) func TestValidSessionKeys_AesCm128(t *testing.T) { masterKey := []byte{0xE1, 0xF9, 0x7A, 0x0D, 0x3E, 0x01, 0x8B, 0xE0, 0xD6, 0x4F, 0xA3, 0x2C, 0x06, 0xDE, 0x41, 0x39} masterSalt := []byte{0x0E, 0xC6, 0x75, 0xAD, 0x49, 0x8A, 0xFE, 0xEB, 0xB6, 0x96, 0x0B, 0x3A, 0xAB, 0xE6} expectedSessionKey := []byte{ 0xC6, 0x1E, 0x7A, 0x93, 0x74, 0x4F, 0x39, 0xEE, 0x10, 0x73, 0x4A, 0xFE, 0x3F, 0xF7, 0xA0, 0x87, } expectedSessionSalt := []byte{0x30, 0xCB, 0xBC, 0x08, 0x86, 0x3D, 0x8C, 0x85, 0xD4, 0x9D, 0xB3, 0x4A, 0x9A, 0xE1} expectedSessionAuthTag := []byte{ 0xCE, 0xBE, 0x32, 0x1F, 0x6F, 0xF7, 0x71, 0x6B, 0x6F, 0xD4, 0xAB, 0x49, 0xAF, 0x25, 0x6A, 0x15, 0x6D, 0x38, 0xBA, 0xA4, } sessionKey, err := aesCmKeyDerivation(labelSRTPEncryption, masterKey, masterSalt, 0, len(masterKey)) assert.NoError(t, err, "generateSessionKey failed") assert.Equalf(t, expectedSessionKey, sessionKey, "Session Key % 02x does not match expected % 02x", sessionKey, expectedSessionKey) sessionSalt, err := aesCmKeyDerivation(labelSRTPSalt, masterKey, masterSalt, 0, len(masterSalt)) assert.NoError(t, err, "generateSessionSalt failed") assert.Equalf(t, expectedSessionSalt, sessionSalt, "Session Salt % 02x does not match expected % 02x", sessionSalt, expectedSessionSalt) authKeyLen, err := ProtectionProfileAes128CmHmacSha1_80.AuthKeyLen() assert.NoError(t, err) sessionAuthTag, err := aesCmKeyDerivation(labelSRTPAuthenticationTag, masterKey, masterSalt, 0, authKeyLen) assert.NoError(t, err) assert.Equalf(t, expectedSessionAuthTag, sessionAuthTag, "Session Auth Tag % 02x does not match expected % 02x", sessionAuthTag, expectedSessionAuthTag) } func TestValidSessionKeys_AesCm256(t *testing.T) { masterKey := []byte{ 0xf0, 0xf0, 0x49, 0x14, 0xb5, 0x13, 0xf2, 0x76, 0x3a, 0x1b, 0x1f, 0xa1, 0x30, 0xf1, 0x0e, 0x29, 0x98, 0xf6, 0xf6, 0xe4, 0x3e, 0x43, 0x09, 0xd1, 0xe6, 0x22, 0xa0, 0xe3, 0x32, 0xb9, 0xf1, 0xb6, } masterSalt := []byte{0x3b, 0x04, 0x80, 0x3d, 0xe5, 0x1e, 0xe7, 0xc9, 0x64, 0x23, 0xab, 0x5b, 0x78, 0xd2} expectedSessionKey := []byte{ 0x5b, 0xa1, 0x06, 0x4e, 0x30, 0xec, 0x51, 0x61, 0x3c, 0xad, 0x92, 0x6c, 0x5a, 0x28, 0xef, 0x73, 0x1e, 0xc7, 0xfb, 0x39, 0x7f, 0x70, 0xa9, 0x60, 0x65, 0x3c, 0xaf, 0x06, 0x55, 0x4c, 0xd8, 0xc4, } expectedSessionSalt := []byte{0xfa, 0x31, 0x79, 0x16, 0x85, 0xca, 0x44, 0x4a, 0x9e, 0x07, 0xc6, 0xc6, 0x4e, 0x93} expectedSessionAuthTag := []byte{ 0xfd, 0x9c, 0x32, 0xd3, 0x9e, 0xd5, 0xfb, 0xb5, 0xa9, 0xdc, 0x96, 0xb3, 0x08, 0x18, 0x45, 0x4d, 0x13, 0x13, 0xdc, 0x05, } sessionKey, err := aesCmKeyDerivation(labelSRTPEncryption, masterKey, masterSalt, 0, len(masterKey)) assert.NoError(t, err) assert.Equalf(t, expectedSessionKey, sessionKey, "Session Key % 02x does not match expected % 02x", sessionKey, expectedSessionKey) sessionSalt, err := aesCmKeyDerivation(labelSRTPSalt, masterKey, masterSalt, 0, len(masterSalt)) assert.NoError(t, err) assert.Equalf(t, expectedSessionSalt, sessionSalt, "Session Salt % 02x does not match expected % 02x", sessionSalt, expectedSessionSalt) authKeyLen, err := ProtectionProfileAes256CmHmacSha1_80.AuthKeyLen() assert.NoError(t, err) sessionAuthTag, err := aesCmKeyDerivation(labelSRTPAuthenticationTag, masterKey, masterSalt, 0, authKeyLen) assert.NoError(t, err) assert.Equalf(t, expectedSessionAuthTag, sessionAuthTag, "Session Auth Tag % 02x does not match expected % 02x", sessionAuthTag, expectedSessionAuthTag) } // This test asserts that calling aesCmKeyDerivation with a non-zero indexOverKdr fails // Currently this isn't supported, but the API makes sure we can add this in the future. func TestIndexOverKDR(t *testing.T) { _, err := aesCmKeyDerivation(labelSRTPAuthenticationTag, []byte{}, []byte{}, 1, 0) assert.Error(t, err) } func BenchmarkGenerateCounter(b *testing.B) { masterKey := []byte{0x0d, 0xcd, 0x21, 0x3e, 0x4c, 0xbc, 0xf2, 0x8f, 0x01, 0x7f, 0x69, 0x94, 0x40, 0x1e, 0x28, 0x89} masterSalt := []byte{0x62, 0x77, 0x60, 0x38, 0xc0, 0x6d, 0xc9, 0x41, 0x9f, 0x6d, 0xd9, 0x43, 0x3e, 0x7c} s := &srtpSSRCState{ssrc: 4160032510} srtpSessionSalt, err := aesCmKeyDerivation(labelSRTPSalt, masterKey, masterSalt, 0, len(masterSalt)) assert.NoError(b, err) b.ResetTimer() for i := 0; i < b.N; i++ { generateCounter(32846, uint32(s.index>>16), s.ssrc, srtpSessionSalt) //nolint:gosec // G115 } } srtp-3.0.9/keying.go000066400000000000000000000033551511235350500143360ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp const labelExtractorDtlsSrtp = "EXTRACTOR-dtls_srtp" // KeyingMaterialExporter allows package SRTP to extract keying material. type KeyingMaterialExporter interface { ExportKeyingMaterial(label string, context []byte, length int) ([]byte, error) } // ExtractSessionKeysFromDTLS allows setting the Config SessionKeys by // extracting them from DTLS. This behavior is defined in RFC5764: // https://tools.ietf.org/html/rfc5764 func (c *Config) ExtractSessionKeysFromDTLS(exporter KeyingMaterialExporter, isClient bool) error { keyLen, err := c.Profile.KeyLen() if err != nil { return err } saltLen, err := c.Profile.SaltLen() if err != nil { return err } keyingMaterial, err := exporter.ExportKeyingMaterial(labelExtractorDtlsSrtp, nil, (keyLen*2)+(saltLen*2)) if err != nil { return err } offset := 0 clientWriteKey := append([]byte{}, keyingMaterial[offset:offset+keyLen]...) offset += keyLen serverWriteKey := append([]byte{}, keyingMaterial[offset:offset+keyLen]...) offset += keyLen clientWriteKey = append(clientWriteKey, keyingMaterial[offset:offset+saltLen]...) offset += saltLen serverWriteKey = append(serverWriteKey, keyingMaterial[offset:offset+saltLen]...) if isClient { c.Keys.LocalMasterKey = clientWriteKey[0:keyLen] c.Keys.LocalMasterSalt = clientWriteKey[keyLen:] c.Keys.RemoteMasterKey = serverWriteKey[0:keyLen] c.Keys.RemoteMasterSalt = serverWriteKey[keyLen:] return nil } c.Keys.LocalMasterKey = serverWriteKey[0:keyLen] c.Keys.LocalMasterSalt = serverWriteKey[keyLen:] c.Keys.RemoteMasterKey = clientWriteKey[0:keyLen] c.Keys.RemoteMasterSalt = clientWriteKey[keyLen:] return nil } srtp-3.0.9/keying_test.go000066400000000000000000000040631511235350500153720ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "crypto/rand" "fmt" "testing" "github.com/stretchr/testify/assert" ) type mockKeyingMaterialExporter struct { exported []byte } func (m *mockKeyingMaterialExporter) ExportKeyingMaterial(label string, _ []byte, length int) ([]byte, error) { if label != labelExtractorDtlsSrtp { return nil, fmt.Errorf("%w: expected(%s) actual(%s)", errExporterWrongLabel, label, labelExtractorDtlsSrtp) } m.exported = make([]byte, length) if _, err := rand.Read(m.exported); err != nil { return nil, err } return m.exported, nil } func TestExtractSessionKeysFromDTLS(t *testing.T) { tt := []struct { config *Config }{ {&Config{Profile: ProtectionProfileAes128CmHmacSha1_80}}, } mockExporter := &mockKeyingMaterialExporter{} for i, tc := range tt { // Test client err := tc.config.ExtractSessionKeysFromDTLS(mockExporter, true) assert.NoErrorf(t, err, "failed to extract keys for %d-client: %v", i, err) keys := tc.config.Keys clientMaterial := append([]byte{}, keys.LocalMasterKey...) clientMaterial = append(clientMaterial, keys.RemoteMasterKey...) clientMaterial = append(clientMaterial, keys.LocalMasterSalt...) clientMaterial = append(clientMaterial, keys.RemoteMasterSalt...) assert.Equalf(t, mockExporter.exported, clientMaterial, "material reconstruction failed for %d-client:\n%#v\nexpected\n%#v") // Test server err = tc.config.ExtractSessionKeysFromDTLS(mockExporter, false) assert.NoErrorf(t, err, "failed to extract keys for %d-server: %v", i, err) keys = tc.config.Keys serverMaterial := append([]byte{}, keys.RemoteMasterKey...) serverMaterial = append(serverMaterial, keys.LocalMasterKey...) serverMaterial = append(serverMaterial, keys.RemoteMasterSalt...) serverMaterial = append(serverMaterial, keys.LocalMasterSalt...) assert.Equalf(t, mockExporter.exported, serverMaterial, "material reconstruction failed for %d-server:\n%#v\nexpected\n%#v", i, serverMaterial, mockExporter.exported) } } srtp-3.0.9/option.go000066400000000000000000000132731511235350500143600ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "github.com/pion/transport/v3/replaydetector" ) // ContextOption represents option of Context using the functional options pattern. type ContextOption func(*Context) error // SRTPReplayProtection sets SRTP replay protection window size. func SRTPReplayProtection(windowSize uint) ContextOption { // nolint:revive return func(c *Context) error { c.newSRTPReplayDetector = func() replaydetector.ReplayDetector { return replaydetector.New(windowSize, maxROC<<16|maxSequenceNumber) } return nil } } // SRTCPReplayProtection sets SRTCP replay protection window size. func SRTCPReplayProtection(windowSize uint) ContextOption { return func(c *Context) error { c.newSRTCPReplayDetector = func() replaydetector.ReplayDetector { return replaydetector.New(windowSize, maxSRTCPIndex) } return nil } } // SRTPNoReplayProtection disables SRTP replay protection. func SRTPNoReplayProtection() ContextOption { // nolint:revive return func(c *Context) error { c.newSRTPReplayDetector = func() replaydetector.ReplayDetector { return &nopReplayDetector{} } return nil } } // SRTCPNoReplayProtection disables SRTCP replay protection. func SRTCPNoReplayProtection() ContextOption { return func(c *Context) error { c.newSRTCPReplayDetector = func() replaydetector.ReplayDetector { return &nopReplayDetector{} } return nil } } // SRTPReplayDetectorFactory sets custom SRTP replay detector. func SRTPReplayDetectorFactory(fn func() replaydetector.ReplayDetector) ContextOption { // nolint:revive return func(c *Context) error { c.newSRTPReplayDetector = fn return nil } } // SRTCPReplayDetectorFactory sets custom SRTCP replay detector. func SRTCPReplayDetectorFactory(fn func() replaydetector.ReplayDetector) ContextOption { return func(c *Context) error { c.newSRTCPReplayDetector = fn return nil } } type nopReplayDetector struct{} func (s *nopReplayDetector) Check(uint64) (func() bool, bool) { return func() bool { return true }, true } // MasterKeyIndicator sets RTP/RTCP MKI for the initial master key. Array passed as an argument will be // copied as-is to encrypted SRTP/SRTCP packets, so it must be of proper length and in Big Endian format. // All MKIs added later using Context.AddCipherForMKI must have the same length as the one used here. func MasterKeyIndicator(mki []byte) ContextOption { return func(c *Context) error { if len(mki) > 0 { c.sendMKI = make([]byte, len(mki)) copy(c.sendMKI, mki) } return nil } } // SRTPEncryption enables SRTP encryption. func SRTPEncryption() ContextOption { // nolint:revive return func(c *Context) error { c.encryptSRTP = true return nil } } // SRTPNoEncryption disables SRTP encryption. // This option is useful when you want to use NullCipher for SRTP and keep authentication only. // It simplifies debugging and testing, but it is not recommended for production use. // // Note: you can also use SRTPAuthenticationTagLength(0) to disable authentication tag too. func SRTPNoEncryption() ContextOption { // nolint:revive return func(c *Context) error { c.encryptSRTP = false return nil } } // SRTCPEncryption enables SRTCP encryption. func SRTCPEncryption() ContextOption { return func(c *Context) error { c.encryptSRTCP = true return nil } } // SRTCPNoEncryption disables SRTCP encryption. // This option is useful when you want to use NullCipher for SRTCP and keep authentication only. // It simplifies debugging and testing, but it is not recommended for production use. func SRTCPNoEncryption() ContextOption { return func(c *Context) error { c.encryptSRTCP = false return nil } } // RolloverCounterCarryingTransform enables Rollover Counter Carrying Transform from RFC 4771. // ROC value is sent in Authentication Tag of SRTP packets every rocTransmitRate packets. // // RFC 4771 defines 3 RCC modes. pion/srtp supports mode RCCm2 for AES-CM and NULL profiles, // and mode RCCm3 for AES-GCM (AEAD) profiles. // // From RFC 4771: "[For modes RCCm1 and and RCCm3] the length of the MAC is shorter than the length // of the authentication tag. To achieve the same (or less) MAC forgery success probability on all // packets when using RCCm1 or RCCm2, as with the default integrity transform in RFC 3711, // the tag-length must be set to 14 octets, which means that the length of MAC_tr is 10 octets." // // Protection profiles ProtectionProfile*CmHmacSha1_32 uses 4-byte SRTP auth tag, so in RCCm2 mode // SRTP packets with ROC will not be integrity protected. // // You can increase the length of the authentication tag using SRTPAuthenticationTagLength option // to mitigate this issue. func RolloverCounterCarryingTransform(mode RCCMode, rocTransmitRate uint16) ContextOption { return func(c *Context) error { c.rccMode = mode c.rocTransmitRate = rocTransmitRate return nil } } // SRTPAuthenticationTagLength sets length of SRTP authentication tag in bytes for AES-CM protection // profiles. Decreasing the length of the authentication tag is not recommended for production use, // as it decreases integrity protection. // // Zero value means that there is no authentication tag, what may be useful for debugging and testing. // // This option is ignored for AEAD profiles. func SRTPAuthenticationTagLength(authTagRTPLen int) ContextOption { // nolint:revive return func(c *Context) error { c.authTagRTPLen = &authTagRTPLen return nil } } // Cryptex allows to enable Cryptex mechanism to completely encrypt RTP Header Extensions and Contributing // Sources, as defined in RFC 9335. func Cryptex(cryptexMode CryptexMode) ContextOption { return func(c *Context) error { c.cryptexMode = cryptexMode return nil } } srtp-3.0.9/protection_profile.go000066400000000000000000000141111511235350500167460ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import "fmt" // ProtectionProfile specifies Cipher and AuthTag details, similar to TLS cipher suite. type ProtectionProfile uint16 // Supported protection profiles // See https://www.iana.org/assignments/srtp-protection/srtp-protection.xhtml // // AES128_CM_HMAC_SHA1_80 and AES128_CM_HMAC_SHA1_32 are valid SRTP profiles, // but they do not have an DTLS-SRTP Protection Profiles ID assigned // in RFC 5764. They were in earlier draft of this RFC: // https://datatracker.ietf.org/doc/html/draft-ietf-avt-dtls-srtp-03#section-4.1.2 // Their IDs are now marked as reserved in the IANA registry. Despite this Chrome supports them: // https://chromium.googlesource.com/chromium/deps/libsrtp/+/84122798bb16927b1e676bd4f938a6e48e5bf2fe/srtp/include/srtp.h#694 // // Null profiles disable encryption, they are used for debugging and testing. // They are not recommended for production use. // Use of them is equivalent to using ProtectionProfileAes128CmHmacSha1_NN // profile with SRTPNoEncryption and SRTCPNoEncryption options. // //nolint:lll const ( ProtectionProfileAes128CmHmacSha1_80 ProtectionProfile = 0x0001 ProtectionProfileAes128CmHmacSha1_32 ProtectionProfile = 0x0002 ProtectionProfileAes256CmHmacSha1_80 ProtectionProfile = 0x0003 ProtectionProfileAes256CmHmacSha1_32 ProtectionProfile = 0x0004 ProtectionProfileNullHmacSha1_80 ProtectionProfile = 0x0005 ProtectionProfileNullHmacSha1_32 ProtectionProfile = 0x0006 ProtectionProfileAeadAes128Gcm ProtectionProfile = 0x0007 ProtectionProfileAeadAes256Gcm ProtectionProfile = 0x0008 ) // KeyLen returns length of encryption key in bytes. // For all profiles except NullHmacSha1_32 and NullHmacSha1_80 is // also the length of the session key. func (p ProtectionProfile) KeyLen() (int, error) { switch p { case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes128CmHmacSha1_80, ProtectionProfileAeadAes128Gcm, ProtectionProfileNullHmacSha1_32, ProtectionProfileNullHmacSha1_80: return 16, nil case ProtectionProfileAeadAes256Gcm, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_80: return 32, nil default: return 0, fmt.Errorf("%w: %#v", errNoSuchSRTPProfile, p) } } // SaltLen returns length of salt key in bytes. // For all profiles except NullHmacSha1_32 and NullHmacSha1_80 // is also the length of the session salt. func (p ProtectionProfile) SaltLen() (int, error) { switch p { case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes128CmHmacSha1_80, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_80, ProtectionProfileNullHmacSha1_32, ProtectionProfileNullHmacSha1_80: return 14, nil case ProtectionProfileAeadAes128Gcm, ProtectionProfileAeadAes256Gcm: return 12, nil default: return 0, fmt.Errorf("%w: %#v", errNoSuchSRTPProfile, p) } } // AuthTagRTPLen returns length of RTP authentication tag in bytes for AES protection profiles. // For AEAD ones it returns zero. func (p ProtectionProfile) AuthTagRTPLen() (int, error) { switch p { case ProtectionProfileAes128CmHmacSha1_80, ProtectionProfileAes256CmHmacSha1_80, ProtectionProfileNullHmacSha1_80: return 10, nil case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileNullHmacSha1_32: return 4, nil case ProtectionProfileAeadAes128Gcm, ProtectionProfileAeadAes256Gcm: return 0, nil default: return 0, fmt.Errorf("%w: %#v", errNoSuchSRTPProfile, p) } } // AuthTagRTCPLen returns length of RTCP authentication tag in bytes for AES protection profiles. // // For AEAD ones it returns zero. func (p ProtectionProfile) AuthTagRTCPLen() (int, error) { switch p { case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes128CmHmacSha1_80, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_80, ProtectionProfileNullHmacSha1_32, ProtectionProfileNullHmacSha1_80: return 10, nil case ProtectionProfileAeadAes128Gcm, ProtectionProfileAeadAes256Gcm: return 0, nil default: return 0, fmt.Errorf("%w: %#v", errNoSuchSRTPProfile, p) } } // AEADAuthTagLen returns length of authentication tag in bytes for AEAD protection profiles. // For AES ones it returns zero. func (p ProtectionProfile) AEADAuthTagLen() (int, error) { switch p { case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes128CmHmacSha1_80, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_80, ProtectionProfileNullHmacSha1_32, ProtectionProfileNullHmacSha1_80: return 0, nil case ProtectionProfileAeadAes128Gcm, ProtectionProfileAeadAes256Gcm: return 16, nil default: return 0, fmt.Errorf("%w: %#v", errNoSuchSRTPProfile, p) } } // AuthKeyLen returns length of authentication key in bytes for AES protection profiles. // For AEAD ones it returns zero. func (p ProtectionProfile) AuthKeyLen() (int, error) { switch p { case ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes128CmHmacSha1_80, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_80, ProtectionProfileNullHmacSha1_32, ProtectionProfileNullHmacSha1_80: return 20, nil case ProtectionProfileAeadAes128Gcm, ProtectionProfileAeadAes256Gcm: return 0, nil default: return 0, fmt.Errorf("%w: %#v", errNoSuchSRTPProfile, p) } } // String returns the name of the protection profile. func (p ProtectionProfile) String() string { switch p { case ProtectionProfileAes128CmHmacSha1_80: return "SRTP_AES128_CM_HMAC_SHA1_80" case ProtectionProfileAes128CmHmacSha1_32: return "SRTP_AES128_CM_HMAC_SHA1_32" case ProtectionProfileAes256CmHmacSha1_80: return "SRTP_AES256_CM_HMAC_SHA1_80" case ProtectionProfileAes256CmHmacSha1_32: return "SRTP_AES256_CM_HMAC_SHA1_32" case ProtectionProfileAeadAes128Gcm: return "SRTP_AEAD_AES_128_GCM" case ProtectionProfileAeadAes256Gcm: return "SRTP_AEAD_AES_256_GCM" case ProtectionProfileNullHmacSha1_80: return "SRTP_NULL_HMAC_SHA1_80" case ProtectionProfileNullHmacSha1_32: return "SRTP_NULL_HMAC_SHA1_32" default: return fmt.Sprintf("Unknown SRTP profile: %#v", p) } } srtp-3.0.9/protection_profile_test.go000066400000000000000000000006361511235350500200140ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "testing" "github.com/stretchr/testify/assert" ) func TestInvalidProtectionProfile(t *testing.T) { var invalidProtectionProfile ProtectionProfile _, err := invalidProtectionProfile.KeyLen() assert.Error(t, err) _, err = invalidProtectionProfile.SaltLen() assert.Error(t, err) } srtp-3.0.9/protection_profile_with_args.go000066400000000000000000000011731511235350500210210ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp // protectionProfileWithArgs is a wrapper around ProtectionProfile that allows to // specify additional arguments for the profile. type protectionProfileWithArgs struct { ProtectionProfile authTagRTPLen *int } // AuthTagRTPLen returns length of RTP authentication tag in bytes for AES protection profiles. // For AEAD ones it returns zero. func (p protectionProfileWithArgs) AuthTagRTPLen() (int, error) { if p.authTagRTPLen != nil { return *p.authTagRTPLen, nil } return p.ProtectionProfile.AuthTagRTPLen() } srtp-3.0.9/renovate.json000066400000000000000000000001731511235350500152320ustar00rootroot00000000000000{ "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ "github>pion/renovate-config" ] } srtp-3.0.9/session.go000066400000000000000000000067371511235350500145420ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "errors" "io" "net" "sync" "time" "github.com/pion/logging" "github.com/pion/transport/v3/packetio" ) type streamSession interface { Close() error write([]byte) (int, error) decrypt([]byte) error } type session struct { localContextMutex sync.Mutex localContext, remoteContext *Context localOptions, remoteOptions []ContextOption newStream chan readStream acceptStreamTimeout time.Time started chan any closed chan any readStreamsClosed bool readStreams map[uint32]readStream readStreamsLock sync.Mutex log logging.LeveledLogger bufferFactory func(packetType packetio.BufferPacketType, ssrc uint32) io.ReadWriteCloser nextConn net.Conn } // Config is used to configure a session. // You can provide either a KeyingMaterialExporter to export keys // or directly pass the keys themselves. // After a Config is passed to a session it must not be modified. type Config struct { Keys SessionKeys Profile ProtectionProfile BufferFactory func(packetType packetio.BufferPacketType, ssrc uint32) io.ReadWriteCloser LoggerFactory logging.LoggerFactory AcceptStreamTimeout time.Time // List of local/remote context options. // ReplayProtection is enabled on remote context by default. // Default replay protection window size is 64. LocalOptions, RemoteOptions []ContextOption } // SessionKeys bundles the keys required to setup an SRTP session. type SessionKeys struct { LocalMasterKey []byte LocalMasterSalt []byte RemoteMasterKey []byte RemoteMasterSalt []byte } func (s *session) getOrCreateReadStream(ssrc uint32, child streamSession, proto func() readStream) (readStream, bool) { s.readStreamsLock.Lock() defer s.readStreamsLock.Unlock() if s.readStreamsClosed { return nil, false } rStream, ok := s.readStreams[ssrc] if ok { return rStream, false } // Create the readStream. rStream = proto() if err := rStream.init(child, ssrc); err != nil { return nil, false } s.readStreams[ssrc] = rStream return rStream, true } func (s *session) removeReadStream(ssrc uint32) { s.readStreamsLock.Lock() defer s.readStreamsLock.Unlock() if s.readStreamsClosed { return } delete(s.readStreams, ssrc) } func (s *session) close() error { if s.nextConn == nil { return nil } else if err := s.nextConn.Close(); err != nil { return err } <-s.closed return nil } func (s *session) start( localMasterKey, localMasterSalt, remoteMasterKey, remoteMasterSalt []byte, profile ProtectionProfile, child streamSession, ) error { var err error s.localContext, err = CreateContext(localMasterKey, localMasterSalt, profile, s.localOptions...) if err != nil { return err } s.remoteContext, err = CreateContext(remoteMasterKey, remoteMasterSalt, profile, s.remoteOptions...) if err != nil { return err } if err = s.nextConn.SetReadDeadline(s.acceptStreamTimeout); err != nil { return err } go func() { defer func() { close(s.newStream) s.readStreamsLock.Lock() s.readStreamsClosed = true s.readStreamsLock.Unlock() close(s.closed) }() b := make([]byte, 8192) for { var i int i, err = s.nextConn.Read(b) if err != nil { if !errors.Is(err, io.EOF) { s.log.Error(err.Error()) } return } if err = child.decrypt(b[:i]); err != nil { s.log.Info(err.Error()) } } }() close(s.started) return nil } srtp-3.0.9/session_srtcp.go000066400000000000000000000114071511235350500157430ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "net" "time" "github.com/pion/logging" "github.com/pion/rtcp" ) const defaultSessionSRTCPReplayProtectionWindow = 64 // SessionSRTCP implements io.ReadWriteCloser and provides a bi-directional SRTCP session // SRTCP itself does not have a design like this, but it is common in most applications // for local/remote to each have their own keying material. This provides those patterns // instead of making everyone re-implement. type SessionSRTCP struct { session writeStream *WriteStreamSRTCP } // NewSessionSRTCP creates a SRTCP session using conn as the underlying transport. func NewSessionSRTCP(conn net.Conn, config *Config) (*SessionSRTCP, error) { //nolint:dupl if config == nil { return nil, errNoConfig } else if conn == nil { return nil, errNoConn } loggerFactory := config.LoggerFactory if loggerFactory == nil { loggerFactory = logging.NewDefaultLoggerFactory() } localOpts := append( []ContextOption{}, config.LocalOptions..., ) remoteOpts := append( []ContextOption{ // Default options SRTCPReplayProtection(defaultSessionSRTCPReplayProtectionWindow), }, config.RemoteOptions..., ) srtcpSession := &SessionSRTCP{ session: session{ nextConn: conn, localOptions: localOpts, remoteOptions: remoteOpts, readStreams: map[uint32]readStream{}, newStream: make(chan readStream), acceptStreamTimeout: config.AcceptStreamTimeout, started: make(chan any), closed: make(chan any), bufferFactory: config.BufferFactory, log: loggerFactory.NewLogger("srtp"), }, } srtcpSession.writeStream = &WriteStreamSRTCP{srtcpSession} err := srtcpSession.session.start( config.Keys.LocalMasterKey, config.Keys.LocalMasterSalt, config.Keys.RemoteMasterKey, config.Keys.RemoteMasterSalt, config.Profile, srtcpSession, ) if err != nil { return nil, err } return srtcpSession, nil } // OpenWriteStream returns the global write stream for the Session. func (s *SessionSRTCP) OpenWriteStream() (*WriteStreamSRTCP, error) { return s.writeStream, nil } // OpenReadStream opens a read stream for the given SSRC, it can be used // if you want a certain SSRC, but don't want to wait for AcceptStream. func (s *SessionSRTCP) OpenReadStream(ssrc uint32) (*ReadStreamSRTCP, error) { r, _ := s.session.getOrCreateReadStream(ssrc, s, newReadStreamSRTCP) if readStream, ok := r.(*ReadStreamSRTCP); ok { return readStream, nil } return nil, errFailedTypeAssertion } // AcceptStream returns a stream to handle RTCP for a single SSRC. func (s *SessionSRTCP) AcceptStream() (*ReadStreamSRTCP, uint32, error) { stream, ok := <-s.newStream if !ok { return nil, 0, errStreamAlreadyClosed } readStream, ok := stream.(*ReadStreamSRTCP) if !ok { return nil, 0, errFailedTypeAssertion } return readStream, stream.GetSSRC(), nil } // Close ends the session. func (s *SessionSRTCP) Close() error { return s.session.close() } // Private func (s *SessionSRTCP) write(buf []byte) (int, error) { if _, ok := <-s.session.started; ok { return 0, errStartedChannelUsedIncorrectly } ibuf := bufferpool.Get() defer bufferpool.Put(ibuf) s.session.localContextMutex.Lock() encrypted, err := s.localContext.EncryptRTCP(ibuf.([]byte), buf, nil) //nolint:forcetypeassert s.session.localContextMutex.Unlock() if err != nil { return 0, err } return s.session.nextConn.Write(encrypted) } func (s *SessionSRTCP) setWriteDeadline(t time.Time) error { return s.session.nextConn.SetWriteDeadline(t) } // create a list of Destination SSRCs // that's a superset of all Destinations in the slice. func destinationSSRC(pkts []rtcp.Packet) []uint32 { ssrcSet := make(map[uint32]struct{}) for _, p := range pkts { for _, ssrc := range p.DestinationSSRC() { ssrcSet[ssrc] = struct{}{} } } out := make([]uint32, 0, len(ssrcSet)) for ssrc := range ssrcSet { out = append(out, ssrc) } return out } func (s *SessionSRTCP) decrypt(buf []byte) error { decrypted, err := s.remoteContext.DecryptRTCP(buf, buf, nil) if err != nil { return err } pkt, err := rtcp.Unmarshal(decrypted) if err != nil { return err } for _, ssrc := range destinationSSRC(pkt) { r, isNew := s.session.getOrCreateReadStream(ssrc, s, newReadStreamSRTCP) if r == nil { return nil // Session has been closed } else if isNew { if !s.session.acceptStreamTimeout.IsZero() { _ = s.session.nextConn.SetReadDeadline(time.Time{}) } s.session.newStream <- r // Notify AcceptStream } readStream, ok := r.(*ReadStreamSRTCP) if !ok { return errFailedTypeAssertion } _, err = readStream.write(decrypted) if err != nil { return err } } return nil } srtp-3.0.9/session_srtcp_test.go000066400000000000000000000222071511235350500170020ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "errors" "io" "net" "strings" "sync" "testing" "time" "github.com/pion/rtcp" "github.com/pion/transport/v3/test" "github.com/stretchr/testify/assert" ) const rtcpHeaderSize = 4 func TestSessionSRTCPBadInit(t *testing.T) { _, err := NewSessionSRTCP(nil, nil) assert.Error(t, err, "NewSessionSRTCP should error if no config was provided") _, err = NewSessionSRTCP(nil, &Config{}) assert.Error(t, err, "NewSessionSRTCP should error if no net was provided") } func buildSessionSRTCP(t *testing.T) (*SessionSRTCP, net.Conn, *Config) { //nolint:dupl t.Helper() aPipe, bPipe := net.Pipe() config := &Config{ Profile: ProtectionProfileAes128CmHmacSha1_80, Keys: SessionKeys{ []byte{0xE1, 0xF9, 0x7A, 0x0D, 0x3E, 0x01, 0x8B, 0xE0, 0xD6, 0x4F, 0xA3, 0x2C, 0x06, 0xDE, 0x41, 0x39}, []byte{0x0E, 0xC6, 0x75, 0xAD, 0x49, 0x8A, 0xFE, 0xEB, 0xB6, 0x96, 0x0B, 0x3A, 0xAB, 0xE6}, []byte{0xE1, 0xF9, 0x7A, 0x0D, 0x3E, 0x01, 0x8B, 0xE0, 0xD6, 0x4F, 0xA3, 0x2C, 0x06, 0xDE, 0x41, 0x39}, []byte{0x0E, 0xC6, 0x75, 0xAD, 0x49, 0x8A, 0xFE, 0xEB, 0xB6, 0x96, 0x0B, 0x3A, 0xAB, 0xE6}, }, } aSession, err := NewSessionSRTCP(aPipe, config) assert.NoError(t, err) assert.NotNil(t, aSession, "NewSessionSRTCP did not error, but returned nil session") return aSession, bPipe, config } func buildSessionSRTCPPair(t *testing.T) (*SessionSRTCP, *SessionSRTCP) { //nolint:dupl t.Helper() aSession, bPipe, config := buildSessionSRTCP(t) bSession, err := NewSessionSRTCP(bPipe, config) assert.NoError(t, err) assert.NotNil(t, bSession, "NewSessionSRTCP did not error, but returned nil session") return aSession, bSession } func TestSessionSRTCP(t *testing.T) { lim := test.TimeOut(time.Second * 10) defer lim.Stop() report := test.CheckRoutines(t) defer report() testPayload, err := rtcp.Marshal([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: 5000}}) assert.NoError(t, err) readBuffer := make([]byte, len(testPayload)) aSession, bSession := buildSessionSRTCPPair(t) aWriteStream, err := aSession.OpenWriteStream() assert.NoError(t, err) _, err = aWriteStream.Write(testPayload) assert.NoError(t, err) bReadStream, _, err := bSession.AcceptStream() assert.NoError(t, err) _, err = bReadStream.Read(readBuffer) assert.NoError(t, err) assert.Equalf(t, readBuffer, testPayload, "Sent buffer does not match the one received exp(%v) actual(%v)", testPayload, readBuffer) assert.NoError(t, aSession.Close()) assert.NoError(t, bSession.Close()) } func TestSessionSRTCPWithIODeadline(t *testing.T) { lim := test.TimeOut(time.Second * 10) defer lim.Stop() report := test.CheckRoutines(t) defer report() testPayload, err := rtcp.Marshal([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: 5000}}) assert.NoError(t, err) readBuffer := make([]byte, len(testPayload)) aSession, bPipe, config := buildSessionSRTCP(t) aWriteStream, err := aSession.OpenWriteStream() assert.NoError(t, err) // When the other peer is not ready, the Write would be blocked if no deadline. assert.NoError(t, aWriteStream.SetWriteDeadline(time.Now().Add(1*time.Second))) _, err = aWriteStream.Write(testPayload) assert.Truef(t, errIsTimeout(err), "Unexpected read-error(%v)", err) assert.NoError(t, aWriteStream.SetWriteDeadline(time.Time{})) // Setup another peer. bSession, err := NewSessionSRTCP(bPipe, config) assert.NoError(t, err) assert.NotNil(t, bSession, "NewSessionSRTCP did not error, but returned nil session") // The second attempt to write. _, err = aWriteStream.Write(testPayload) // The other peer is ready, this write attempt should work. assert.NoError(t, err) bReadStream, _, err := bSession.AcceptStream() assert.NoError(t, err) _, err = bReadStream.Read(readBuffer) assert.NoError(t, err) assert.Equalf(t, readBuffer, testPayload, "Sent buffer does not match the one received exp(%v) actual(%v)", testPayload, readBuffer) // The second Read attempt would be blocked if the deadline is not set. assert.NoError(t, bReadStream.SetReadDeadline(time.Now().Add(1*time.Second))) _, err = bReadStream.Read(readBuffer) assert.Truef(t, errIsTimeout(err), "Unexpected read-error(%v)", err) assert.NoError(t, aSession.Close()) assert.NoError(t, bSession.Close()) } func TestSessionSRTCPOpenReadStream(t *testing.T) { lim := test.TimeOut(time.Second * 10) defer lim.Stop() report := test.CheckRoutines(t) defer report() testPayload, err := rtcp.Marshal([]rtcp.Packet{&rtcp.PictureLossIndication{MediaSSRC: 5000}}) assert.NoError(t, err) readBuffer := make([]byte, len(testPayload)) aSession, bSession := buildSessionSRTCPPair(t) bReadStream, err := bSession.OpenReadStream(5000) assert.NoError(t, err) aWriteStream, err := aSession.OpenWriteStream() assert.NoError(t, err) _, err = aWriteStream.Write(testPayload) assert.NoError(t, err) _, err = bReadStream.Read(readBuffer) assert.NoError(t, err) assert.Equalf(t, readBuffer, testPayload, "Sent buffer does not match the one received exp(%v) actual(%v)", testPayload, readBuffer) assert.NoError(t, aSession.Close()) assert.NoError(t, bSession.Close()) } func TestSessionSRTCPReplayProtection(t *testing.T) { lim := test.TimeOut(time.Second * 5) defer lim.Stop() report := test.CheckRoutines(t) defer report() const ( testSSRC = 5000 ) aSession, bSession := buildSessionSRTCPPair(t) bReadStream, err := bSession.OpenReadStream(testSSRC) assert.NoError(t, err) // Generate test packets var packets [][]byte var expectedSSRC []uint32 for i := uint32(0); i < 0x100; i++ { testPacket := &rtcp.PictureLossIndication{ MediaSSRC: testSSRC, SenderSSRC: i, } expectedSSRC = append(expectedSSRC, i) encrypted, eerr := encryptSRTCP(aSession.session.localContext, testPacket) assert.NoError(t, eerr) packets = append(packets, encrypted) } // Receive SRTCP packets with replay protection var receivedSSRC []uint32 var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() for { if ssrc, perr := getSenderSSRC(t, bReadStream); perr == nil { receivedSSRC = append(receivedSSRC, ssrc) } else if errors.Is(perr, io.EOF) { return } } }() // Write with replay attack for _, p := range packets { _, err = aSession.session.nextConn.Write(p) assert.NoError(t, err) // Immediately replay _, err = aSession.session.nextConn.Write(p) assert.NoError(t, err) } for _, p := range packets { // Delayed replay _, err = aSession.session.nextConn.Write(p) assert.NoError(t, err) } assert.NoError(t, aSession.Close()) assert.NoError(t, bSession.Close()) assert.NoError(t, bReadStream.Close()) wg.Wait() assert.Equalf(t, expectedSSRC, receivedSSRC, "Expected and received SSRCs differ,\nexpected:\n%v\nreceived:\n%v", expectedSSRC, receivedSSRC) } // nolint: dupl func TestSessionSRTCPAcceptStreamTimeout(t *testing.T) { lim := test.TimeOut(time.Second * 5) defer lim.Stop() report := test.CheckRoutines(t) defer report() pipe, _ := net.Pipe() config := &Config{ Profile: ProtectionProfileAes128CmHmacSha1_80, Keys: SessionKeys{ []byte{0xE1, 0xF9, 0x7A, 0x0D, 0x3E, 0x01, 0x8B, 0xE0, 0xD6, 0x4F, 0xA3, 0x2C, 0x06, 0xDE, 0x41, 0x39}, []byte{0x0E, 0xC6, 0x75, 0xAD, 0x49, 0x8A, 0xFE, 0xEB, 0xB6, 0x96, 0x0B, 0x3A, 0xAB, 0xE6}, []byte{0xE1, 0xF9, 0x7A, 0x0D, 0x3E, 0x01, 0x8B, 0xE0, 0xD6, 0x4F, 0xA3, 0x2C, 0x06, 0xDE, 0x41, 0x39}, []byte{0x0E, 0xC6, 0x75, 0xAD, 0x49, 0x8A, 0xFE, 0xEB, 0xB6, 0x96, 0x0B, 0x3A, 0xAB, 0xE6}, }, AcceptStreamTimeout: time.Now().Add(3 * time.Second), } newSession, err := NewSessionSRTCP(pipe, config) assert.NoError(t, err) assert.NotNil(t, newSession, "NewSessionSRTCP did not error, but returned nil session") _, _, err = newSession.AcceptStream() if !errors.Is(err, errStreamAlreadyClosed) { assert.NoError(t, err) } assert.NoError(t, newSession.Close()) } func getSenderSSRC(t *testing.T, stream *ReadStreamSRTCP) (ssrc uint32, err error) { t.Helper() authTagSize, err := ProtectionProfileAes128CmHmacSha1_80.AuthTagRTCPLen() if err != nil { return 0, err } const pliPacketSize = 8 readBuffer := make([]byte, pliPacketSize+authTagSize+srtcpIndexSize) n, _, err := stream.ReadRTCP(readBuffer) if errors.Is(err, io.EOF) { return 0, err } if err != nil { assert.NoError(t, err) return 0, err } pli := &rtcp.PictureLossIndication{} if uerr := pli.Unmarshal(readBuffer[:n]); uerr != nil { assert.NoError(t, uerr) return 0, uerr } return pli.SenderSSRC, nil } func encryptSRTCP(context *Context, pkt rtcp.Packet) ([]byte, error) { decryptedRaw, err := pkt.Marshal() if err != nil { return nil, err } encryptInput := make([]byte, len(decryptedRaw), rtcpHeaderSize+len(decryptedRaw)+10) copy(encryptInput, decryptedRaw) encrypted, eerr := context.EncryptRTCP(encryptInput, encryptInput, nil) if eerr != nil { return nil, eerr } return encrypted, nil } func errIsTimeout(err error) bool { if err == nil { return false } s := err.Error() switch { case strings.Contains(s, "i/o timeout"): // error message when timeout before go1.15. return true case strings.Contains(s, "deadline exceeded"): // error message when timeout after go1.15. return true } return false } srtp-3.0.9/session_srtp.go000066400000000000000000000134431511235350500156020ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "net" "sync" "time" "github.com/pion/logging" "github.com/pion/rtp" ) const defaultSessionSRTPReplayProtectionWindow = 64 // SessionSRTP implements io.ReadWriteCloser and provides a bi-directional SRTP session // SRTP itself does not have a design like this, but it is common in most applications // for local/remote to each have their own keying material. This provides those patterns // instead of making everyone re-implement. type SessionSRTP struct { session writeStream *WriteStreamSRTP } // NewSessionSRTP creates a SRTP session using conn as the underlying transport. func NewSessionSRTP(conn net.Conn, config *Config) (*SessionSRTP, error) { //nolint:dupl if config == nil { return nil, errNoConfig } else if conn == nil { return nil, errNoConn } loggerFactory := config.LoggerFactory if loggerFactory == nil { loggerFactory = logging.NewDefaultLoggerFactory() } localOpts := append( []ContextOption{}, config.LocalOptions..., ) remoteOpts := append( []ContextOption{ // Default options SRTPReplayProtection(defaultSessionSRTPReplayProtectionWindow), }, config.RemoteOptions..., ) srtpSession := &SessionSRTP{ session: session{ nextConn: conn, localOptions: localOpts, remoteOptions: remoteOpts, readStreams: map[uint32]readStream{}, newStream: make(chan readStream), acceptStreamTimeout: config.AcceptStreamTimeout, started: make(chan any), closed: make(chan any), bufferFactory: config.BufferFactory, log: loggerFactory.NewLogger("srtp"), }, } srtpSession.writeStream = &WriteStreamSRTP{srtpSession} err := srtpSession.session.start( config.Keys.LocalMasterKey, config.Keys.LocalMasterSalt, config.Keys.RemoteMasterKey, config.Keys.RemoteMasterSalt, config.Profile, srtpSession, ) if err != nil { return nil, err } return srtpSession, nil } // OpenWriteStream returns the global write stream for the Session. func (s *SessionSRTP) OpenWriteStream() (*WriteStreamSRTP, error) { return s.writeStream, nil } // OpenReadStream opens a read stream for the given SSRC, it can be used // if you want a certain SSRC, but don't want to wait for AcceptStream. func (s *SessionSRTP) OpenReadStream(ssrc uint32) (*ReadStreamSRTP, error) { r, _ := s.session.getOrCreateReadStream(ssrc, s, newReadStreamSRTP) if readStream, ok := r.(*ReadStreamSRTP); ok { return readStream, nil } return nil, errFailedTypeAssertion } // AcceptStream returns a stream to handle RTCP for a single SSRC. func (s *SessionSRTP) AcceptStream() (*ReadStreamSRTP, uint32, error) { stream, ok := <-s.newStream if !ok { return nil, 0, errStreamAlreadyClosed } readStream, ok := stream.(*ReadStreamSRTP) if !ok { return nil, 0, errFailedTypeAssertion } return readStream, stream.GetSSRC(), nil } // Close ends the session. func (s *SessionSRTP) Close() error { return s.session.close() } func (s *SessionSRTP) write(b []byte) (int, error) { packet := &rtp.Packet{} if err := packet.Unmarshal(b); err != nil { return 0, err } return s.writeRTP(&packet.Header, packet.Payload) } // bufferpool is a global pool of buffers used for encrypted packets in // writeRTP below. Since it's global, buffers can be shared between // different sessions, which amortizes the cost of allocating the pool. // // 1472 is the maximum Ethernet UDP payload. We give ourselves 20 bytes // of slack for any authentication tags, which is more than enough for // either CTR or GCM. If the buffer is too small, no harm, it will just // get expanded by growBuffer. var bufferpool = sync.Pool{ // nolint:gochecknoglobals New: func() any { return make([]byte, 1492) }, } func (s *SessionSRTP) writeRTP(header *rtp.Header, payload []byte) (int, error) { if _, ok := <-s.session.started; ok { return 0, errStartedChannelUsedIncorrectly } // encryptRTP will either return our buffer, or, if it is too // small, allocate a new buffer itself. In either case, it is // safe to put the buffer back into the pool, but only after // nextConn.Write has returned. ibuf := bufferpool.Get() defer bufferpool.Put(ibuf) buf := ibuf.([]byte) // nolint:forcetypeassert headerLen, marshalSize := rtp.HeaderAndPacketMarshalSize(header, payload) // nolint:staticcheck if len(buf) < marshalSize+20 { // The buffer is too small, so we need to allocate a new one. Add 20 bytes for auth tag like // for bufferpool above. buf = make([]byte, marshalSize+20) } _, err := rtp.MarshalPacketTo(buf, header, payload) // nolint:staticcheck if err != nil { return 0, err } s.session.localContextMutex.Lock() encrypted, err := s.localContext.encryptRTP(buf, header, headerLen, buf[:marshalSize]) s.session.localContextMutex.Unlock() if err != nil { return 0, err } return s.session.nextConn.Write(encrypted) } func (s *SessionSRTP) setWriteDeadline(t time.Time) error { return s.session.nextConn.SetWriteDeadline(t) } func (s *SessionSRTP) decrypt(buf []byte) error { header := &rtp.Header{} headerLen, err := header.Unmarshal(buf) if err != nil { return err } r, isNew := s.session.getOrCreateReadStream(header.SSRC, s, newReadStreamSRTP) if r == nil { return nil // Session has been closed } else if isNew { if !s.session.acceptStreamTimeout.IsZero() { _ = s.session.nextConn.SetReadDeadline(time.Time{}) } s.session.newStream <- r // Notify AcceptStream } readStream, ok := r.(*ReadStreamSRTP) if !ok { return errFailedTypeAssertion } decrypted, err := s.remoteContext.decryptRTP(buf, buf, header, headerLen) if err != nil { return err } _, err = readStream.write(decrypted) if err != nil { return err } return nil } srtp-3.0.9/session_srtp_test.go000066400000000000000000000303211511235350500166330ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "errors" "io" "net" "sync" "testing" "time" "github.com/pion/rtp" "github.com/pion/transport/v3/test" "github.com/stretchr/testify/assert" ) func TestSessionSRTPBadInit(t *testing.T) { _, err := NewSessionSRTP(nil, nil) assert.Error(t, err, "NewSessionSRTP should error if no net was provided") _, err = NewSessionSRTP(nil, &Config{}) assert.Error(t, err, "NewSessionSRTP should error if no net was provided") } func buildSessionSRTP(t *testing.T) (*SessionSRTP, net.Conn, *Config) { //nolint:dupl t.Helper() aPipe, bPipe := net.Pipe() config := &Config{ Profile: ProtectionProfileAes128CmHmacSha1_80, Keys: SessionKeys{ []byte{0xE1, 0xF9, 0x7A, 0x0D, 0x3E, 0x01, 0x8B, 0xE0, 0xD6, 0x4F, 0xA3, 0x2C, 0x06, 0xDE, 0x41, 0x39}, []byte{0x0E, 0xC6, 0x75, 0xAD, 0x49, 0x8A, 0xFE, 0xEB, 0xB6, 0x96, 0x0B, 0x3A, 0xAB, 0xE6}, []byte{0xE1, 0xF9, 0x7A, 0x0D, 0x3E, 0x01, 0x8B, 0xE0, 0xD6, 0x4F, 0xA3, 0x2C, 0x06, 0xDE, 0x41, 0x39}, []byte{0x0E, 0xC6, 0x75, 0xAD, 0x49, 0x8A, 0xFE, 0xEB, 0xB6, 0x96, 0x0B, 0x3A, 0xAB, 0xE6}, }, } aSession, err := NewSessionSRTP(aPipe, config) assert.NoError(t, err) assert.NotNil(t, aSession, "NewSessionSRTP did not error, but returned nil session") return aSession, bPipe, config } func buildSessionSRTPPair(t *testing.T) (*SessionSRTP, *SessionSRTP) { //nolint:dupl t.Helper() aSession, bPipe, config := buildSessionSRTP(t) bSession, err := NewSessionSRTP(bPipe, config) assert.NoError(t, err) assert.NotNil(t, bSession, "NewSessionSRTP did not error, but returned nil session") return aSession, bSession } func TestSessionSRTP(t *testing.T) { lim := test.TimeOut(time.Second * 5) defer lim.Stop() report := test.CheckRoutines(t) defer report() const ( testSSRC = 5000 rtpHeaderSize = 12 ) testPayload := []byte{0x00, 0x01, 0x03, 0x04} readBuffer := make([]byte, rtpHeaderSize+len(testPayload)) aSession, bSession := buildSessionSRTPPair(t) aWriteStream, err := aSession.OpenWriteStream() assert.NoError(t, err) _, err = aWriteStream.WriteRTP(&rtp.Header{SSRC: testSSRC}, append([]byte{}, testPayload...)) assert.NoError(t, err) bReadStream, ssrc, err := bSession.AcceptStream() assert.NoError(t, err) assert.Equalf(t, uint32(testSSRC), ssrc, "SSRC mismatch during accept exp(%v) actual(%v)", testSSRC, ssrc) _, err = bReadStream.Read(readBuffer) assert.NoError(t, err) assert.Equalf(t, readBuffer[rtpHeaderSize:], testPayload, "Sent buffer does not match the one received exp(%v) actual(%v)", testPayload, readBuffer[rtpHeaderSize:]) assert.NoError(t, aSession.Close()) assert.NoError(t, bSession.Close()) } func TestSessionSRTPWithIODeadline(t *testing.T) { lim := test.TimeOut(time.Second * 10) defer lim.Stop() report := test.CheckRoutines(t) defer report() const ( testSSRC = 5000 rtpHeaderSize = 12 ) testPayload := []byte{0x00, 0x01, 0x03, 0x04} readBuffer := make([]byte, rtpHeaderSize+len(testPayload)) aSession, bPipe, config := buildSessionSRTP(t) aWriteStream, err := aSession.OpenWriteStream() assert.NoError(t, err) // When the other peer is not ready, the Write would be blocked if no deadline. assert.NoError(t, aWriteStream.SetWriteDeadline(time.Now().Add(1*time.Second))) _, err = aWriteStream.WriteRTP(&rtp.Header{SSRC: testSSRC}, append([]byte{}, testPayload...)) assert.Truef(t, errIsTimeout(err), "Unexpected read-error(%v)", err) assert.NoError(t, aWriteStream.SetWriteDeadline(time.Time{})) // Setup another peer. bSession, err := NewSessionSRTP(bPipe, config) assert.NoError(t, err) assert.NotNil(t, bSession, "NewSessionSRTP did not error, but returned nil session") // The second attempt to write, even without deadline. _, err = aWriteStream.WriteRTP(&rtp.Header{SSRC: testSSRC}, append([]byte{}, testPayload...)) assert.NoError(t, err) bReadStream, ssrc, err := bSession.AcceptStream() assert.NoError(t, err) assert.Equal(t, uint32(testSSRC), ssrc, "SSRC mismatch during accept exp(%v) actual(%v)", testSSRC, ssrc) _, err = bReadStream.Read(readBuffer) assert.NoError(t, err) assert.Equal(t, testPayload, readBuffer[rtpHeaderSize:], "Sent buffer does not match the one received exp(%v) actual(%v)", testPayload, readBuffer[rtpHeaderSize:]) // The second Read attempt would be blocked if the deadline is not set. assert.NoError(t, bReadStream.SetReadDeadline(time.Now().Add(1*time.Second))) _, err = bReadStream.Read(readBuffer) assert.Truef(t, errIsTimeout(err), "Unexpected read-error(%v)", err) assert.NoError(t, aSession.Close()) assert.NoError(t, bSession.Close()) } func TestSessionSRTPOpenReadStream(t *testing.T) { lim := test.TimeOut(time.Second * 5) defer lim.Stop() report := test.CheckRoutines(t) defer report() const ( testSSRC = 5000 rtpHeaderSize = 12 ) testPayload := []byte{0x00, 0x01, 0x03, 0x04} readBuffer := make([]byte, rtpHeaderSize+len(testPayload)) aSession, bSession := buildSessionSRTPPair(t) bReadStream, err := bSession.OpenReadStream(5000) assert.NoError(t, err) aWriteStream, err := aSession.OpenWriteStream() assert.NoError(t, err) _, err = aWriteStream.WriteRTP(&rtp.Header{SSRC: testSSRC}, append([]byte{}, testPayload...)) assert.NoError(t, err) _, err = bReadStream.Read(readBuffer) assert.NoError(t, err) assert.Equalf(t, testPayload, readBuffer[rtpHeaderSize:], "Sent buffer does not match the one received exp(%v) actual(%v)", testPayload, readBuffer[rtpHeaderSize:]) assert.NoError(t, aSession.Close()) assert.NoError(t, bSession.Close()) } func TestSessionSRTPMultiSSRC(t *testing.T) { lim := test.TimeOut(time.Second * 5) defer lim.Stop() report := test.CheckRoutines(t) defer report() const rtpHeaderSize = 12 ssrcs := []uint32{5000, 5001, 5002} testPayload := []byte{0x00, 0x01, 0x03, 0x04} aSession, bSession := buildSessionSRTPPair(t) bReadStreams := make(map[uint32]*ReadStreamSRTP) for _, ssrc := range ssrcs { bReadStream, err := bSession.OpenReadStream(ssrc) assert.NoError(t, err) bReadStreams[ssrc] = bReadStream } aWriteStream, err := aSession.OpenWriteStream() assert.NoError(t, err) for _, ssrc := range ssrcs { _, err = aWriteStream.WriteRTP(&rtp.Header{SSRC: ssrc}, append([]byte{}, testPayload...)) assert.NoError(t, err) readBuffer := make([]byte, rtpHeaderSize+len(testPayload)) _, err = bReadStreams[ssrc].Read(readBuffer) assert.NoError(t, err) assert.Equal(t, testPayload, readBuffer[rtpHeaderSize:], "Sent buffer does not match the one received exp(%v) actual(%v)", testPayload, readBuffer[rtpHeaderSize:]) } assert.NoError(t, aSession.Close()) assert.NoError(t, bSession.Close()) } func TestSessionSRTPReplayProtection(t *testing.T) { lim := test.TimeOut(time.Second * 5) defer lim.Stop() report := test.CheckRoutines(t) defer report() const ( testSSRC = 5000 rtpHeaderSize = 12 ) testPayload := []byte{0x00, 0x01, 0x03, 0x04} aSession, bSession := buildSessionSRTPPair(t) bReadStream, err := bSession.OpenReadStream(testSSRC) assert.NoError(t, err) // Generate test packets var packets [][]byte var expectedSequenceNumber []uint16 for i := uint16(0xFF00); i != 0x100; i++ { expectedSequenceNumber = append(expectedSequenceNumber, i) encrypted, eerr := encryptSRTP(aSession.session.localContext, &rtp.Packet{ Header: rtp.Header{ SSRC: testSSRC, SequenceNumber: i, }, Payload: testPayload, }) assert.NoError(t, eerr) packets = append(packets, encrypted) } // Receive SRTP packets with replay protection var receivedSequenceNumber []uint16 var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() for { if seq, perr := assertPayloadSRTP(t, bReadStream, rtpHeaderSize, testPayload); perr == nil { receivedSequenceNumber = append(receivedSequenceNumber, seq) } else if errors.Is(perr, io.EOF) { return } } }() // Write with replay attack for _, p := range packets { _, err = aSession.session.nextConn.Write(p) assert.NoError(t, err) // Immediately replay _, err = aSession.session.nextConn.Write(p) assert.NoError(t, err) } for _, p := range packets { // Delayed replay _, err = aSession.session.nextConn.Write(p) assert.NoError(t, err) } assert.NoError(t, aSession.Close()) assert.NoError(t, bSession.Close()) assert.NoError(t, bReadStream.Close()) wg.Wait() assert.Equalf(t, expectedSequenceNumber, receivedSequenceNumber, "Expected and received sequence number differs,\nexpected:\n%v\nreceived:\n%v", expectedSequenceNumber, receivedSequenceNumber) } // nolint: dupl func TestSessionSRTPAcceptStreamTimeout(t *testing.T) { lim := test.TimeOut(time.Second * 5) defer lim.Stop() report := test.CheckRoutines(t) defer report() pipe, _ := net.Pipe() config := &Config{ Profile: ProtectionProfileAes128CmHmacSha1_80, Keys: SessionKeys{ []byte{0xE1, 0xF9, 0x7A, 0x0D, 0x3E, 0x01, 0x8B, 0xE0, 0xD6, 0x4F, 0xA3, 0x2C, 0x06, 0xDE, 0x41, 0x39}, []byte{0x0E, 0xC6, 0x75, 0xAD, 0x49, 0x8A, 0xFE, 0xEB, 0xB6, 0x96, 0x0B, 0x3A, 0xAB, 0xE6}, []byte{0xE1, 0xF9, 0x7A, 0x0D, 0x3E, 0x01, 0x8B, 0xE0, 0xD6, 0x4F, 0xA3, 0x2C, 0x06, 0xDE, 0x41, 0x39}, []byte{0x0E, 0xC6, 0x75, 0xAD, 0x49, 0x8A, 0xFE, 0xEB, 0xB6, 0x96, 0x0B, 0x3A, 0xAB, 0xE6}, }, AcceptStreamTimeout: time.Now().Add(3 * time.Second), } newSession, err := NewSessionSRTP(pipe, config) assert.NoError(t, err) assert.NotNil(t, newSession, "NewSessionSRTP did not error, but returned nil session") _, _, err = newSession.AcceptStream() assert.ErrorIs(t, err, errStreamAlreadyClosed) assert.NoError(t, newSession.Close()) } func assertPayloadSRTP( t *testing.T, stream *ReadStreamSRTP, headerSize int, expectedPayload []byte, ) (seq uint16, err error) { t.Helper() readBuffer := make([]byte, headerSize+len(expectedPayload)) n, hdr, err := stream.ReadRTP(readBuffer) if errors.Is(err, io.EOF) { return 0, err } if !assert.NoError(t, err) { return 0, err } if !assert.Equalf(t, expectedPayload, readBuffer[headerSize:n], "Sent buffer does not match the one received exp(%v) actual(%v)", expectedPayload, readBuffer[headerSize:n]) { return 0, errPayloadDiffers } return hdr.SequenceNumber, nil } func encryptSRTP(context *Context, pkt *rtp.Packet) ([]byte, error) { decryptedRaw, err := pkt.Marshal() if err != nil { return nil, err } encryptInput := make([]byte, len(decryptedRaw), len(decryptedRaw)+10) copy(encryptInput, decryptedRaw) encrypted, eerr := context.EncryptRTP(encryptInput, encryptInput, nil) if eerr != nil { return nil, eerr } return encrypted, nil } func TestSessionSRTPPacketWithPadding(t *testing.T) { lim := test.TimeOut(time.Second * 5) defer lim.Stop() report := test.CheckRoutines(t) defer report() const ( testSSRC = 5000 rtpHeaderSize = 12 paddingSize = 5 authTagLen = 10 // For AES_CM_128_HMAC_SHA1_80, the auth tag length is 10 bytes. ) testPayload := []byte{0x00, 0x01, 0x03, 0x04} readBuffer := make([]byte, rtpHeaderSize+paddingSize+len(testPayload)) aSession, bSession := buildSessionSRTPPair(t) aWriteStream, err := aSession.OpenWriteStream() assert.NoError(t, err) writeBytes, err := aWriteStream.WriteRTP(&rtp.Header{SSRC: testSSRC, Padding: true, PaddingSize: paddingSize}, append([]byte{}, testPayload...)) assert.NoError(t, err) assert.Equalf(t, rtpHeaderSize+paddingSize+len(testPayload)+authTagLen, writeBytes, "WriteRTP should return the size of the packet including padding, exp(%v) actual(%v)", rtpHeaderSize+paddingSize+len(testPayload)+authTagLen, writeBytes) bReadStream, ssrc, err := bSession.AcceptStream() assert.NoError(t, err) assert.Equalf(t, uint32(testSSRC), ssrc, "SSRC mismatch during accept exp(%v) actual(%v)", testSSRC, ssrc) readBytes, err := bReadStream.Read(readBuffer) assert.NoError(t, err) assert.Equal(t, rtpHeaderSize+paddingSize+len(testPayload), readBytes, "Read should return the size of the packet including padding, exp(%v) actual(%v)", rtpHeaderSize+paddingSize+len(testPayload), readBytes) var rtpPacket rtp.Packet err = rtpPacket.Unmarshal(readBuffer[:readBytes]) assert.NoError(t, err) assert.Equal(t, rtpPacket.Padding, true) assert.Equal(t, rtpPacket.PaddingSize, byte(paddingSize)) assert.Equal(t, rtpPacket.Payload, testPayload) assert.NoError(t, aSession.Close()) assert.NoError(t, bSession.Close()) } srtp-3.0.9/srtcp.go000066400000000000000000000060511511235350500141770ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "encoding/binary" "fmt" "github.com/pion/rtcp" ) /* Simplified structure of SRTCP Packets: - RTCP Header - Payload - AEAD Auth Tag - used by AEAD profiles only - E flag and SRTCP Index - MKI (optional) - Auth Tag - used by non-AEAD profiles only */ const ( maxSRTCPIndex = 0x7FFFFFFF srtcpHeaderSize = 8 srtcpIndexSize = 4 srtcpEncryptionFlag = 0x80 ) func (c *Context) decryptRTCP(dst, encrypted []byte) ([]byte, error) { authTagLen, err := c.cipher.AuthTagRTCPLen() if err != nil { return nil, err } aeadAuthTagLen, err := c.cipher.AEADAuthTagLen() if err != nil { return nil, err } mkiLen := len(c.sendMKI) // Verify that encrypted packet is long enough if len(encrypted) < (srtcpHeaderSize + aeadAuthTagLen + srtcpIndexSize + mkiLen + authTagLen) { return nil, fmt.Errorf("%w: %d", errTooShortRTCP, len(encrypted)) } index := c.cipher.getRTCPIndex(encrypted) ssrc := binary.BigEndian.Uint32(encrypted[4:]) s := c.getSRTCPSSRCState(ssrc) markAsValid, ok := s.replayDetector.Check(uint64(index)) if !ok { return nil, &duplicatedError{Proto: "srtcp", SSRC: ssrc, Index: index} } cipher := c.cipher if len(c.mkis) > 0 { // Find cipher for MKI actualMKI := encrypted[len(encrypted)-mkiLen-authTagLen : len(encrypted)-authTagLen] cipher, ok = c.mkis[string(actualMKI)] if !ok { return nil, ErrMKINotFound } } out, err := cipher.decryptRTCP(dst, encrypted, index, ssrc) if err != nil { return nil, err } markAsValid() return out, nil } // DecryptRTCP decrypts a buffer that contains a RTCP packet. func (c *Context) DecryptRTCP(dst, encrypted []byte, header *rtcp.Header) ([]byte, error) { if header == nil { header = &rtcp.Header{} } if err := header.Unmarshal(encrypted); err != nil { return nil, err } return c.decryptRTCP(dst, encrypted) } func (c *Context) encryptRTCP(dst, decrypted []byte) ([]byte, error) { if len(decrypted) < srtcpHeaderSize { return nil, fmt.Errorf("%w: %d", errTooShortRTCP, len(decrypted)) } ssrc := binary.BigEndian.Uint32(decrypted[4:]) ssrcState := c.getSRTCPSSRCState(ssrc) if ssrcState.srtcpIndex >= maxSRTCPIndex { // ... when 2^48 SRTP packets or 2^31 SRTCP packets have been secured with the same key // (whichever occurs before), the key management MUST be called to provide new master key(s) // (previously stored and used keys MUST NOT be used again), or the session MUST be terminated. // https://www.rfc-editor.org/rfc/rfc3711#section-9.2 return nil, errExceededMaxPackets } // We roll over early because MSB is used for marking as encrypted ssrcState.srtcpIndex++ return c.cipher.encryptRTCP(dst, decrypted, ssrcState.srtcpIndex, ssrc) } // EncryptRTCP Encrypts a RTCP packet. func (c *Context) EncryptRTCP(dst, decrypted []byte, header *rtcp.Header) ([]byte, error) { if header == nil { header = &rtcp.Header{} } if err := header.Unmarshal(decrypted); err != nil { return nil, err } return c.encryptRTCP(dst, decrypted) } srtp-3.0.9/srtcp_test.go000066400000000000000000000611021511235350500152340ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "encoding/binary" "testing" "github.com/pion/rtcp" "github.com/pion/transport/v3/replaydetector" "github.com/stretchr/testify/assert" ) type rtcpTestPacket struct { ssrc uint32 index uint32 pktType rtcp.PacketType encrypted []byte decrypted []byte } type rtcpTestCase struct { algo ProtectionProfile masterKey []byte masterSalt []byte packets []rtcpTestPacket } func rtcpTestCases() map[string]rtcpTestCase { return map[string]rtcpTestCase{ "AEAD_AES_128_GCM": { algo: ProtectionProfileAeadAes128Gcm, masterKey: []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}, masterSalt: []byte{0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab}, packets: []rtcpTestPacket{ { ssrc: 0xcafebabe, index: 0, pktType: rtcp.TypeSenderReport, encrypted: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0xc9, 0x8b, 0x8b, 0x5d, 0xf0, 0x39, 0x2a, 0x55, 0x85, 0x2b, 0x6c, 0x21, 0xac, 0x8e, 0x70, 0x25, 0xc5, 0x2c, 0x6f, 0xbe, 0xa2, 0xb3, 0xb4, 0x46, 0xea, 0x31, 0x12, 0x3b, 0xa8, 0x8c, 0xe6, 0x1e, 0x80, 0x00, 0x00, 0x01, }, decrypted: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, }, }, }, }, "AES_128_CM_HMAC_SHA1_80": { algo: ProtectionProfileAes128CmHmacSha1_80, masterKey: []byte{0xfd, 0xa6, 0x25, 0x95, 0xd7, 0xf6, 0x92, 0x6f, 0x7d, 0x9c, 0x02, 0x4c, 0xc9, 0x20, 0x9f, 0x34}, masterSalt: []byte{0xa9, 0x65, 0x19, 0x85, 0x54, 0x0b, 0x47, 0xbe, 0x2f, 0x27, 0xa8, 0xb8, 0x81, 0x23}, packets: []rtcpTestPacket{ { ssrc: 0x66ef91ff, index: 0, pktType: rtcp.TypeSenderReport, encrypted: []byte{ 0x80, 0xc8, 0x00, 0x06, 0x66, 0xef, 0x91, 0xff, 0xcd, 0x34, 0xc5, 0x78, 0xb2, 0x8b, 0xe1, 0x6b, 0xc5, 0x09, 0xd5, 0x77, 0xe4, 0xce, 0x5f, 0x20, 0x80, 0x21, 0xbd, 0x66, 0x74, 0x65, 0xe9, 0x5f, 0x49, 0xe5, 0xf5, 0xc0, 0x68, 0x4e, 0xe5, 0x6a, 0x78, 0x07, 0x75, 0x46, 0xed, 0x90, 0xf6, 0xdc, 0x9d, 0xef, 0x3b, 0xdf, 0xf2, 0x79, 0xa9, 0xd8, 0x80, 0x00, 0x00, 0x01, 0x60, 0xc0, 0xae, 0xb5, 0x6f, 0x40, 0x88, 0x0e, 0x28, 0xba, }, decrypted: []byte{ 0x80, 0xc8, 0x00, 0x06, 0x66, 0xef, 0x91, 0xff, 0xdf, 0x48, 0x80, 0xdd, 0x61, 0xa6, 0x2e, 0xd3, 0xd8, 0xbc, 0xde, 0xbe, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x16, 0x04, 0x81, 0xca, 0x00, 0x06, 0x66, 0xef, 0x91, 0xff, 0x01, 0x10, 0x52, 0x6e, 0x54, 0x35, 0x43, 0x6d, 0x4a, 0x68, 0x7a, 0x79, 0x65, 0x74, 0x41, 0x78, 0x77, 0x2b, 0x00, 0x00, }, }, { ssrc: 0x11111111, index: 0, pktType: rtcp.TypeSenderReport, encrypted: []byte{ 0x80, 0xc8, 0x00, 0x06, 0x11, 0x11, 0x11, 0x11, 0x17, 0x8c, 0x15, 0xf1, 0x4b, 0x11, 0xda, 0xf5, 0x74, 0x53, 0x86, 0x2b, 0xc9, 0x07, 0x29, 0x40, 0xbf, 0x22, 0xf6, 0x46, 0x11, 0xa4, 0xc1, 0x3a, 0xff, 0x5a, 0xbd, 0xd0, 0xf8, 0x8b, 0x38, 0xe4, 0x95, 0x38, 0x5d, 0xcf, 0x1b, 0xf5, 0x27, 0x77, 0xfb, 0xdb, 0x3f, 0x10, 0x68, 0x99, 0xd8, 0xad, 0x80, 0x00, 0x00, 0x01, 0x34, 0x3c, 0x2e, 0x83, 0x17, 0x13, 0x93, 0x69, 0xcf, 0xc0, }, decrypted: []byte{ 0x80, 0xc8, 0x00, 0x06, 0x11, 0x11, 0x11, 0x11, 0xdf, 0x48, 0x80, 0xdd, 0x61, 0xa6, 0x2e, 0xd3, 0xd8, 0xbc, 0xde, 0xbe, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x16, 0x04, 0x81, 0xca, 0x00, 0x06, 0x66, 0xef, 0x91, 0xff, 0x01, 0x10, 0x52, 0x6e, 0x54, 0x35, 0x43, 0x6d, 0x4a, 0x68, 0x7a, 0x79, 0x65, 0x74, 0x41, 0x78, 0x77, 0x2b, 0x00, 0x00, }, }, }, }, } } func TestRTCPLifecycle(t *testing.T) { options := map[string][]ContextOption{ "Default": {}, "WithReplayProtection": {SRTCPReplayProtection(10)}, } for name, option := range options { option := option t.Run(name, func(t *testing.T) { for caseName, testCase := range rtcpTestCases() { testCase := testCase t.Run(caseName, func(t *testing.T) { assertT := assert.New(t) encryptContext, err := CreateContext(testCase.masterKey, testCase.masterSalt, testCase.algo, option...) assertT.NoError(err) decryptContext, err := CreateContext(testCase.masterKey, testCase.masterSalt, testCase.algo, option...) assertT.NoError(err) for _, pkt := range testCase.packets { decryptResult, err := decryptContext.DecryptRTCP(nil, pkt.encrypted, nil) assertT.NoError(err) assertT.Equal(pkt.decrypted, decryptResult, "RTCP failed to decrypt") encryptContext.SetIndex(pkt.ssrc, pkt.index) encryptResult, err := encryptContext.EncryptRTCP(nil, pkt.decrypted, nil) assertT.NoError(err) assertT.Equal(pkt.encrypted, encryptResult, "RTCP failed to encrypt") } }) } }) } } func TestRTCPLifecycleInPlace(t *testing.T) { for caseName, testCase := range rtcpTestCases() { testCase := testCase t.Run(caseName, func(t *testing.T) { assertT := assert.New(t) authTagLen, err := testCase.algo.AuthTagRTCPLen() assertT.NoError(err) aeadAuthTagLen, err := testCase.algo.AEADAuthTagLen() assertT.NoError(err) encryptHeader := &rtcp.Header{} encryptContext, err := CreateContext(testCase.masterKey, testCase.masterSalt, testCase.algo) assertT.NoError(err) decryptHeader := &rtcp.Header{} decryptContext, err := CreateContext(testCase.masterKey, testCase.masterSalt, testCase.algo) assertT.NoError(err) for _, pkt := range testCase.packets { // Copy packet, asserts that everything was done in place decryptInput := append([]byte{}, pkt.encrypted...) actualDecrypted, err := decryptContext.DecryptRTCP(decryptInput, decryptInput, decryptHeader) assertT.NoError(err) assertT.Equal(pkt.pktType, decryptHeader.Type, "DecryptRTCP failed to populate input rtcp.Header") assertT.Equal(decryptInput[:len(decryptInput)-(authTagLen+aeadAuthTagLen+srtcpIndexSize)], actualDecrypted, "DecryptRTCP failed to decrypt in place") assertT.Equal(pkt.decrypted, actualDecrypted, "RTCP failed to decrypt") // Destination buffer should have capacity to store the resutl. // Otherwise, the buffer may be realloc-ed and the actual result will be written to the other address. encryptInput := make([]byte, 0, len(pkt.encrypted)) // Copy packet, asserts that everything was done in place encryptInput = append(encryptInput, pkt.decrypted...) encryptContext.SetIndex(pkt.ssrc, pkt.index) actualEncrypted, err := encryptContext.EncryptRTCP(encryptInput, encryptInput, encryptHeader) assertT.NoError(err) assertT.Equal(pkt.pktType, encryptHeader.Type, "EncryptRTCP failed to populate input rtcp.Header") assertT.Equal(actualEncrypted[:len(actualEncrypted)-(authTagLen+aeadAuthTagLen+srtcpIndexSize)], encryptInput, "EncryptRTCP failed to encrypt in place") assertT.Equal(pkt.encrypted, actualEncrypted, "RTCP failed to encrypt") } }) } } // Assert that passing a dst buffer that is too short doesn't result in a failure. func TestRTCPLifecyclePartialAllocation(t *testing.T) { for caseName, testCase := range rtcpTestCases() { testCase := testCase t.Run(caseName, func(t *testing.T) { assertT := assert.New(t) encryptHeader := &rtcp.Header{} encryptContext, err := CreateContext(testCase.masterKey, testCase.masterSalt, testCase.algo) assertT.NoError(err) decryptHeader := &rtcp.Header{} decryptContext, err := CreateContext(testCase.masterKey, testCase.masterSalt, testCase.algo) assertT.NoError(err) for _, pkt := range testCase.packets { // Copy packet, asserts that partial buffers can be used decryptDst := make([]byte, len(pkt.decrypted)*2) actualDecrypted, err := decryptContext.DecryptRTCP(decryptDst, pkt.encrypted, decryptHeader) assertT.NoError(err) assertT.Equal(pkt.pktType, decryptHeader.Type, "DecryptRTCP failed to populate input rtcp.Header") assertT.Equal(pkt.decrypted, actualDecrypted, "RTCP failed to decrypt") // Copy packet, asserts that partial buffers can be used encryptDst := make([]byte, len(pkt.encrypted)/2) encryptContext.SetIndex(pkt.ssrc, pkt.index) actualEncrypted, err := encryptContext.EncryptRTCP(encryptDst, pkt.decrypted, encryptHeader) assertT.NoError(err) assertT.Equal(pkt.pktType, encryptHeader.Type, "EncryptRTCP failed to populate input rtcp.Header") assertT.Equal(pkt.encrypted, actualEncrypted, "RTCP failed to encrypt") } }) } } func TestRTCPInvalidAuthTag(t *testing.T) { for caseName, testCase := range rtcpTestCases() { testCase := testCase t.Run(caseName, func(t *testing.T) { assertT := assert.New(t) authTagLen, err := testCase.algo.AuthTagRTCPLen() assertT.NoError(err) aeadAuthTagLen, err := testCase.algo.AEADAuthTagLen() assertT.NoError(err) decryptContext, err := CreateContext(testCase.masterKey, testCase.masterSalt, testCase.algo) assertT.NoError(err) for _, pkt := range testCase.packets { rtcpPacket := append([]byte{}, pkt.encrypted...) decryptResult, err := decryptContext.DecryptRTCP(nil, rtcpPacket, nil) assertT.NoError(err) assertT.Equal(pkt.decrypted, decryptResult, "RTCP failed to decrypt") // Zero out auth tag if authTagLen > 0 { copy(rtcpPacket[len(rtcpPacket)-authTagLen:], make([]byte, authTagLen)) } if aeadAuthTagLen > 0 { authTagPos := len(rtcpPacket) - authTagLen - srtcpIndexSize - aeadAuthTagLen copy(rtcpPacket[authTagPos:authTagPos+aeadAuthTagLen], make([]byte, aeadAuthTagLen)) } _, err = decryptContext.DecryptRTCP(nil, rtcpPacket, nil) assertT.Error(err, "Was able to decrypt RTCP packet with invalid Auth Tag") } }) } } func TestRTCPReplayDetectorSeparation(t *testing.T) { for caseName, testCase := range rtcpTestCases() { testCase := testCase t.Run(caseName, func(t *testing.T) { assertT := assert.New(t) decryptContext, err := CreateContext( testCase.masterKey, testCase.masterSalt, testCase.algo, SRTCPReplayProtection(10), ) assertT.NoError(err) for _, pkt := range testCase.packets { rtcpPacket := append([]byte{}, pkt.encrypted...) decryptResult, errDec := decryptContext.DecryptRTCP(nil, rtcpPacket, nil) assertT.NoError(errDec) assertT.Equal(pkt.decrypted, decryptResult, "RTCP failed to decrypt") } for i, pkt := range testCase.packets { rtcpPacket := append([]byte{}, pkt.encrypted...) _, err = decryptContext.DecryptRTCP(nil, rtcpPacket, nil) assertT.ErrorIs(err, errDuplicated, "RTCP packet %d was not detected as replayed", i) } }) } } func getRTCPIndex(encrypted []byte, authTagLen int) uint32 { tailOffset := len(encrypted) - (authTagLen + srtcpIndexSize) srtcpIndexBuffer := encrypted[tailOffset : tailOffset+srtcpIndexSize] return binary.BigEndian.Uint32(srtcpIndexBuffer) &^ (1 << 31) } func TestEncryptRTCPSeparation(t *testing.T) { for caseName, testCase := range rtcpTestCases() { testCase := testCase t.Run(caseName, func(t *testing.T) { assertT := assert.New(t) encryptContext, err := CreateContext(testCase.masterKey, testCase.masterSalt, testCase.algo) assertT.NoError(err) authTagLen, err := testCase.algo.AuthTagRTCPLen() assertT.NoError(err) decryptContext, err := CreateContext( testCase.masterKey, testCase.masterSalt, testCase.algo, SRTCPReplayProtection(10), ) assertT.NoError(err) encryptHeader := &rtcp.Header{} inputs := [][]byte{} expectedIndexes := []uint32{} pktCnt := map[uint32]uint32{} for _, pkt := range testCase.packets { inputs = append(inputs, pkt.decrypted) pktCnt[pkt.ssrc]++ expectedIndexes = append(expectedIndexes, pktCnt[pkt.ssrc]) } for _, pkt := range testCase.packets { inputs = append(inputs, pkt.decrypted) pktCnt[pkt.ssrc]++ expectedIndexes = append(expectedIndexes, pktCnt[pkt.ssrc]) } encryptedRCTPs := make([][]byte, len(inputs)) for i, input := range inputs { encrypted, err := encryptContext.EncryptRTCP(nil, input, encryptHeader) assertT.NoError(err) encryptedRCTPs[i] = encrypted } for i, expectedIndex := range expectedIndexes { assertT.Equal(expectedIndex, getRTCPIndex(encryptedRCTPs[i], authTagLen), "RTCP index does not match") } for i, output := range encryptedRCTPs { decrypted, err := decryptContext.DecryptRTCP(nil, output, encryptHeader) assertT.NoError(err) assertT.Equal(decrypted, inputs[i]) } }) } } func TestRTCPDecryptShortenedPacket(t *testing.T) { for caseName, testCase := range rtcpTestCases() { testCase := testCase t.Run(caseName, func(t *testing.T) { pkt := testCase.packets[0] for i := 1; i < len(pkt.encrypted)-1; i++ { packet := pkt.encrypted[:i] decryptContext, err := CreateContext(testCase.masterKey, testCase.masterSalt, testCase.algo) assert.NoError(t, err) assert.NotPanics(t, func() { _, _ = decryptContext.DecryptRTCP(nil, packet, nil) }, "Panic on length %d/%d", i, len(pkt.encrypted)) } }) } } func TestRTCPMaxPackets(t *testing.T) { const ssrc = 0x11111111 testCases := map[string]rtcpTestCase{ "AEAD_AES_128_GCM": { algo: ProtectionProfileAeadAes128Gcm, masterKey: []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f}, masterSalt: []byte{0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab}, packets: []rtcpTestPacket{ { pktType: rtcp.TypeSenderReport, encrypted: []byte{ 0x80, 0xc8, 0x00, 0x06, 0x11, 0x11, 0x11, 0x11, 0x02, 0xb6, 0xc1, 0x47, 0x92, 0xbe, 0xf0, 0xae, 0xd9, 0x40, 0xa5, 0x1c, 0xbe, 0xec, 0xaf, 0xfc, 0x7d, 0x86, 0x3b, 0xbb, 0x93, 0x0c, 0xb0, 0xd4, 0xea, 0x4a, 0x3c, 0x5b, 0xd1, 0xd5, 0x47, 0xb1, 0x1a, 0x61, 0xae, 0xa6, 0x1a, 0x0c, 0xb9, 0x14, 0xa5, 0x16, 0x08, 0xe4, 0xfb, 0x0d, 0x15, 0xba, 0x7f, 0x70, 0x2b, 0xb8, 0x99, 0x97, 0x91, 0xfd, 0x53, 0x03, 0xcd, 0x57, 0xbb, 0x8f, 0x93, 0xbe, 0xff, 0xff, 0xff, 0xff, }, decrypted: []byte{ 0x80, 0xc8, 0x00, 0x06, 0x11, 0x11, 0x11, 0x11, 0x04, 0x99, 0x47, 0x53, 0xc4, 0x1e, 0xb9, 0xde, 0x52, 0xa3, 0x1d, 0x77, 0x2f, 0xff, 0xcc, 0x75, 0xbb, 0x6a, 0x29, 0xb8, 0x01, 0xb7, 0x2e, 0x4b, 0x4e, 0xcb, 0xa4, 0x81, 0x2d, 0x46, 0x04, 0x5e, 0x86, 0x90, 0x17, 0x4f, 0x4d, 0x78, 0x2f, 0x58, 0xb8, 0x67, 0x91, 0x89, 0xe3, 0x61, 0x01, 0x7d, }, }, { pktType: rtcp.TypeSenderReport, encrypted: []byte{ 0x80, 0xc8, 0x00, 0x06, 0x11, 0x11, 0x11, 0x11, 0x77, 0x47, 0x0c, 0x21, 0xc2, 0xcd, 0x33, 0xa7, 0x5a, 0x81, 0xb5, 0xb5, 0x8f, 0xe2, 0x34, 0x28, 0x11, 0xa8, 0xa3, 0x34, 0xf8, 0x9d, 0xfc, 0xd8, 0xcb, 0x87, 0xe2, 0x51, 0x8e, 0xae, 0xdb, 0xfd, 0x9d, 0xf1, 0xfa, 0x18, 0xe2, 0xdc, 0x0a, 0xd4, 0xe3, 0x06, 0x18, 0xff, 0xf7, 0x27, 0x92, 0x1f, 0x28, 0xcd, 0x3c, 0xf8, 0xa4, 0x0a, 0x2b, 0xbb, 0x5b, 0x1f, 0x4d, 0x1f, 0xef, 0x0e, 0xc4, 0x91, 0x80, 0x00, 0x00, 0x01, }, decrypted: []byte{ 0x80, 0xc8, 0x00, 0x06, 0x11, 0x11, 0x11, 0x11, 0xda, 0xb5, 0xe0, 0x56, 0x9a, 0x4a, 0x74, 0xed, 0x8a, 0x54, 0x0c, 0xcf, 0xd5, 0x09, 0xb1, 0x40, 0x01, 0x42, 0xc3, 0x9a, 0x76, 0x00, 0xa9, 0xd4, 0xf7, 0x29, 0x9e, 0x51, 0xfb, 0x3c, 0xc1, 0x74, 0x72, 0xf9, 0x52, 0xb1, 0x92, 0x31, 0xca, 0x22, 0xab, 0x3e, 0xc5, 0x5f, 0x83, 0x34, 0xf0, 0x28, }, }, }, }, "AES_128_CM_HMAC_SHA1_80": { algo: ProtectionProfileAes128CmHmacSha1_80, masterKey: []byte{0xfd, 0xa6, 0x25, 0x95, 0xd7, 0xf6, 0x92, 0x6f, 0x7d, 0x9c, 0x02, 0x4c, 0xc9, 0x20, 0x9f, 0x34}, masterSalt: []byte{0xa9, 0x65, 0x19, 0x85, 0x54, 0x0b, 0x47, 0xbe, 0x2f, 0x27, 0xa8, 0xb8, 0x81, 0x23}, packets: []rtcpTestPacket{ { encrypted: []byte{ 0x80, 0xc8, 0x00, 0x06, 0x11, 0x11, 0x11, 0x11, 0x17, 0x8c, 0x15, 0xf1, 0x4b, 0x11, 0xda, 0xf5, 0x74, 0x53, 0x86, 0x2b, 0xc9, 0x07, 0x29, 0x40, 0xbf, 0x22, 0xf6, 0x46, 0x11, 0xa4, 0xc1, 0x3a, 0xff, 0x5a, 0xbd, 0xd0, 0xf8, 0x8b, 0x38, 0xe4, 0x95, 0x38, 0x5d, 0xcf, 0x1b, 0xf5, 0x27, 0x77, 0xfb, 0xdb, 0x3f, 0x10, 0x68, 0x99, 0xd8, 0xad, 0xff, 0xff, 0xff, 0xff, 0x5a, 0x99, 0xce, 0xed, 0x9f, 0x2e, 0x4d, 0x9d, 0xfa, 0x97, }, decrypted: []byte{ 0x80, 0xc8, 0x00, 0x06, 0x11, 0x11, 0x11, 0x11, 0x04, 0x99, 0x47, 0x53, 0xc4, 0x1e, 0xb9, 0xde, 0x52, 0xa3, 0x1d, 0x77, 0x2f, 0xff, 0xcc, 0x75, 0xbb, 0x6a, 0x29, 0xb8, 0x01, 0xb7, 0x2e, 0x4b, 0x4e, 0xcb, 0xa4, 0x81, 0x2d, 0x46, 0x04, 0x5e, 0x86, 0x90, 0x17, 0x4f, 0x4d, 0x78, 0x2f, 0x58, 0xb8, 0x67, 0x91, 0x89, 0xe3, 0x61, 0x01, 0x7d, }, }, { encrypted: []byte{ 0x80, 0xc8, 0x00, 0x06, 0x11, 0x11, 0x11, 0x11, 0x12, 0x71, 0x75, 0x7a, 0xb0, 0xfd, 0x80, 0xcb, 0x26, 0xbb, 0x54, 0x5a, 0x1c, 0x0e, 0x98, 0x09, 0xbe, 0x60, 0x23, 0xd8, 0xe6, 0x6e, 0x68, 0xe8, 0x6e, 0x9c, 0xb2, 0x7e, 0x02, 0xa7, 0xab, 0xfe, 0xb3, 0xf4, 0x4c, 0x13, 0xc3, 0xac, 0x97, 0x2c, 0x35, 0x91, 0xbb, 0x37, 0x9c, 0x86, 0x28, 0x85, 0x80, 0x00, 0x00, 0x01, 0x89, 0x76, 0x07, 0xca, 0xd9, 0xc4, 0xcb, 0xca, 0x66, 0xab, }, decrypted: []byte{ 0x80, 0xc8, 0x00, 0x06, 0x11, 0x11, 0x11, 0x11, 0xda, 0xb5, 0xe0, 0x56, 0x9a, 0x4a, 0x74, 0xed, 0x8a, 0x54, 0x0c, 0xcf, 0xd5, 0x09, 0xb1, 0x40, 0x01, 0x42, 0xc3, 0x9a, 0x76, 0x00, 0xa9, 0xd4, 0xf7, 0x29, 0x9e, 0x51, 0xfb, 0x3c, 0xc1, 0x74, 0x72, 0xf9, 0x52, 0xb1, 0x92, 0x31, 0xca, 0x22, 0xab, 0x3e, 0xc5, 0x5f, 0x83, 0x34, 0xf0, 0x28, }, }, }, }, } for caseName, testCase := range testCases { testCase := testCase t.Run(caseName, func(t *testing.T) { assertT := assert.New(t) encryptContext, err := CreateContext(testCase.masterKey, testCase.masterSalt, testCase.algo) assertT.NoError(err) decryptContext, err := CreateContext( testCase.masterKey, testCase.masterSalt, testCase.algo, SRTCPReplayProtection(10), ) assertT.NoError(err) // Upper boundary of index encryptContext.SetIndex(ssrc, 0x7ffffffe) decryptResult, err := decryptContext.DecryptRTCP(nil, testCase.packets[0].encrypted, nil) assertT.NoError(err) assertT.Equal(testCase.packets[0].decrypted, decryptResult, "RTCP failed to decrypt") encryptResult, err := encryptContext.EncryptRTCP(nil, testCase.packets[0].decrypted, nil) assertT.NoError(err) assertT.Equal(testCase.packets[0].encrypted, encryptResult, "RTCP failed to encrypt") // Next packet will exceeds the maximum packet count _, err = decryptContext.DecryptRTCP(nil, testCase.packets[1].encrypted, nil) assertT.ErrorIs(err, errDuplicated) _, err = encryptContext.EncryptRTCP(nil, testCase.packets[1].decrypted, nil) assertT.ErrorIs(err, errExceededMaxPackets) }) } } func TestRTCPReplayDetectorFactory(t *testing.T) { assertT := assert.New(t) testCase := rtcpTestCases()["AEAD_AES_128_GCM"] data := testCase.packets[0] var cntFactory int decryptContext, err := CreateContext( testCase.masterKey, testCase.masterSalt, testCase.algo, SRTCPReplayDetectorFactory(func() replaydetector.ReplayDetector { cntFactory++ return &nopReplayDetector{} }), ) assertT.NoError(err) _, err = decryptContext.DecryptRTCP(nil, data.encrypted, nil) assertT.NoError(err) assertT.Equal(1, cntFactory) } func TestDecryptInvalidSRTCP(t *testing.T) { assertT := assert.New(t) key := []byte{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01} salt := []byte{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01} decryptContext, err := CreateContext(key, salt, ProtectionProfileAes128CmHmacSha1_80) assertT.NoError(err) packet := []byte{0x8f, 0x48, 0xff, 0xff, 0xec, 0x77, 0xb0, 0x43, 0xf9, 0x04, 0x51, 0xff, 0xfb, 0xdf} _, err = decryptContext.DecryptRTCP(nil, packet, nil) assertT.Error(err) } func TestEncryptInvalidRTCP(t *testing.T) { assertT := assert.New(t) key := []byte{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01} salt := []byte{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01} decryptContext, err := CreateContext(key, salt, ProtectionProfileAes128CmHmacSha1_80) assertT.NoError(err) packet := []byte{0xbb, 0xbb, 0x0a, 0x2f} _, err = decryptContext.EncryptRTCP(nil, packet, nil) assertT.Error(err) } func TestRTCPInvalidMKI(t *testing.T) { mki1 := []byte{0x01, 0x02, 0x03, 0x04} mki2 := []byte{0x02, 0x03, 0x04, 0x05} for caseName, testCase := range rtcpTestCases() { testCase := testCase t.Run(caseName, func(t *testing.T) { encryptContext, err := CreateContext( testCase.masterKey, testCase.masterSalt, testCase.algo, MasterKeyIndicator(mki1), ) assert.NoError(t, err) decryptContext, err := CreateContext( testCase.masterKey, testCase.masterSalt, testCase.algo, MasterKeyIndicator(mki2), ) assert.NoError(t, err) for _, pkt := range testCase.packets { rtcpPacket := append([]byte{}, pkt.decrypted...) encrypted, err := encryptContext.encryptRTCP(nil, rtcpPacket) assert.NoError(t, err) _, err = decryptContext.DecryptRTCP(nil, encrypted, nil) assert.ErrorIs(t, err, ErrMKINotFound, "Managed to decrypt with incorrect MKI for packet with SSRC: %d", pkt.ssrc) } }) } } func TestRTCPHandleMultipleMKI(t *testing.T) { //nolint:cyclop mki1 := []byte{0x01, 0x02, 0x03, 0x04} mki2 := []byte{0x02, 0x03, 0x04, 0x05} for caseName, testCase := range rtcpTestCases() { testCase := testCase t.Run(caseName, func(t *testing.T) { masterKey2 := make([]byte, len(testCase.masterKey)) copy(masterKey2, testCase.masterKey) masterKey2[0] = ^masterKey2[0] encryptContext1, err := CreateContext( testCase.masterKey, testCase.masterSalt, testCase.algo, MasterKeyIndicator(mki1), ) assert.NoError(t, err) encryptContext2, err := CreateContext(masterKey2, testCase.masterSalt, testCase.algo, MasterKeyIndicator(mki2)) assert.NoError(t, err) decryptContext, err := CreateContext( testCase.masterKey, testCase.masterSalt, testCase.algo, MasterKeyIndicator(mki1), ) assert.NoError(t, err) err = decryptContext.AddCipherForMKI(mki2, masterKey2, testCase.masterSalt) assert.NoError(t, err) for _, pkt := range testCase.packets { rtcpPacket := append([]byte{}, pkt.decrypted...) encrypted1, err := encryptContext1.encryptRTCP(nil, rtcpPacket) assert.NoError(t, err) encrypted2, err := encryptContext2.encryptRTCP(nil, rtcpPacket) assert.NoError(t, err) decrypted1, err := decryptContext.DecryptRTCP(nil, encrypted1, nil) assert.NoError(t, err) decrypted2, err := decryptContext.DecryptRTCP(nil, encrypted2, nil) assert.NoError(t, err) assert.Equal(t, rtcpPacket, decrypted1) assert.Equal(t, rtcpPacket, decrypted2) } }) } } func TestRTCPSwitchMKI(t *testing.T) { //nolint:cyclop mki1 := []byte{0x01, 0x02, 0x03, 0x04} mki2 := []byte{0x02, 0x03, 0x04, 0x05} for caseName, testCase := range rtcpTestCases() { testCase := testCase t.Run(caseName, func(t *testing.T) { masterKey2 := make([]byte, len(testCase.masterKey)) copy(masterKey2, testCase.masterKey) masterKey2[0] = ^masterKey2[0] encryptContext, err := CreateContext( testCase.masterKey, testCase.masterSalt, testCase.algo, MasterKeyIndicator(mki1), ) assert.NoError(t, err) err = encryptContext.AddCipherForMKI(mki2, masterKey2, testCase.masterSalt) assert.NoError(t, err) decryptContext1, err := CreateContext( testCase.masterKey, testCase.masterSalt, testCase.algo, MasterKeyIndicator(mki1), ) assert.NoError(t, err) decryptContext2, err := CreateContext(masterKey2, testCase.masterSalt, testCase.algo, MasterKeyIndicator(mki2)) assert.NoError(t, err) for _, pkt := range testCase.packets { rtcpPacket := append([]byte{}, pkt.decrypted...) encrypted1, err := encryptContext.encryptRTCP(nil, rtcpPacket) assert.NoError(t, err) err = encryptContext.SetSendMKI(mki2) assert.NoError(t, err) encrypted2, err := encryptContext.encryptRTCP(nil, rtcpPacket) assert.NoError(t, err) assert.NotEqual(t, encrypted1, encrypted2) decrypted1, err := decryptContext1.DecryptRTCP(nil, encrypted1, nil) assert.NoError(t, err) decrypted2, err := decryptContext2.DecryptRTCP(nil, encrypted2, nil) assert.NoError(t, err) assert.Equal(t, rtcpPacket, decrypted1) assert.Equal(t, rtcpPacket, decrypted2) err = encryptContext.SetSendMKI(mki1) assert.NoError(t, err) } }) } } srtp-3.0.9/srtp.go000066400000000000000000000134371511235350500140420ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT // Package srtp implements Secure Real-time Transport Protocol package srtp import ( "encoding/binary" "fmt" "github.com/pion/rtp" ) /* Simplified structure of SRTP Packets: - RTP Header (with optional RTP Header Extension) - Payload (with optional padding) - AEAD Auth Tag - used by AEAD profiles only - MKI (optional) - Auth Tag - used by non-AEAD profiles only. When RCC is used with AEAD profiles, the ROC is sent here. */ func (c *Context) decryptRTP(dst, ciphertext []byte, header *rtp.Header, headerLen int) ([]byte, error) { authTagLen, err := c.cipher.AuthTagRTPLen() if err != nil { return nil, err } aeadAuthTagLen, err := c.cipher.AEADAuthTagLen() if err != nil { return nil, err } mkiLen := len(c.sendMKI) var hasRocInPacket bool hasRocInPacket, authTagLen = c.hasROCInPacket(header, authTagLen) // Verify that encrypted packet is long enough if len(ciphertext) < (headerLen + aeadAuthTagLen + mkiLen + authTagLen) { return nil, fmt.Errorf("%w: %d", errTooShortRTP, len(ciphertext)) } ssrcState := c.getSRTPSSRCState(header.SSRC) var roc uint32 var diff int64 var index uint64 if !hasRocInPacket { // The ROC is not sent in the packet. We need to guess it. roc, diff, _ = ssrcState.nextRolloverCount(header.SequenceNumber) index = (uint64(roc) << 16) | uint64(header.SequenceNumber) } else { // Extract ROC from the packet. The ROC is sent in the first 4 bytes of the auth tag. roc = binary.BigEndian.Uint32(ciphertext[len(ciphertext)-authTagLen:]) index = (uint64(roc) << 16) | uint64(header.SequenceNumber) diff = int64(ssrcState.index) - int64(index) //nolint:gosec } markAsValid, ok := ssrcState.replayDetector.Check(index) if !ok { return nil, &duplicatedError{ Proto: "srtp", SSRC: header.SSRC, Index: uint32(header.SequenceNumber), } } err = c.checkCryptex(header) if err != nil { return nil, err } cipher := c.cipher if len(c.mkis) > 0 { // Find cipher for MKI actualMKI := ciphertext[len(ciphertext)-mkiLen-authTagLen : len(ciphertext)-authTagLen] cipher, ok = c.mkis[string(actualMKI)] if !ok { return nil, ErrMKINotFound } } dst = growBufferSize(dst, len(ciphertext)-authTagLen-mkiLen) dst, err = cipher.decryptRTP(dst, ciphertext, header, headerLen, roc, hasRocInPacket) if err != nil { return nil, err } markAsValid() ssrcState.updateRolloverCount(header.SequenceNumber, diff, hasRocInPacket, roc) return dst, nil } // DecryptRTP decrypts a RTP packet with an encrypted payload. func (c *Context) DecryptRTP(dst, encrypted []byte, header *rtp.Header) ([]byte, error) { if header == nil { header = &rtp.Header{} } headerLen, err := header.Unmarshal(encrypted) if err != nil { return nil, err } return c.decryptRTP(dst, encrypted, header, headerLen) } // EncryptRTP marshals and encrypts an RTP packet, writing to the dst buffer provided. // If the dst buffer does not have the capacity to hold `len(plaintext) + 10` bytes, // a new one will be allocated and returned. // If a rtp.Header is provided, it will be Unmarshaled using the plaintext. func (c *Context) EncryptRTP(dst []byte, plaintext []byte, header *rtp.Header) ([]byte, error) { if header == nil { header = &rtp.Header{} } headerLen, err := header.Unmarshal(plaintext) if err != nil { return nil, err } return c.encryptRTP(dst, header, headerLen, plaintext) } // encryptRTP marshals and encrypts an RTP packet, writing to the dst buffer provided. // If the dst buffer does not have the capacity, a new one will be allocated and returned. // Similar to above but faster because it can avoid unmarshaling the header and marshaling the payload. func (c *Context) encryptRTP(dst []byte, header *rtp.Header, headerLen int, plaintext []byte, ) (ciphertext []byte, err error) { // RFC 9335, section 5.1: This mechanism [Cryptex] MUST NOT be used with header extensions other than // the variety described in [RFC8285]. if c.cryptexMode != CryptexModeDisabled && header.Extension && header.ExtensionProfile != rtp.ExtensionProfileOneByte && header.ExtensionProfile != rtp.ExtensionProfileTwoByte { return nil, errUnsupportedHeaderExtension } s := c.getSRTPSSRCState(header.SSRC) roc, diff, ovf := s.nextRolloverCount(header.SequenceNumber) if ovf { // ... when 2^48 SRTP packets or 2^31 SRTCP packets have been secured with the same key // (whichever occurs before), the key management MUST be called to provide new master key(s) // (previously stored and used keys MUST NOT be used again), or the session MUST be terminated. // https://www.rfc-editor.org/rfc/rfc3711#section-9.2 return nil, errExceededMaxPackets } s.updateRolloverCount(header.SequenceNumber, diff, false, 0) rocInPacket := c.rccMode != RCCModeNone && header.SequenceNumber%c.rocTransmitRate == 0 return c.cipher.encryptRTP(dst, header, headerLen, plaintext, roc, rocInPacket) } func (c *Context) hasROCInPacket(header *rtp.Header, authTagLen int) (bool, int) { hasRocInPacket := false switch c.rccMode { case RCCMode2: // This mode is supported for AES-CM and NULL profiles only. The ROC is sent in the first 4 bytes of the auth tag. hasRocInPacket = header.SequenceNumber%c.rocTransmitRate == 0 case RCCMode3: // This mode is supported for AES-GCM only. The ROC is sent as 4-byte auth tag. hasRocInPacket = header.SequenceNumber%c.rocTransmitRate == 0 if hasRocInPacket { authTagLen = 4 } default: } return hasRocInPacket, authTagLen } func (c *Context) checkCryptex(header *rtp.Header) error { switch c.cryptexMode { case CryptexModeDisabled: if isCryptexPacket(header) { return errCryptexDisabled } case CryptexModeRequired: if (header.Extension || len(header.CSRC) > 0) && !isCryptexPacket(header) { return errUnencryptedHeaderExtAndCSRCs } default: } return nil } srtp-3.0.9/srtp_cipher.go000066400000000000000000000035161511235350500153710ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import "github.com/pion/rtp" // cipher represents a implementation of one // of the SRTP Specific ciphers. type srtpCipher interface { // AuthTagRTPLen/AuthTagRTCPLen return auth key length of the cipher. // See the note below. AuthTagRTPLen() (int, error) AuthTagRTCPLen() (int, error) // AEADAuthTagLen returns AEAD auth key length of the cipher. // See the note below. AEADAuthTagLen() (int, error) getRTCPIndex([]byte) uint32 encryptRTP([]byte, *rtp.Header, int, []byte, uint32, bool) ([]byte, error) encryptRTCP([]byte, []byte, uint32, uint32) ([]byte, error) decryptRTP([]byte, []byte, *rtp.Header, int, uint32, bool) ([]byte, error) decryptRTCP([]byte, []byte, uint32, uint32) ([]byte, error) } /* NOTE: Auth tag and AEAD auth tag are placed at the different position in SRTCP In non-AEAD cipher, the authentication tag is placed *after* the ESRTCP word (Encrypted-flag and SRTCP index). > AES_128_CM_HMAC_SHA1_80 > | RTCP Header | Encrypted payload |E| SRTCP Index | Auth tag | > ^ |----------| > | ^ > | authTagLen=10 > aeadAuthTagLen=0 In AEAD cipher, the AEAD authentication tag is embedded in the ciphertext. It is *before* the ESRTCP word (Encrypted-flag and SRTCP index). > AEAD_AES_128_GCM > | RTCP Header | Encrypted payload | AEAD auth tag |E| SRTCP Index | > |---------------| ^ > ^ authTagLen=0 > aeadAuthTagLen=16 See https://tools.ietf.org/html/rfc7714 for the full specifications. */ srtp-3.0.9/srtp_cipher_aead_aes_gcm.go000066400000000000000000000255051511235350500200230ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "crypto/aes" "crypto/cipher" "encoding/binary" "fmt" "github.com/pion/rtp" ) type srtpCipherAeadAesGcm struct { protectionProfileWithArgs srtpCipher, srtcpCipher cipher.AEAD srtpSessionSalt, srtcpSessionSalt []byte mki []byte srtpEncrypted, srtcpEncrypted bool useCryptex bool } func newSrtpCipherAeadAesGcm( profile protectionProfileWithArgs, masterKey, masterSalt, mki []byte, encryptSRTP, encryptSRTCP, useCryptex bool, ) (*srtpCipherAeadAesGcm, error) { srtpCipher := &srtpCipherAeadAesGcm{ protectionProfileWithArgs: profile, srtpEncrypted: encryptSRTP, srtcpEncrypted: encryptSRTCP, useCryptex: useCryptex, } srtpSessionKey, err := aesCmKeyDerivation(labelSRTPEncryption, masterKey, masterSalt, 0, len(masterKey)) if err != nil { return nil, err } srtpBlock, err := aes.NewCipher(srtpSessionKey) if err != nil { return nil, err } srtpCipher.srtpCipher, err = cipher.NewGCM(srtpBlock) if err != nil { return nil, err } srtcpSessionKey, err := aesCmKeyDerivation(labelSRTCPEncryption, masterKey, masterSalt, 0, len(masterKey)) if err != nil { return nil, err } srtcpBlock, err := aes.NewCipher(srtcpSessionKey) if err != nil { return nil, err } srtpCipher.srtcpCipher, err = cipher.NewGCM(srtcpBlock) if err != nil { return nil, err } if srtpCipher.srtpSessionSalt, err = aesCmKeyDerivation( labelSRTPSalt, masterKey, masterSalt, 0, len(masterSalt), ); err != nil { return nil, err } else if srtpCipher.srtcpSessionSalt, err = aesCmKeyDerivation( labelSRTCPSalt, masterKey, masterSalt, 0, len(masterSalt), ); err != nil { return nil, err } mkiLen := len(mki) if mkiLen > 0 { srtpCipher.mki = make([]byte, mkiLen) copy(srtpCipher.mki, mki) } return srtpCipher, nil } func (s *srtpCipherAeadAesGcm) encryptRTP( dst []byte, header *rtp.Header, headerLen int, plaintext []byte, roc uint32, rocInAuthTag bool, ) (ciphertext []byte, err error) { // Grow the given buffer to fit the output. authTagLen, err := s.AEADAuthTagLen() if err != nil { return nil, err } payloadLen := len(plaintext) - headerLen authPartLen := headerLen + payloadLen + authTagLen dstLen := authPartLen + len(s.mki) if rocInAuthTag { dstLen += 4 } insertEmptyExtHdr := needsEmptyExtensionHeader(s.useCryptex, header) if insertEmptyExtHdr { dstLen += extensionHeaderSize } dst = growBufferSize(dst, dstLen) sameBuffer := isSameBuffer(dst, plaintext) if insertEmptyExtHdr { plaintext = insertEmptyExtensionHeader(dst, plaintext, sameBuffer, header) sameBuffer = true headerLen += extensionHeaderSize } err = s.doEncryptRTP(dst, header, headerLen, plaintext, roc, rocInAuthTag, sameBuffer, payloadLen, authPartLen) if err != nil { return nil, err } return dst, nil } func (s *srtpCipherAeadAesGcm) doEncryptRTP(dst []byte, header *rtp.Header, headerLen int, plaintext []byte, roc uint32, rocInAuthTag bool, sameBuffer bool, payloadLen int, authPartLen int, ) error { iv := s.rtpInitializationVector(header, roc) encrypt := func(dst, plaintext []byte, headerLen int) error { s.srtpCipher.Seal(dst[headerLen:headerLen], iv[:], plaintext[headerLen:], plaintext[:headerLen]) return nil } switch { case s.useCryptex && header.Extension: err := encryptCryptexRTP(dst, plaintext, sameBuffer, header, encrypt) if err != nil { return err } case s.srtpEncrypted: // Copy the header unencrypted. if !sameBuffer { copy(dst, plaintext[:headerLen]) } s.srtpCipher.Seal(dst[headerLen:headerLen], iv[:], plaintext[headerLen:], dst[:headerLen]) default: clearLen := headerLen + payloadLen if !sameBuffer { copy(dst, plaintext) } s.srtpCipher.Seal(dst[clearLen:clearLen], iv[:], nil, dst[:clearLen]) } // Add MKI after the encrypted payload if len(s.mki) > 0 { copy(dst[authPartLen:], s.mki) } if rocInAuthTag { binary.BigEndian.PutUint32(dst[len(dst)-4:], roc) } return nil } func (s *srtpCipherAeadAesGcm) decryptRTP( dst, ciphertext []byte, header *rtp.Header, headerLen int, roc uint32, rocInAuthTag bool, ) ([]byte, error) { // Grow the given buffer to fit the output. authTagLen, err := s.AEADAuthTagLen() if err != nil { return nil, err } rocLen := 0 if rocInAuthTag { rocLen = 4 } nDst := len(ciphertext) - authTagLen - len(s.mki) - rocLen if nDst < headerLen { // Size of ciphertext is shorter than AEAD auth tag len. return nil, ErrFailedToVerifyAuthTag } dst = growBufferSize(dst, nDst) sameBuffer := isSameBuffer(dst, ciphertext) nEnd := len(ciphertext) - len(s.mki) - rocLen err = s.doDecryptRTP(dst, ciphertext, header, headerLen, roc, sameBuffer, nEnd, authTagLen) if err != nil { return nil, err } return dst, nil } func (s *srtpCipherAeadAesGcm) doDecryptRTP(dst, ciphertext []byte, header *rtp.Header, headerLen int, roc uint32, sameBuffer bool, nEnd int, authTagLen int, ) error { iv := s.rtpInitializationVector(header, roc) decrypt := func(dst, ciphertext []byte, headerLen int) error { _, err := s.srtpCipher.Open(dst[headerLen:headerLen], iv[:], ciphertext[headerLen:nEnd], ciphertext[:headerLen]) return err } switch { case isCryptexPacket(header): err := decryptCryptexRTP(dst, ciphertext, sameBuffer, header, headerLen, decrypt) if err != nil { return fmt.Errorf("%w: %w", ErrFailedToVerifyAuthTag, err) } case s.srtpEncrypted: if err := decrypt(dst, ciphertext[:nEnd], headerLen); err != nil { return fmt.Errorf("%w: %w", ErrFailedToVerifyAuthTag, err) } // Copy the header unencrypted. if !sameBuffer { copy(dst[:headerLen], ciphertext[:headerLen]) } default: nDataEnd := nEnd - authTagLen if _, err := s.srtpCipher.Open( nil, iv[:], ciphertext[nDataEnd:nEnd], ciphertext[:nDataEnd], ); err != nil { return fmt.Errorf("%w: %w", ErrFailedToVerifyAuthTag, err) } // Copy the header and payload unencrypted. if !sameBuffer { copy(dst, ciphertext[:nDataEnd]) } } return nil } func (s *srtpCipherAeadAesGcm) encryptRTCP(dst, decrypted []byte, srtcpIndex uint32, ssrc uint32) ([]byte, error) { authTagLen, err := s.AEADAuthTagLen() if err != nil { return nil, err } aadPos := len(decrypted) + authTagLen // Grow the given buffer to fit the output. dst = growBufferSize(dst, aadPos+srtcpIndexSize+len(s.mki)) sameBuffer := isSameBuffer(dst, decrypted) iv := s.rtcpInitializationVector(srtcpIndex, ssrc) if s.srtcpEncrypted { aad := s.rtcpAdditionalAuthenticatedData(decrypted, srtcpIndex) if !sameBuffer { // Copy the header unencrypted. copy(dst[:srtcpHeaderSize], decrypted[:srtcpHeaderSize]) } // Copy index to the proper place. copy(dst[aadPos:aadPos+srtcpIndexSize], aad[8:12]) s.srtcpCipher.Seal(dst[srtcpHeaderSize:srtcpHeaderSize], iv[:], decrypted[srtcpHeaderSize:], aad[:]) } else { // Copy the packet unencrypted. if !sameBuffer { copy(dst, decrypted) } // Append the SRTCP index to the end of the packet - this will form the AAD. binary.BigEndian.PutUint32(dst[len(decrypted):], srtcpIndex) // Generate the authentication tag. tag := make([]byte, authTagLen) s.srtcpCipher.Seal(tag[0:0], iv[:], nil, dst[:len(decrypted)+srtcpIndexSize]) // Copy index to the proper place. copy(dst[aadPos:], dst[len(decrypted):len(decrypted)+srtcpIndexSize]) // Copy the auth tag after RTCP payload. copy(dst[len(decrypted):], tag) } copy(dst[aadPos+srtcpIndexSize:], s.mki) return dst, nil } func (s *srtpCipherAeadAesGcm) decryptRTCP(dst, encrypted []byte, srtcpIndex, ssrc uint32) ([]byte, error) { aadPos := len(encrypted) - srtcpIndexSize - len(s.mki) // Grow the given buffer to fit the output. authTagLen, err := s.AEADAuthTagLen() if err != nil { return nil, err } nDst := aadPos - authTagLen if nDst < 0 { // Size of ciphertext is shorter than AEAD auth tag len. return nil, ErrFailedToVerifyAuthTag } dst = growBufferSize(dst, nDst) sameBuffer := isSameBuffer(dst, encrypted) isEncrypted := encrypted[aadPos]&srtcpEncryptionFlag != 0 iv := s.rtcpInitializationVector(srtcpIndex, ssrc) if isEncrypted { aad := s.rtcpAdditionalAuthenticatedData(encrypted, srtcpIndex) if _, err := s.srtcpCipher.Open(dst[srtcpHeaderSize:srtcpHeaderSize], iv[:], encrypted[srtcpHeaderSize:aadPos], aad[:]); err != nil { return nil, fmt.Errorf("%w: %w", ErrFailedToVerifyAuthTag, err) } } else { // Prepare AAD for received packet. dataEnd := aadPos - authTagLen aad := make([]byte, dataEnd+4) copy(aad, encrypted[:dataEnd]) copy(aad[dataEnd:], encrypted[aadPos:aadPos+4]) // Verify the auth tag. if _, err := s.srtcpCipher.Open(nil, iv[:], encrypted[dataEnd:aadPos], aad); err != nil { return nil, fmt.Errorf("%w: %w", ErrFailedToVerifyAuthTag, err) } // Copy the unencrypted payload. if !sameBuffer { copy(dst[srtcpHeaderSize:], encrypted[srtcpHeaderSize:dataEnd]) } } // Copy the header unencrypted. if !sameBuffer { copy(dst[:srtcpHeaderSize], encrypted[:srtcpHeaderSize]) } return dst, nil } // The 12-octet IV used by AES-GCM SRTP is formed by first concatenating // 2 octets of zeroes, the 4-octet SSRC, the 4-octet rollover counter // (ROC), and the 2-octet sequence number (SEQ). The resulting 12-octet // value is then XORed to the 12-octet salt to form the 12-octet IV. // // https://tools.ietf.org/html/rfc7714#section-8.1 func (s *srtpCipherAeadAesGcm) rtpInitializationVector(header *rtp.Header, roc uint32) [12]byte { var iv [12]byte binary.BigEndian.PutUint32(iv[2:], header.SSRC) binary.BigEndian.PutUint32(iv[6:], roc) binary.BigEndian.PutUint16(iv[10:], header.SequenceNumber) for i := range iv { iv[i] ^= s.srtpSessionSalt[i] } return iv } // The 12-octet IV used by AES-GCM SRTCP is formed by first // concatenating 2 octets of zeroes, the 4-octet SSRC identifier, // 2 octets of zeroes, a single "0" bit, and the 31-bit SRTCP index. // The resulting 12-octet value is then XORed to the 12-octet salt to // form the 12-octet IV. // // https://tools.ietf.org/html/rfc7714#section-9.1 func (s *srtpCipherAeadAesGcm) rtcpInitializationVector(srtcpIndex uint32, ssrc uint32) [12]byte { var iv [12]byte binary.BigEndian.PutUint32(iv[2:], ssrc) binary.BigEndian.PutUint32(iv[8:], srtcpIndex) for i := range iv { iv[i] ^= s.srtcpSessionSalt[i] } return iv } // In an SRTCP packet, a 1-bit Encryption flag is prepended to the // 31-bit SRTCP index to form a 32-bit value we shall call the // "ESRTCP word" // // https://tools.ietf.org/html/rfc7714#section-17 func (s *srtpCipherAeadAesGcm) rtcpAdditionalAuthenticatedData(rtcpPacket []byte, srtcpIndex uint32) [12]byte { var aad [12]byte copy(aad[:], rtcpPacket[:8]) binary.BigEndian.PutUint32(aad[8:], srtcpIndex) aad[8] |= srtcpEncryptionFlag return aad } func (s *srtpCipherAeadAesGcm) getRTCPIndex(in []byte) uint32 { return binary.BigEndian.Uint32(in[len(in)-len(s.mki)-srtcpIndexSize:]) &^ (srtcpEncryptionFlag << 24) } srtp-3.0.9/srtp_cipher_aead_aes_gcm_rfc_test.go000066400000000000000000000205441511235350500217120ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "encoding/hex" "strings" "testing" "github.com/stretchr/testify/assert" ) func fromHex(t *testing.T, s string) []byte { t.Helper() s = strings.ReplaceAll(s, " ", "") s = strings.ReplaceAll(s, "\n", "") s = strings.ReplaceAll(s, "\t", "") s = strings.ReplaceAll(s, "\r", "") b, err := hex.DecodeString(s) assert.NoError(t, err) return b } type testRfcAeadCipher struct { profile ProtectionProfile // Protection profile keys derivedSessionKeys // Derived session keys decryptedRTPPacket []byte encryptedRTPPacket []byte authenticatedRTPPacket []byte decryptedRTCPPacket []byte encryptedRTCPPacket []byte authenticatedRTCPPacket []byte } // createRfcAeadTestCiphers returns a list of test ciphers for the RFC test vectors. func createRfcAeadTestCiphers(t *testing.T) []testRfcAeadCipher { t.Helper() tests := []testRfcAeadCipher{} // AES-128-GCM, RFC 7714, Sections 16 and 17 aes128Gcm := testRfcAeadCipher{ profile: ProtectionProfileAeadAes128Gcm, keys: derivedSessionKeys{ srtpSessionKey: fromHex(t, `00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f`), srtpSessionSalt: fromHex(t, `51 75 69 64 20 70 72 6f 20 71 75 6f`), }, decryptedRTPPacket: fromHex(t, `8040f17b 8041f8d3 5501a0b2 47616c6c 69612065 7374206f 6d6e6973 20646976 69736120 696e2070 61727465 73207472 6573`), encryptedRTPPacket: fromHex(t, `8040f17b 8041f8d3 5501a0b2 f24de3a3 fb34de6c acba861c 9d7e4bca be633bd5 0d294e6f 42a5f47a 51c7d19b 36de3adf 8833899d 7f27beb1 6a9152cf 765ee439 0cce`), authenticatedRTPPacket: fromHex(t, `8040f17b 8041f8d3 5501a0b2 47616c6c 69612065 7374206f 6d6e6973 20646976 69736120 696e2070 61727465 73207472 65732249 3f82d2bc e397e9d7 9e3b19aa 4216`), decryptedRTCPPacket: fromHex(t, `81c8000d 4d617273 4e545031 4e545032 52545020 0000042a 0000e930 4c756e61 deadbeef deadbeef deadbeef deadbeef deadbeef`), encryptedRTCPPacket: fromHex(t, `81c8000d 4d617273 63e94885 dcdab67c a727d766 2f6b7e99 7ff5c0f7 6c06f32d c676a5f1 730d6fda 4ce09b46 86303ded 0bb9275b c84aa458 96cf4d2f c5abf872 45d9eade 800005d4`), authenticatedRTCPPacket: fromHex(t, `81c8000d 4d617273 4e545031 4e545032 52545020 0000042a 0000e930 4c756e61 deadbeef deadbeef deadbeef deadbeef deadbeef 841dd968 3dd78ec9 2ae58790 125f62b3 000005d4`), } aes128Gcm.keys.srtcpSessionKey = aes128Gcm.keys.srtpSessionKey aes128Gcm.keys.srtcpSessionSalt = aes128Gcm.keys.srtpSessionSalt tests = append(tests, aes128Gcm) // AES-256-GCM, RFC 7714, Sections 16 and 17 aes256Gcm := testRfcAeadCipher{ profile: ProtectionProfileAeadAes256Gcm, keys: derivedSessionKeys{ srtpSessionKey: fromHex(t, `00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f`), srtpSessionSalt: fromHex(t, `51 75 69 64 20 70 72 6f 20 71 75 6f`), }, decryptedRTPPacket: fromHex(t, `8040f17b 8041f8d3 5501a0b2 47616c6c 69612065 7374206f 6d6e6973 20646976 69736120 696e2070 61727465 73207472 6573`), encryptedRTPPacket: fromHex(t, `8040f17b 8041f8d3 5501a0b2 32b1de78 a822fe12 ef9f78fa 332e33aa b1801238 9a58e2f3 b50b2a02 76ffae0f 1ba63799 b87b7aa3 db36dfff d6b0f9bb 7878d7a7 6c13`), authenticatedRTPPacket: fromHex(t, `8040f17b 8041f8d3 5501a0b2 47616c6c 69612065 7374206f 6d6e6973 20646976 69736120 696e2070 61727465 73207472 6573a866 d5910f88 7463067c eefec452 15d4`), decryptedRTCPPacket: fromHex(t, `81c8000d 4d617273 4e545031 4e545032 52545020 0000042a 0000e930 4c756e61 deadbeef deadbeef deadbeef deadbeef deadbeef`), encryptedRTCPPacket: fromHex(t, `81c8000d 4d617273 d50ae4d1 f5ce5d30 4ba297e4 7d470c28 2c3ece5d bffe0a50 a2eaa5c1 110555be 8415f658 c61de047 6f1b6fad 1d1eb30c 4446839f 57ff6f6c b26ac3be 800005d4`), authenticatedRTCPPacket: fromHex(t, `81c8000d 4d617273 4e545031 4e545032 52545020 0000042a 0000e930 4c756e61 deadbeef deadbeef deadbeef deadbeef deadbeef 91db4afb feee5a97 8fab4393 ed2615fe 000005d4`), } aes256Gcm.keys.srtcpSessionKey = aes256Gcm.keys.srtpSessionKey aes256Gcm.keys.srtcpSessionSalt = aes256Gcm.keys.srtpSessionSalt tests = append(tests, aes256Gcm) return tests } func TestAeadCiphersWithRfcTestVectors(t *testing.T) { for _, testCase := range createRfcAeadTestCiphers(t) { t.Run(testCase.profile.String(), func(t *testing.T) { t.Run("Encrypt RTP", func(t *testing.T) { cipher, err := newSrtpCipherAeadAesGcmWithDerivedKeys(testCase.profile, testCase.keys, true, true) assert.NoError(t, err) ctx, err := createContextWithCipher(testCase.profile, cipher) assert.NoError(t, err) ctx.SetIndex(0x4d617273, 0x000005d3) actualEncrypted, err := ctx.EncryptRTP(nil, testCase.decryptedRTPPacket, nil) assert.NoError(t, err) assert.Equal(t, testCase.encryptedRTPPacket, actualEncrypted) }) t.Run("Decrypt RTP", func(t *testing.T) { cipher, err := newSrtpCipherAeadAesGcmWithDerivedKeys(testCase.profile, testCase.keys, true, true) assert.NoError(t, err) ctx, err := createContextWithCipher(testCase.profile, cipher) assert.NoError(t, err) ctx.SetIndex(0x4d617273, 0x000005d3) actualDecrypted, err := ctx.DecryptRTP(nil, testCase.encryptedRTPPacket, nil) assert.NoError(t, err) assert.Equal(t, testCase.decryptedRTPPacket, actualDecrypted) }) t.Run("Encrypt RTCP", func(t *testing.T) { cipher, err := newSrtpCipherAeadAesGcmWithDerivedKeys(testCase.profile, testCase.keys, true, true) assert.NoError(t, err) ctx, err := createContextWithCipher(testCase.profile, cipher) assert.NoError(t, err) ctx.SetIndex(0x4d617273, 0x000005d3) actualEncrypted, err := ctx.EncryptRTCP(nil, testCase.decryptedRTCPPacket, nil) assert.NoError(t, err) assert.Equal(t, testCase.encryptedRTCPPacket, actualEncrypted) }) t.Run("Decrypt RTCP", func(t *testing.T) { cipher, err := newSrtpCipherAeadAesGcmWithDerivedKeys(testCase.profile, testCase.keys, true, true) assert.NoError(t, err) ctx, err := createContextWithCipher(testCase.profile, cipher) assert.NoError(t, err) ctx.SetIndex(0x4d617273, 0x000005d3) actualDecrypted, err := ctx.DecryptRTCP(nil, testCase.encryptedRTCPPacket, nil) assert.NoError(t, err) assert.Equal(t, testCase.decryptedRTCPPacket, actualDecrypted) }) t.Run("Encrypt RTP with NULL cipher", func(t *testing.T) { cipher, err := newSrtpCipherAeadAesGcmWithDerivedKeys(testCase.profile, testCase.keys, false, false) assert.NoError(t, err) ctx, err := createContextWithCipher(testCase.profile, cipher) assert.NoError(t, err) ctx.SetIndex(0x4d617273, 0x000005d3) actualEncrypted, err := ctx.EncryptRTP(nil, testCase.decryptedRTPPacket, nil) assert.NoError(t, err) assert.Equal(t, testCase.authenticatedRTPPacket, actualEncrypted) }) t.Run("Decrypt RTP with NULL cipher", func(t *testing.T) { cipher, err := newSrtpCipherAeadAesGcmWithDerivedKeys(testCase.profile, testCase.keys, false, false) assert.NoError(t, err) ctx, err := createContextWithCipher(testCase.profile, cipher) assert.NoError(t, err) ctx.SetIndex(0x4d617273, 0x000005d3) actualDecrypted, err := ctx.DecryptRTP(nil, testCase.authenticatedRTPPacket, nil) assert.NoError(t, err) assert.Equal(t, testCase.decryptedRTPPacket, actualDecrypted) }) t.Run("Encrypt RTCP with NULL cipher", func(t *testing.T) { cipher, err := newSrtpCipherAeadAesGcmWithDerivedKeys(testCase.profile, testCase.keys, false, false) assert.NoError(t, err) ctx, err := createContextWithCipher(testCase.profile, cipher) assert.NoError(t, err) ctx.SetIndex(0x4d617273, 0x000005d3) actualEncrypted, err := ctx.EncryptRTCP(nil, testCase.decryptedRTCPPacket, nil) assert.NoError(t, err) assert.Equal(t, testCase.authenticatedRTCPPacket, actualEncrypted) }) t.Run("Decrypt RTCP with NULL cipher", func(t *testing.T) { cipher, err := newSrtpCipherAeadAesGcmWithDerivedKeys(testCase.profile, testCase.keys, false, false) assert.NoError(t, err) ctx, err := createContextWithCipher(testCase.profile, cipher) assert.NoError(t, err) ctx.SetIndex(0x4d617273, 0x000005d3) actualDecrypted, err := ctx.DecryptRTCP(nil, testCase.authenticatedRTCPPacket, nil) assert.NoError(t, err) assert.Equal(t, testCase.decryptedRTCPPacket, actualDecrypted) }) }) } } srtp-3.0.9/srtp_cipher_aes_cm_hmac_sha1.go000066400000000000000000000301761511235350500206060ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( //nolint:gci "crypto/aes" "crypto/cipher" "crypto/hmac" "crypto/sha1" //nolint:gosec "crypto/subtle" "encoding/binary" "hash" "github.com/pion/rtp" ) type srtpCipherAesCmHmacSha1 struct { protectionProfileWithArgs srtpSessionSalt []byte srtpSessionAuth hash.Hash srtpBlock cipher.Block srtpEncrypted bool srtcpSessionSalt []byte srtcpSessionAuth hash.Hash srtcpBlock cipher.Block srtcpEncrypted bool mki []byte useCryptex bool } //nolint:cyclop func newSrtpCipherAesCmHmacSha1( profile protectionProfileWithArgs, masterKey, masterSalt, mki []byte, encryptSRTP, encryptSRTCP, useCryptex bool, ) (*srtpCipherAesCmHmacSha1, error) { switch profile.ProtectionProfile { case ProtectionProfileNullHmacSha1_80, ProtectionProfileNullHmacSha1_32: encryptSRTP = false encryptSRTCP = false default: } srtpCipher := &srtpCipherAesCmHmacSha1{ protectionProfileWithArgs: profile, srtpEncrypted: encryptSRTP, srtcpEncrypted: encryptSRTCP, useCryptex: useCryptex, } srtpSessionKey, err := aesCmKeyDerivation(labelSRTPEncryption, masterKey, masterSalt, 0, len(masterKey)) if err != nil { return nil, err } else if srtpCipher.srtpBlock, err = aes.NewCipher(srtpSessionKey); err != nil { return nil, err } srtcpSessionKey, err := aesCmKeyDerivation(labelSRTCPEncryption, masterKey, masterSalt, 0, len(masterKey)) if err != nil { return nil, err } else if srtpCipher.srtcpBlock, err = aes.NewCipher(srtcpSessionKey); err != nil { return nil, err } if srtpCipher.srtpSessionSalt, err = aesCmKeyDerivation( labelSRTPSalt, masterKey, masterSalt, 0, len(masterSalt), ); err != nil { return nil, err } else if srtpCipher.srtcpSessionSalt, err = aesCmKeyDerivation( labelSRTCPSalt, masterKey, masterSalt, 0, len(masterSalt), ); err != nil { return nil, err } authKeyLen, err := profile.AuthKeyLen() if err != nil { return nil, err } srtpSessionAuthTag, err := aesCmKeyDerivation(labelSRTPAuthenticationTag, masterKey, masterSalt, 0, authKeyLen) if err != nil { return nil, err } srtcpSessionAuthTag, err := aesCmKeyDerivation(labelSRTCPAuthenticationTag, masterKey, masterSalt, 0, authKeyLen) if err != nil { return nil, err } srtpCipher.srtcpSessionAuth = hmac.New(sha1.New, srtcpSessionAuthTag) srtpCipher.srtpSessionAuth = hmac.New(sha1.New, srtpSessionAuthTag) mkiLen := len(mki) if mkiLen > 0 { srtpCipher.mki = make([]byte, mkiLen) copy(srtpCipher.mki, mki) } return srtpCipher, nil } func (s *srtpCipherAesCmHmacSha1) encryptRTP( dst []byte, header *rtp.Header, headerLen int, plaintext []byte, roc uint32, rocInAuthTag bool, ) (ciphertext []byte, err error) { // Grow the given buffer to fit the output. authTagLen, err := s.AuthTagRTPLen() if err != nil { return nil, err } payloadLen := len(plaintext) - headerLen dstLen := headerLen + payloadLen + len(s.mki) + authTagLen insertEmptyExtHdr := needsEmptyExtensionHeader(s.useCryptex, header) if insertEmptyExtHdr { dstLen += extensionHeaderSize } dst = growBufferSize(dst, dstLen) sameBuffer := isSameBuffer(dst, plaintext) if insertEmptyExtHdr { // Insert an empty extension header to plaintext using dst buffer. After this operation dst is used as the // plaintext buffer for next operations. plaintext = insertEmptyExtensionHeader(dst, plaintext, sameBuffer, header) sameBuffer = true headerLen += extensionHeaderSize } err = s.doEncryptRTP(dst, header, headerLen, plaintext, roc, rocInAuthTag, sameBuffer, payloadLen) if err != nil { return nil, err } return dst, nil } func (s *srtpCipherAesCmHmacSha1) doEncryptRTP(dst []byte, header *rtp.Header, headerLen int, plaintext []byte, roc uint32, rocInAuthTag bool, sameBuffer bool, payloadLen int, ) error { encrypt := func(dst, plaintext []byte, headerLen int) error { counter := generateCounter(header.SequenceNumber, roc, header.SSRC, s.srtpSessionSalt) return xorBytesCTR(s.srtpBlock, counter[:], dst[headerLen:], plaintext[headerLen:]) } var err error switch { case s.useCryptex && header.Extension: err = encryptCryptexRTP(dst, plaintext, sameBuffer, header, encrypt) case s.srtpEncrypted: // Copy the header unencrypted. if !sameBuffer { copy(dst, plaintext[:headerLen]) } // Encrypt the payload err = encrypt(dst, plaintext, headerLen) case !sameBuffer: copy(dst, plaintext) default: } if err != nil { return err } n := headerLen + payloadLen // Generate the auth tag. authTag, err := s.generateSrtpAuthTag(dst[:n], roc, rocInAuthTag) if err != nil { return err } // Append the MKI (if used) if len(s.mki) > 0 { copy(dst[n:], s.mki) n += len(s.mki) } // Write the auth tag to the dest. copy(dst[n:], authTag) return nil } func (s *srtpCipherAesCmHmacSha1) decryptRTP( dst, ciphertext []byte, header *rtp.Header, headerLen int, roc uint32, rocInAuthTag bool, ) ([]byte, error) { // Split the auth tag and the cipher text into two parts. authTagLen, err := s.AuthTagRTPLen() if err != nil { return nil, err } // Split the auth tag and the cipher text into two parts. actualTag := ciphertext[len(ciphertext)-authTagLen:] ciphertext = ciphertext[:len(ciphertext)-len(s.mki)-authTagLen] // Generate the auth tag we expect to see from the ciphertext. expectedTag, err := s.generateSrtpAuthTag(ciphertext, roc, rocInAuthTag) if err != nil { return nil, err } // See if the auth tag actually matches. // We use a constant time comparison to prevent timing attacks. if subtle.ConstantTimeCompare(actualTag, expectedTag) != 1 { return nil, ErrFailedToVerifyAuthTag } sameBuffer := isSameBuffer(dst, ciphertext) err = s.doDecryptRTP(dst, ciphertext, header, headerLen, roc, sameBuffer) if err != nil { return nil, err } return dst, nil } func (s *srtpCipherAesCmHmacSha1) doDecryptRTP(dst, ciphertext []byte, header *rtp.Header, headerLen int, roc uint32, sameBuffer bool, ) error { decrypt := func(dst, ciphertext []byte, headerLen int) error { counter := generateCounter(header.SequenceNumber, roc, header.SSRC, s.srtpSessionSalt) return xorBytesCTR(s.srtpBlock, counter[:], dst[headerLen:], ciphertext[headerLen:]) } switch { case isCryptexPacket(header): err := decryptCryptexRTP(dst, ciphertext, sameBuffer, header, headerLen, decrypt) if err != nil { return err } case s.srtpEncrypted: // Write the plaintext header to the destination buffer. if !sameBuffer { copy(dst, ciphertext[:headerLen]) } // Decrypt the ciphertext for the payload. err := decrypt(dst, ciphertext, headerLen) if err != nil { return err } case !sameBuffer: copy(dst, ciphertext) default: } return nil } func (s *srtpCipherAesCmHmacSha1) encryptRTCP(dst, decrypted []byte, srtcpIndex uint32, ssrc uint32) ([]byte, error) { authTagLen, err := s.AuthTagRTCPLen() if err != nil { return nil, err } mkiLen := len(s.mki) decryptedLen := len(decrypted) encryptedLen := decryptedLen + authTagLen + mkiLen + srtcpIndexSize dst = growBufferSize(dst, encryptedLen) sameBuffer := isSameBuffer(dst, decrypted) if !sameBuffer { copy(dst, decrypted[:srtcpHeaderSize]) // Copy the first 8 bytes (RTCP header) } // Encrypt everything after header if s.srtcpEncrypted { counter := generateCounter(uint16(srtcpIndex&0xffff), srtcpIndex>>16, ssrc, s.srtcpSessionSalt) //nolint:gosec // G115 if err = xorBytesCTR(s.srtcpBlock, counter[:], dst[srtcpHeaderSize:], decrypted[srtcpHeaderSize:]); err != nil { return nil, err } // Add SRTCP Index and set Encryption bit binary.BigEndian.PutUint32(dst[decryptedLen:], srtcpIndex) dst[decryptedLen] |= srtcpEncryptionFlag } else { // Copy the decrypted payload as is if !sameBuffer { copy(dst[srtcpHeaderSize:], decrypted[srtcpHeaderSize:]) } // Add SRTCP Index with Encryption bit cleared binary.BigEndian.PutUint32(dst[decryptedLen:], srtcpIndex) } n := decryptedLen + srtcpIndexSize // Generate the authentication tag authTag, err := s.generateSrtcpAuthTag(dst[:n]) if err != nil { return nil, err } // Include the MKI if provided if len(s.mki) > 0 { copy(dst[n:], s.mki) n += mkiLen } // Append the auth tag at the end of the buffer copy(dst[n:], authTag) return dst, nil } func (s *srtpCipherAesCmHmacSha1) decryptRTCP(dst, encrypted []byte, index, ssrc uint32) ([]byte, error) { authTagLen, err := s.AuthTagRTCPLen() if err != nil { return nil, err } mkiLen := len(s.mki) encryptedLen := len(encrypted) decryptedLen := encryptedLen - (authTagLen + mkiLen + srtcpIndexSize) if decryptedLen < 8 { return nil, errTooShortRTCP } expectedTag, err := s.generateSrtcpAuthTag(encrypted[:encryptedLen-mkiLen-authTagLen]) if err != nil { return nil, err } actualTag := encrypted[encryptedLen-authTagLen:] if subtle.ConstantTimeCompare(actualTag, expectedTag) != 1 { return nil, ErrFailedToVerifyAuthTag } dst = growBufferSize(dst, decryptedLen) sameBuffer := isSameBuffer(dst, encrypted) if !sameBuffer { copy(dst, encrypted[:srtcpHeaderSize]) // Copy the first 8 bytes (RTCP header) } isEncrypted := encrypted[decryptedLen]&srtcpEncryptionFlag != 0 if isEncrypted { counter := generateCounter(uint16(index&0xffff), index>>16, ssrc, s.srtcpSessionSalt) //nolint:gosec // G115 err = xorBytesCTR(s.srtcpBlock, counter[:], dst[srtcpHeaderSize:], encrypted[srtcpHeaderSize:decryptedLen]) } else if !sameBuffer { copy(dst[srtcpHeaderSize:], encrypted[srtcpHeaderSize:]) } return dst, err } func (s *srtpCipherAesCmHmacSha1) generateSrtpAuthTag(buf []byte, roc uint32, rocInAuthTag bool) ([]byte, error) { // https://tools.ietf.org/html/rfc3711#section-4.2 // In the case of SRTP, M SHALL consist of the Authenticated // Portion of the packet (as specified in Figure 1) concatenated with // the ROC, M = Authenticated Portion || ROC; // // The pre-defined authentication transform for SRTP is HMAC-SHA1 // [RFC2104]. With HMAC-SHA1, the SRTP_PREFIX_LENGTH (Figure 3) SHALL // be 0. For SRTP (respectively SRTCP), the HMAC SHALL be applied to // the session authentication key and M as specified above, i.e., // HMAC(k_a, M). The HMAC output SHALL then be truncated to the n_tag // left-most bits. // - Authenticated portion of the packet is everything BEFORE MKI // - k_a is the session message authentication key // - n_tag is the bit-length of the output authentication tag s.srtpSessionAuth.Reset() if _, err := s.srtpSessionAuth.Write(buf); err != nil { return nil, err } // For SRTP only, we need to hash the rollover counter as well. rocRaw := [4]byte{} binary.BigEndian.PutUint32(rocRaw[:], roc) _, err := s.srtpSessionAuth.Write(rocRaw[:]) if err != nil { return nil, err } // Truncate the hash to the size indicated by the profile authTagLen, err := s.AuthTagRTPLen() if err != nil { return nil, err } var authTag []byte if rocInAuthTag { authTag = append(authTag, rocRaw[:]...) } return s.srtpSessionAuth.Sum(authTag)[0:authTagLen], nil } func (s *srtpCipherAesCmHmacSha1) generateSrtcpAuthTag(buf []byte) ([]byte, error) { // https://tools.ietf.org/html/rfc3711#section-4.2 // // The pre-defined authentication transform for SRTP is HMAC-SHA1 // [RFC2104]. With HMAC-SHA1, the SRTP_PREFIX_LENGTH (Figure 3) SHALL // be 0. For SRTP (respectively SRTCP), the HMAC SHALL be applied to // the session authentication key and M as specified above, i.e., // HMAC(k_a, M). The HMAC output SHALL then be truncated to the n_tag // left-most bits. // - Authenticated portion of the packet is everything BEFORE MKI // - k_a is the session message authentication key // - n_tag is the bit-length of the output authentication tag s.srtcpSessionAuth.Reset() if _, err := s.srtcpSessionAuth.Write(buf); err != nil { return nil, err } authTagLen, err := s.AuthTagRTCPLen() if err != nil { return nil, err } return s.srtcpSessionAuth.Sum(nil)[0:authTagLen], nil } func (s *srtpCipherAesCmHmacSha1) getRTCPIndex(in []byte) uint32 { authTagLen, _ := s.AuthTagRTCPLen() tailOffset := len(in) - (authTagLen + srtcpIndexSize + len(s.mki)) srtcpIndexBuffer := in[tailOffset : tailOffset+srtcpIndexSize] return binary.BigEndian.Uint32(srtcpIndexBuffer) &^ (1 << 31) } srtp-3.0.9/srtp_cipher_aes_cm_hmac_sha1_rfc_test.go000066400000000000000000000055351511235350500225000ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "testing" "github.com/stretchr/testify/assert" ) type testRfcAesCipher struct { profile ProtectionProfile // Protection profile keys derivedSessionKeys // Derived session keys keystream []byte } // createRfcAesTestCiphers returns a list of test ciphers for the RFC test vectors. func createRfcAesTestCiphers(t *testing.T) []testRfcAesCipher { t.Helper() tests := []testRfcAesCipher{} // AES-128-CM, RFC 3711, Appendix B.2 aes128Cm := testRfcAesCipher{ profile: ProtectionProfileAes128CmHmacSha1_80, keys: derivedSessionKeys{ srtpSessionKey: fromHex(t, `2B7E151628AED2A6ABF7158809CF4F3C`), srtpSessionSalt: fromHex(t, `F0F1F2F3F4F5F6F7F8F9FAFBFCFD0000`), }, keystream: fromHex(t, `E03EAD0935C95E80E166B16DD92B4EB4 D23513162B02D0F72A43A2FE4A5F97AB 41E95B3BB0A2E8DD477901E4FCA894C0`), } aes128Cm.keys.srtcpSessionKey = aes128Cm.keys.srtpSessionKey aes128Cm.keys.srtcpSessionSalt = aes128Cm.keys.srtpSessionSalt tests = append(tests, aes128Cm) // AES-256-CM, RFC 6188, Section 7.1 aes256Cm := testRfcAesCipher{ profile: ProtectionProfileAes256CmHmacSha1_80, keys: derivedSessionKeys{ srtpSessionKey: fromHex(t, `57f82fe3613fd170a85ec93c40b1f092 2ec4cb0dc025b58272147cc438944a98`), srtpSessionSalt: fromHex(t, `f0f1f2f3f4f5f6f7f8f9fafbfcfd0000`), }, keystream: fromHex(t, `92bdd28a93c3f52511c677d08b5515a4 9da71b2378a854f67050756ded165bac 63c4868b7096d88421b563b8c94c9a31`), } aes256Cm.keys.srtcpSessionKey = aes256Cm.keys.srtpSessionKey aes256Cm.keys.srtcpSessionSalt = aes256Cm.keys.srtpSessionSalt tests = append(tests, aes256Cm) return tests } func TestAesCiphersWithRfcTestVectors(t *testing.T) { for _, testCase := range createRfcAesTestCiphers(t) { t.Run(testCase.profile.String(), func(t *testing.T) { // Use zero SSRC and sequence number as specified in RFC rtpHeader := []byte{ 0x80, 0x0f, 0x00, 0x00, 0xde, 0xca, 0xfb, 0xad, 0x00, 0x00, 0x00, 0x00, } t.Run("Keystream generation", func(t *testing.T) { cipher, err := newSrtpCipherAesCmHmacSha1WithDerivedKeys(testCase.profile, testCase.keys, true, true) assert.NoError(t, err) ctx, err := createContextWithCipher(testCase.profile, cipher) assert.NoError(t, err) // Generated AES keystream will be XOR'ed with zeroes in RTP packet payload, // so SRTP payload will be equal to keystream decryptedRTPPacket := make([]byte, len(rtpHeader)+len(testCase.keystream)) copy(decryptedRTPPacket, rtpHeader) actualEncrypted, err := ctx.EncryptRTP(nil, decryptedRTPPacket, nil) assert.NoError(t, err) assert.Equal(t, rtpHeader, actualEncrypted[:len(rtpHeader)]) assert.Equal(t, testCase.keystream, actualEncrypted[len(rtpHeader):len(rtpHeader)+len(testCase.keystream)]) }) }) } } srtp-3.0.9/srtp_cipher_rcc_test.go000066400000000000000000000217761511235350500172670ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "encoding/binary" "fmt" "slices" "testing" "github.com/pion/rtp" "github.com/stretchr/testify/assert" ) // Tests for Roll-over Counter Carrying Transform from RFC 4771 // nolint:cyclop,maintidx func TestRCC(t *testing.T) { const ( ROC = uint32(0x12345678) SSRC = uint32(0xcafebabe) ROCTransmitRate = uint16(10) ) MKI := []byte{0x05, 0x06, 0x07, 0x08} decryptedRTPPacket := []byte{ 0x80, 0x0f, 0x00, 0x00, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, } // Test all combinations of profiles, MKI enabled/disabled and RTP encryption enabled/disabled profiles := []ProtectionProfile{ ProtectionProfileAes128CmHmacSha1_80, ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_80, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileAeadAes128Gcm, ProtectionProfileAeadAes256Gcm, } useMkiInTest := map[string]bool{ "NoMKI": false, "MKI": true, } useRTPEncryption := map[string]bool{ "Encrypt": false, "NULL": true, } useLongerAuthTag := map[string]bool{ "NormalAuthTag": false, "LongerAuthTag": true, } for _, profile := range profiles { for useMkiName, useMki := range useMkiInTest { for rtpEncryptName, rtpEncrypt := range useRTPEncryption { for longerAuthTagName, longerAuthTag := range useLongerAuthTag { aeadAuthTagLen, err := profile.AEADAuthTagLen() assert.NoError(t, err) if aeadAuthTagLen != 0 && longerAuthTag { continue } t.Run(fmt.Sprintf("%s-%s-%s-%s", profile.String(), useMkiName, rtpEncryptName, longerAuthTagName), func(t *testing.T) { keyLen, err := profile.KeyLen() assert.NoError(t, err) saltLen, err := profile.SaltLen() assert.NoError(t, err) authTagLen, err := profile.AuthTagRTPLen() assert.NoError(t, err) masterKey := make([]byte, keyLen) masterSalt := make([]byte, saltLen) var optsRCC, optsNoRCC []ContextOption if aeadAuthTagLen == 0 { optsRCC = []ContextOption{RolloverCounterCarryingTransform(RCCMode2, ROCTransmitRate)} } else { optsRCC = []ContextOption{RolloverCounterCarryingTransform(RCCMode3, ROCTransmitRate)} } appendCtxOpt := func(opt ContextOption) { optsRCC = append(optsRCC, opt) optsNoRCC = append(optsNoRCC, opt) } if useMki { appendCtxOpt(MasterKeyIndicator(MKI)) } if !rtpEncrypt { appendCtxOpt(SRTPNoEncryption()) } if longerAuthTag { appendCtxOpt(SRTPAuthenticationTagLength(authTagLen + 4)) } else if authTagLen == 4 { // ROC overrides whole auth tag, need to explicitly set it to 4 appendCtxOpt(SRTPAuthenticationTagLength(4)) } t.Run("CheckEncryptDecryptRTPPacketsWithROC", func(t *testing.T) { ctxEnc, err := CreateContext(masterKey, masterSalt, profile, optsRCC...) assert.NoError(t, err) ctxEnc.SetROC(SSRC, ROC) ctxEncNoRCC, err := CreateContext(masterKey, masterSalt, profile, optsNoRCC...) assert.NoError(t, err) ctxEncNoRCC.SetROC(SSRC, ROC) ctxDec, err := CreateContext(masterKey, masterSalt, profile, optsRCC...) assert.NoError(t, err) rtpPacket := slices.Clone(decryptedRTPPacket) var header rtp.Header _, err = header.Unmarshal(rtpPacket) assert.NoError(t, err) header.SequenceNumber = 0 _, err = header.MarshalTo(rtpPacket) assert.NoError(t, err) srtpPacketWithROC, err := ctxEnc.EncryptRTP(nil, rtpPacket, nil) assert.NoError(t, err) srtpPacketWithoutROC, err := ctxEncNoRCC.EncryptRTP(nil, rtpPacket, nil) assert.NoError(t, err) pktLen := len(srtpPacketWithROC) switch { case aeadAuthTagLen == 0 && !longerAuthTag: // AES-CM with default auth tag length - ROC is at the beginning of the auth tag, // which is shifted and truncated by 4 bytes assert.Equal(t, pktLen, len(srtpPacketWithoutROC)) // ROC assert.Equal(t, ROC, binary.BigEndian.Uint32(srtpPacketWithROC[pktLen-authTagLen:])) // Header, payload, MKI assert.Equal(t, srtpPacketWithROC[:pktLen-authTagLen], srtpPacketWithoutROC[:pktLen-authTagLen]) // Auth tag assert.Equal(t, srtpPacketWithROC[pktLen-authTagLen+4:], srtpPacketWithoutROC[pktLen-authTagLen:pktLen-4]) case aeadAuthTagLen == 0 && longerAuthTag: // AES-CM with auth tag length increased by 4 - ROC is at the beginning of the auth tag assert.Equal(t, pktLen, len(srtpPacketWithoutROC)) // ROC assert.Equal(t, ROC, binary.BigEndian.Uint32(srtpPacketWithROC[pktLen-authTagLen-4:])) // Header, payload, MKI assert.Equal(t, srtpPacketWithROC[:pktLen-authTagLen-4], srtpPacketWithoutROC[:pktLen-authTagLen-4]) // Auth tag assert.Equal(t, srtpPacketWithROC[pktLen-authTagLen:], srtpPacketWithoutROC[pktLen-authTagLen-4:pktLen-4]) default: // AEAD - ROC is appended at the end of the packet assert.Equal(t, pktLen-4, len(srtpPacketWithoutROC)) // ROC assert.Equal(t, ROC, binary.BigEndian.Uint32(srtpPacketWithROC[pktLen-4:])) // Header, payload, AEAD auth tag, MKI assert.Equal(t, srtpPacketWithROC[:pktLen-4], srtpPacketWithoutROC) } decrypted, err := ctxDec.DecryptRTP(nil, srtpPacketWithROC, nil) assert.NoError(t, err) assert.Equal(t, rtpPacket, decrypted) }) t.Run("CheckEncryptDecryptRTPPacketsWithoutROC", func(t *testing.T) { ctxEnc, err := CreateContext(masterKey, masterSalt, profile, optsRCC...) assert.NoError(t, err) ctxEnc.SetROC(SSRC, ROC) ctxEncNoRCC, err := CreateContext(masterKey, masterSalt, profile, optsNoRCC...) assert.NoError(t, err) ctxEncNoRCC.SetROC(SSRC, ROC) ctxDec, err := CreateContext(masterKey, masterSalt, profile, optsRCC...) assert.NoError(t, err) ctxDec.SetROC(SSRC, ROC) rtpPacket := slices.Clone(decryptedRTPPacket) var header rtp.Header _, err = header.Unmarshal(rtpPacket) assert.NoError(t, err) header.SequenceNumber = 1 _, err = header.MarshalTo(rtpPacket) assert.NoError(t, err) srtpPacket1, err := ctxEnc.EncryptRTP(nil, rtpPacket, nil) assert.NoError(t, err) srtpPacket2, err := ctxEncNoRCC.EncryptRTP(nil, rtpPacket, nil) assert.NoError(t, err) assert.Equal(t, srtpPacket1, srtpPacket2) decrypted, err := ctxDec.DecryptRTP(nil, srtpPacket1, nil) assert.NoError(t, err) assert.Equal(t, rtpPacket, decrypted) }) t.Run("CheckROCFromSRTPPacketIsUsed", func(t *testing.T) { ctxEnc, err := CreateContext(masterKey, masterSalt, profile, optsRCC...) assert.NoError(t, err) ctxEnc.SetROC(SSRC, ROC) ctxDec, err := CreateContext(masterKey, masterSalt, profile, optsRCC...) assert.NoError(t, err) // Do not set ROC in ctxDec rtpPacket := slices.Clone(decryptedRTPPacket) var header rtp.Header _, err = header.Unmarshal(rtpPacket) assert.NoError(t, err) for n := ROCTransmitRate - 1; n <= ROCTransmitRate+1; n++ { header.SequenceNumber = n _, err = header.MarshalTo(rtpPacket) assert.NoError(t, err) srtpPacket, err := ctxEnc.EncryptRTP(nil, rtpPacket, nil) assert.NoError(t, err) _, err = ctxDec.DecryptRTP(nil, srtpPacket, nil) if n == ROCTransmitRate-1 { assert.ErrorIs(t, err, ErrFailedToVerifyAuthTag) } else { assert.NoError(t, err) } } }) t.Run("CheckROCTransmitRate", func(t *testing.T) { ctxEnc, err := CreateContext(masterKey, masterSalt, profile, optsRCC...) assert.NoError(t, err) ctxEnc.SetROC(SSRC, ROC) ctxEncNoRCC, err := CreateContext(masterKey, masterSalt, profile, optsNoRCC...) assert.NoError(t, err) ctxEncNoRCC.SetROC(SSRC, ROC) rtpPacket := slices.Clone(decryptedRTPPacket) var header rtp.Header _, err = header.Unmarshal(rtpPacket) assert.NoError(t, err) for n := uint16(0); n <= ROCTransmitRate*4; n++ { header.SequenceNumber = n _, err = header.MarshalTo(rtpPacket) assert.NoError(t, err) srtpPacket1, err := ctxEnc.EncryptRTP(nil, rtpPacket, nil) assert.NoError(t, err) srtpPacket2, err := ctxEncNoRCC.EncryptRTP(nil, rtpPacket, nil) assert.NoError(t, err) if n%ROCTransmitRate == 0 { assert.NotEqual(t, srtpPacket1, srtpPacket2) } else { assert.Equal(t, srtpPacket1, srtpPacket2) } } }) }) } } } } } srtp-3.0.9/srtp_cipher_test.go000066400000000000000000001021011511235350500164160ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "testing" "github.com/stretchr/testify/assert" ) type testCipher struct { profile ProtectionProfile // Protection profile masterKey []byte // Master key masterSalt []byte // Master salt mki []byte // Master key identifier decryptedRTPPacket []byte encryptedRTPPacket []byte encryptedRTPPacketWithMKI []byte authenticatedRTPPacket []byte authenticatedRTPPacketWithMKI []byte decryptedRTCPPacket []byte encryptedRTCPPacket []byte encryptedRTCPPacketWithMKI []byte authenticatedRTCPPacket []byte authenticatedRTCPPacketWithMKI []byte } // create array of testCiphers for each supported profile. func createTestCiphers(t *testing.T) []testCipher { //nolint:maintidx t.Helper() tests := []testCipher{ { //nolint:dupl profile: ProtectionProfileAes128CmHmacSha1_32, encryptedRTPPacket: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xe2, 0xd8, 0xdf, 0x8f, 0x7a, 0x75, 0xd6, 0x88, 0xc3, 0x50, 0x2e, 0xee, 0xc2, 0xa9, 0x80, 0x66, 0xcd, 0x7c, 0x0d, 0x09, }, encryptedRTCPPacket: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0x56, 0x74, 0xbf, 0x01, 0x81, 0x3d, 0xc0, 0x62, 0xac, 0x1d, 0xf6, 0xf7, 0x5f, 0x77, 0xc6, 0x88, 0x80, 0x00, 0x00, 0x01, 0x3d, 0xb7, 0xa1, 0x98, 0x37, 0xff, 0x64, 0xe5, 0xcb, 0xd2, }, encryptedRTPPacketWithMKI: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xe2, 0xd8, 0xdf, 0x8f, 0x7a, 0x75, 0xd6, 0x88, 0xc3, 0x50, 0x2e, 0xee, 0xc2, 0xa9, 0x80, 0x66, 0x01, 0x02, 0x00, 0x04, 0xcd, 0x7c, 0x0d, 0x09, }, encryptedRTCPPacketWithMKI: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0x56, 0x74, 0xbf, 0x01, 0x81, 0x3d, 0xc0, 0x62, 0xac, 0x1d, 0xf6, 0xf7, 0x5f, 0x77, 0xc6, 0x88, 0x80, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x04, 0x3d, 0xb7, 0xa1, 0x98, 0x37, 0xff, 0x64, 0xe5, 0xcb, 0xd2, }, authenticatedRTPPacket: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xda, 0x9a, 0x3c, 0xa1, }, authenticatedRTCPPacket: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x00, 0x00, 0x00, 0x01, 0xd2, 0xa2, 0x36, 0x2d, 0x01, 0x1b, 0x8c, 0xfc, 0x0a, 0xc9, }, authenticatedRTPPacketWithMKI: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x01, 0x02, 0x00, 0x04, 0xda, 0x9a, 0x3c, 0xa1, }, authenticatedRTCPPacketWithMKI: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x04, 0xd2, 0xa2, 0x36, 0x2d, 0x01, 0x1b, 0x8c, 0xfc, 0x0a, 0xc9, }, }, { //nolint:dupl profile: ProtectionProfileAes128CmHmacSha1_80, encryptedRTPPacket: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xe2, 0xd8, 0xdf, 0x8f, 0x7a, 0x75, 0xd6, 0x88, 0xc3, 0x50, 0x2e, 0xee, 0xc2, 0xa9, 0x80, 0x66, 0xcd, 0x7c, 0x0d, 0x09, 0xca, 0x44, 0x32, 0xa5, 0x6e, 0x3d, }, encryptedRTCPPacket: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0x56, 0x74, 0xbf, 0x01, 0x81, 0x3d, 0xc0, 0x62, 0xac, 0x1d, 0xf6, 0xf7, 0x5f, 0x77, 0xc6, 0x88, 0x80, 0x00, 0x00, 0x01, 0x3d, 0xb7, 0xa1, 0x98, 0x37, 0xff, 0x64, 0xe5, 0xcb, 0xd2, }, encryptedRTPPacketWithMKI: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xe2, 0xd8, 0xdf, 0x8f, 0x7a, 0x75, 0xd6, 0x88, 0xc3, 0x50, 0x2e, 0xee, 0xc2, 0xa9, 0x80, 0x66, 0x01, 0x02, 0x00, 0x04, 0xcd, 0x7c, 0x0d, 0x09, 0xca, 0x44, 0x32, 0xa5, 0x6e, 0x3d, }, encryptedRTCPPacketWithMKI: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0x56, 0x74, 0xbf, 0x01, 0x81, 0x3d, 0xc0, 0x62, 0xac, 0x1d, 0xf6, 0xf7, 0x5f, 0x77, 0xc6, 0x88, 0x80, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x04, 0x3d, 0xb7, 0xa1, 0x98, 0x37, 0xff, 0x64, 0xe5, 0xcb, 0xd2, }, authenticatedRTPPacket: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xda, 0x9a, 0x3c, 0xa1, 0xba, 0x8e, 0xfd, 0xd7, 0x07, 0xdc, }, authenticatedRTCPPacket: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x00, 0x00, 0x00, 0x01, 0xd2, 0xa2, 0x36, 0x2d, 0x01, 0x1b, 0x8c, 0xfc, 0x0a, 0xc9, }, authenticatedRTPPacketWithMKI: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x01, 0x02, 0x00, 0x04, 0xda, 0x9a, 0x3c, 0xa1, 0xba, 0x8e, 0xfd, 0xd7, 0x07, 0xdc, }, authenticatedRTCPPacketWithMKI: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x04, 0xd2, 0xa2, 0x36, 0x2d, 0x01, 0x1b, 0x8c, 0xfc, 0x0a, 0xc9, }, }, { //nolint:dupl profile: ProtectionProfileAes256CmHmacSha1_32, encryptedRTPPacket: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xac, 0x3b, 0xca, 0x88, 0x14, 0x37, 0x57, 0x83, 0x35, 0xc6, 0xd4, 0x57, 0xf1, 0xc3, 0x6b, 0xa7, 0x3d, 0x71, 0x48, 0x63, }, encryptedRTCPPacket: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0x97, 0x04, 0x31, 0xdc, 0x4a, 0xe6, 0xd2, 0xaf, 0xd6, 0x54, 0xbf, 0x90, 0xf4, 0x35, 0x44, 0x9e, 0x80, 0x00, 0x00, 0x01, 0xbf, 0x18, 0x18, 0x2d, 0xd1, 0x18, 0x81, 0x28, 0x78, 0xb1, }, encryptedRTPPacketWithMKI: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xac, 0x3b, 0xca, 0x88, 0x14, 0x37, 0x57, 0x83, 0x35, 0xc6, 0xd4, 0x57, 0xf1, 0xc3, 0x6b, 0xa7, 0x01, 0x02, 0x00, 0x04, 0x3d, 0x71, 0x48, 0x63, }, encryptedRTCPPacketWithMKI: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0x97, 0x04, 0x31, 0xdc, 0x4a, 0xe6, 0xd2, 0xaf, 0xd6, 0x54, 0xbf, 0x90, 0xf4, 0x35, 0x44, 0x9e, 0x80, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x04, 0xbf, 0x18, 0x18, 0x2d, 0xd1, 0x18, 0x81, 0x28, 0x78, 0xb1, }, authenticatedRTPPacket: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x3d, 0x03, 0x2a, 0x52, }, authenticatedRTCPPacket: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x00, 0x00, 0x00, 0x01, 0xf6, 0xd9, 0xd0, 0xc1, 0x44, 0xf6, 0x6a, 0xb5, 0x25, 0x43, }, authenticatedRTPPacketWithMKI: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x01, 0x02, 0x00, 0x04, 0x3d, 0x03, 0x2a, 0x52, }, authenticatedRTCPPacketWithMKI: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x04, 0xf6, 0xd9, 0xd0, 0xc1, 0x44, 0xf6, 0x6a, 0xb5, 0x25, 0x43, }, }, { //nolint:dupl profile: ProtectionProfileAes256CmHmacSha1_80, encryptedRTPPacket: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xac, 0x3b, 0xca, 0x88, 0x14, 0x37, 0x57, 0x83, 0x35, 0xc6, 0xd4, 0x57, 0xf1, 0xc3, 0x6b, 0xa7, 0x3d, 0x71, 0x48, 0x63, 0x90, 0x9b, 0xbf, 0x15, 0xac, 0xec, }, encryptedRTCPPacket: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0x97, 0x04, 0x31, 0xdc, 0x4a, 0xe6, 0xd2, 0xaf, 0xd6, 0x54, 0xbf, 0x90, 0xf4, 0x35, 0x44, 0x9e, 0x80, 0x00, 0x00, 0x01, 0xbf, 0x18, 0x18, 0x2d, 0xd1, 0x18, 0x81, 0x28, 0x78, 0xb1, }, encryptedRTPPacketWithMKI: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xac, 0x3b, 0xca, 0x88, 0x14, 0x37, 0x57, 0x83, 0x35, 0xc6, 0xd4, 0x57, 0xf1, 0xc3, 0x6b, 0xa7, 0x01, 0x02, 0x00, 0x04, 0x3d, 0x71, 0x48, 0x63, 0x90, 0x9b, 0xbf, 0x15, 0xac, 0xec, }, encryptedRTCPPacketWithMKI: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0x97, 0x04, 0x31, 0xdc, 0x4a, 0xe6, 0xd2, 0xaf, 0xd6, 0x54, 0xbf, 0x90, 0xf4, 0x35, 0x44, 0x9e, 0x80, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x04, 0xbf, 0x18, 0x18, 0x2d, 0xd1, 0x18, 0x81, 0x28, 0x78, 0xb1, }, authenticatedRTPPacket: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x3d, 0x03, 0x2a, 0x52, 0x72, 0x97, 0x99, 0x48, 0x5c, 0x39, }, authenticatedRTCPPacket: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x00, 0x00, 0x00, 0x01, 0xf6, 0xd9, 0xd0, 0xc1, 0x44, 0xf6, 0x6a, 0xb5, 0x25, 0x43, }, authenticatedRTPPacketWithMKI: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x01, 0x02, 0x00, 0x04, 0x3d, 0x03, 0x2a, 0x52, 0x72, 0x97, 0x99, 0x48, 0x5c, 0x39, }, authenticatedRTCPPacketWithMKI: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x04, 0xf6, 0xd9, 0xd0, 0xc1, 0x44, 0xf6, 0x6a, 0xb5, 0x25, 0x43, }, }, { //nolint:dupl profile: ProtectionProfileAeadAes128Gcm, encryptedRTPPacket: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xc5, 0x00, 0x2e, 0xde, 0x04, 0xcf, 0xdd, 0x2e, 0xb9, 0x11, 0x59, 0xe0, 0x88, 0x0a, 0xa0, 0x6e, 0xd2, 0x97, 0x68, 0x26, 0xf7, 0x96, 0xb2, 0x01, 0xdf, 0x31, 0x31, 0xa1, 0x27, 0xe8, 0xa3, 0x92, }, encryptedRTCPPacket: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0xc9, 0x8b, 0x8b, 0x5d, 0xf0, 0x39, 0x2a, 0x55, 0x85, 0x2b, 0x6c, 0x21, 0xac, 0x8e, 0x70, 0x25, 0xc5, 0x2c, 0x6f, 0xbe, 0xa2, 0xb3, 0xb4, 0x46, 0xea, 0x31, 0x12, 0x3b, 0xa8, 0x8c, 0xe6, 0x1e, 0x80, 0x00, 0x00, 0x01, }, encryptedRTPPacketWithMKI: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xc5, 0x00, 0x2e, 0xde, 0x04, 0xcf, 0xdd, 0x2e, 0xb9, 0x11, 0x59, 0xe0, 0x88, 0x0a, 0xa0, 0x6e, 0xd2, 0x97, 0x68, 0x26, 0xf7, 0x96, 0xb2, 0x01, 0xdf, 0x31, 0x31, 0xa1, 0x27, 0xe8, 0xa3, 0x92, 0x01, 0x02, 0x00, 0x04, }, encryptedRTCPPacketWithMKI: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0xc9, 0x8b, 0x8b, 0x5d, 0xf0, 0x39, 0x2a, 0x55, 0x85, 0x2b, 0x6c, 0x21, 0xac, 0x8e, 0x70, 0x25, 0xc5, 0x2c, 0x6f, 0xbe, 0xa2, 0xb3, 0xb4, 0x46, 0xea, 0x31, 0x12, 0x3b, 0xa8, 0x8c, 0xe6, 0x1e, 0x80, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x04, }, authenticatedRTPPacket: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x8b, 0xdd, 0xb6, 0x20, 0xb1, 0x0d, 0x2f, 0xe2, 0x76, 0xf7, 0xbd, 0xcf, 0xc5, 0xc3, 0x8a, 0xe5, }, authenticatedRTCPPacket: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x0c, 0xf6, 0x35, 0x16, 0x8f, 0x82, 0x42, 0xa2, 0x1b, 0x12, 0xd6, 0x64, 0xec, 0xd8, 0x62, 0xe8, 0x00, 0x00, 0x00, 0x01, }, authenticatedRTPPacketWithMKI: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x8b, 0xdd, 0xb6, 0x20, 0xb1, 0x0d, 0x2f, 0xe2, 0x76, 0xf7, 0xbd, 0xcf, 0xc5, 0xc3, 0x8a, 0xe5, 0x01, 0x02, 0x00, 0x04, }, authenticatedRTCPPacketWithMKI: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x0c, 0xf6, 0x35, 0x16, 0x8f, 0x82, 0x42, 0xa2, 0x1b, 0x12, 0xd6, 0x64, 0xec, 0xd8, 0x62, 0xe8, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x04, }, }, { //nolint:dupl profile: ProtectionProfileAeadAes256Gcm, encryptedRTPPacket: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xaf, 0x49, 0x96, 0x8f, 0x7e, 0x9c, 0x43, 0xf8, 0x01, 0xdd, 0x0c, 0x84, 0x8b, 0x1e, 0xc9, 0xb0, 0x29, 0xcd, 0xf8, 0x5c, 0xb7, 0x9a, 0x2f, 0x95, 0x60, 0xd4, 0x69, 0x75, 0x98, 0x50, 0x77, 0x25, }, encryptedRTCPPacket: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0x98, 0x22, 0xba, 0x22, 0x96, 0x1c, 0x31, 0x48, 0xe7, 0xb7, 0xec, 0x4f, 0x09, 0xf4, 0x26, 0xdc, 0xf6, 0xb5, 0x9a, 0x75, 0xad, 0xec, 0x74, 0xfd, 0xb9, 0x51, 0xb6, 0x66, 0x84, 0x24, 0xd4, 0xe2, 0x80, 0x00, 0x00, 0x01, }, encryptedRTPPacketWithMKI: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xaf, 0x49, 0x96, 0x8f, 0x7e, 0x9c, 0x43, 0xf8, 0x01, 0xdd, 0x0c, 0x84, 0x8b, 0x1e, 0xc9, 0xb0, 0x29, 0xcd, 0xf8, 0x5c, 0xb7, 0x9a, 0x2f, 0x95, 0x60, 0xd4, 0x69, 0x75, 0x98, 0x50, 0x77, 0x25, 0x01, 0x02, 0x00, 0x04, }, encryptedRTCPPacketWithMKI: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0x98, 0x22, 0xba, 0x22, 0x96, 0x1c, 0x31, 0x48, 0xe7, 0xb7, 0xec, 0x4f, 0x09, 0xf4, 0x26, 0xdc, 0xf6, 0xb5, 0x9a, 0x75, 0xad, 0xec, 0x74, 0xfd, 0xb9, 0x51, 0xb6, 0x66, 0x84, 0x24, 0xd4, 0xe2, 0x80, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x04, }, authenticatedRTPPacket: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x9c, 0x27, 0x45, 0xcc, 0xde, 0x31, 0xda, 0x1f, 0x03, 0xa5, 0x4c, 0xfd, 0xfa, 0xa2, 0x62, 0x8d, }, authenticatedRTCPPacket: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x22, 0x55, 0xdc, 0xaf, 0x86, 0x9a, 0xbb, 0x1c, 0xd0, 0x1a, 0xe8, 0x35, 0x4c, 0x94, 0x11, 0xee, 0x00, 0x00, 0x00, 0x01, }, authenticatedRTPPacketWithMKI: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x9c, 0x27, 0x45, 0xcc, 0xde, 0x31, 0xda, 0x1f, 0x03, 0xa5, 0x4c, 0xfd, 0xfa, 0xa2, 0x62, 0x8d, 0x01, 0x02, 0x00, 0x04, }, authenticatedRTCPPacketWithMKI: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x22, 0x55, 0xdc, 0xaf, 0x86, 0x9a, 0xbb, 0x1c, 0xd0, 0x1a, 0xe8, 0x35, 0x4c, 0x94, 0x11, 0xee, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x04, }, }, { //nolint:dupl profile: ProtectionProfileNullHmacSha1_32, encryptedRTPPacket: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xda, 0x9a, 0x3c, 0xa1, }, encryptedRTCPPacket: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x00, 0x00, 0x00, 0x01, 0xd2, 0xa2, 0x36, 0x2d, 0x01, 0x1b, 0x8c, 0xfc, 0x0a, 0xc9, }, encryptedRTPPacketWithMKI: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x01, 0x02, 0x00, 0x04, 0xda, 0x9a, 0x3c, 0xa1, }, encryptedRTCPPacketWithMKI: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x04, 0xd2, 0xa2, 0x36, 0x2d, 0x01, 0x1b, 0x8c, 0xfc, 0x0a, 0xc9, }, authenticatedRTPPacket: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xda, 0x9a, 0x3c, 0xa1, }, authenticatedRTCPPacket: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x00, 0x00, 0x00, 0x01, 0xd2, 0xa2, 0x36, 0x2d, 0x01, 0x1b, 0x8c, 0xfc, 0x0a, 0xc9, }, authenticatedRTPPacketWithMKI: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x01, 0x02, 0x00, 0x04, 0xda, 0x9a, 0x3c, 0xa1, }, authenticatedRTCPPacketWithMKI: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x04, 0xd2, 0xa2, 0x36, 0x2d, 0x01, 0x1b, 0x8c, 0xfc, 0x0a, 0xc9, }, }, { //nolint:dupl profile: ProtectionProfileNullHmacSha1_80, encryptedRTPPacket: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xda, 0x9a, 0x3c, 0xa1, 0xba, 0x8e, 0xfd, 0xd7, 0x07, 0xdc, }, encryptedRTCPPacket: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x00, 0x00, 0x00, 0x01, 0xd2, 0xa2, 0x36, 0x2d, 0x01, 0x1b, 0x8c, 0xfc, 0x0a, 0xc9, }, encryptedRTPPacketWithMKI: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x01, 0x02, 0x00, 0x04, 0xda, 0x9a, 0x3c, 0xa1, 0xba, 0x8e, 0xfd, 0xd7, 0x07, 0xdc, }, encryptedRTCPPacketWithMKI: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x04, 0xd2, 0xa2, 0x36, 0x2d, 0x01, 0x1b, 0x8c, 0xfc, 0x0a, 0xc9, }, authenticatedRTPPacket: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xda, 0x9a, 0x3c, 0xa1, 0xba, 0x8e, 0xfd, 0xd7, 0x07, 0xdc, }, authenticatedRTCPPacket: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x00, 0x00, 0x00, 0x01, 0xd2, 0xa2, 0x36, 0x2d, 0x01, 0x1b, 0x8c, 0xfc, 0x0a, 0xc9, }, authenticatedRTPPacketWithMKI: []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x01, 0x02, 0x00, 0x04, 0xda, 0x9a, 0x3c, 0xa1, 0xba, 0x8e, 0xfd, 0xd7, 0x07, 0xdc, }, authenticatedRTCPPacketWithMKI: []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0x00, 0x00, 0x00, 0x01, 0x01, 0x02, 0x00, 0x04, 0xd2, 0xa2, 0x36, 0x2d, 0x01, 0x1b, 0x8c, 0xfc, 0x0a, 0xc9, }, }, } masterKey := []byte{ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, } masterSalt := []byte{ 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, } mki := []byte{0x01, 0x02, 0x00, 0x04} decryptedRTPPacket := []byte{ 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, } decryptedRTCPPacket := []byte{ 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, } for key, v := range tests { keyLen, err := v.profile.KeyLen() assert.NoError(t, err) saltLen, err := v.profile.SaltLen() assert.NoError(t, err) tests[key].masterKey = masterKey[:keyLen] tests[key].masterSalt = masterSalt[:saltLen] tests[key].mki = mki tests[key].decryptedRTPPacket = decryptedRTPPacket tests[key].decryptedRTCPPacket = decryptedRTCPPacket } return tests } // nolint:maintidx,dupl func TestSrtpCipher(t *testing.T) { const testSSRC = 0xcafebabe for _, testCase := range createTestCiphers(t) { t.Run(testCase.profile.String(), func(t *testing.T) { assert.Equal(t, testCase.decryptedRTPPacket, testCase.authenticatedRTPPacket[:len(testCase.decryptedRTPPacket)]) assert.Equal(t, testCase.decryptedRTCPPacket, testCase.authenticatedRTCPPacket[:len(testCase.decryptedRTCPPacket)]) t.Run("Encrypt RTP", func(t *testing.T) { ctx, err := CreateContext(testCase.masterKey, testCase.masterSalt, testCase.profile) assert.NoError(t, err) t.Run("New Allocation", func(t *testing.T) { actualEncrypted, err := ctx.EncryptRTP(nil, testCase.decryptedRTPPacket, nil) assert.NoError(t, err) assert.Equal(t, testCase.encryptedRTPPacket, actualEncrypted) }) t.Run("Same buffer", func(t *testing.T) { buffer := make([]byte, 0, 1000) src, dst := buffer, buffer src = append(src, testCase.decryptedRTPPacket...) assert.True(t, isSameBuffer(dst, src)) actualEncrypted, err := ctx.EncryptRTP(dst, src, nil) assert.NoError(t, err) assert.Equal(t, testCase.encryptedRTPPacket, actualEncrypted) assert.True(t, isSameBuffer(actualEncrypted, src)) }) }) t.Run("Decrypt RTP", func(t *testing.T) { ctx, err := CreateContext(testCase.masterKey, testCase.masterSalt, testCase.profile) assert.NoError(t, err) t.Run("New Allocation", func(t *testing.T) { actualDecrypted, err := ctx.DecryptRTP(nil, testCase.encryptedRTPPacket, nil) assert.NoError(t, err) assert.Equal(t, testCase.decryptedRTPPacket, actualDecrypted) }) t.Run("Same buffer", func(t *testing.T) { buffer := make([]byte, 0, 1000) src, dst := buffer, buffer src = append(src, testCase.encryptedRTPPacket...) assert.True(t, isSameBuffer(dst, src)) actualDecrypted, err := ctx.DecryptRTP(dst, src, nil) assert.NoError(t, err) assert.Equal(t, testCase.decryptedRTPPacket, actualDecrypted) assert.True(t, isSameBuffer(actualDecrypted, src)) }) }) t.Run("Encrypt RTCP", func(t *testing.T) { ctx, err := CreateContext(testCase.masterKey, testCase.masterSalt, testCase.profile) assert.NoError(t, err) t.Run("New Allocation", func(t *testing.T) { ctx.SetIndex(testSSRC, 0) actualEncrypted, err := ctx.EncryptRTCP(nil, testCase.decryptedRTCPPacket, nil) assert.NoError(t, err) assert.Equal(t, testCase.encryptedRTCPPacket, actualEncrypted) }) t.Run("Same buffer", func(t *testing.T) { ctx.SetIndex(testSSRC, 0) buffer := make([]byte, 0, 1000) src, dst := buffer, buffer src = append(src, testCase.decryptedRTCPPacket...) assert.True(t, isSameBuffer(dst, src)) actualEncrypted, err := ctx.EncryptRTCP(dst, src, nil) assert.NoError(t, err) assert.Equal(t, testCase.encryptedRTCPPacket, actualEncrypted) assert.True(t, isSameBuffer(actualEncrypted, src)) }) }) t.Run("Decrypt RTCP", func(t *testing.T) { ctx, err := CreateContext(testCase.masterKey, testCase.masterSalt, testCase.profile) assert.NoError(t, err) t.Run("New Allocation", func(t *testing.T) { ctx.SetIndex(testSSRC, 0) actualDecrypted, err := ctx.DecryptRTCP(nil, testCase.encryptedRTCPPacket, nil) assert.NoError(t, err) assert.Equal(t, testCase.decryptedRTCPPacket, actualDecrypted) }) t.Run("Same buffer", func(t *testing.T) { ctx.SetIndex(testSSRC, 0) buffer := make([]byte, 0, 1000) src, dst := buffer, buffer src = append(src, testCase.encryptedRTCPPacket...) assert.True(t, isSameBuffer(dst, src)) actualDecrypted, err := ctx.DecryptRTCP(dst, src, nil) assert.NoError(t, err) assert.Equal(t, testCase.decryptedRTCPPacket, actualDecrypted) assert.True(t, isSameBuffer(actualDecrypted, src)) }) }) t.Run("Encrypt RTP with MKI", func(t *testing.T) { ctx, err := CreateContext( testCase.masterKey, testCase.masterSalt, testCase.profile, MasterKeyIndicator(testCase.mki), ) assert.NoError(t, err) t.Run("New Allocation", func(t *testing.T) { actualEncrypted, err := ctx.EncryptRTP(nil, testCase.decryptedRTPPacket, nil) assert.NoError(t, err) assert.Equal(t, testCase.encryptedRTPPacketWithMKI, actualEncrypted) }) }) t.Run("Decrypt RTP with MKI", func(t *testing.T) { ctx, err := CreateContext( testCase.masterKey, testCase.masterSalt, testCase.profile, MasterKeyIndicator(testCase.mki), ) assert.NoError(t, err) t.Run("New Allocation", func(t *testing.T) { actualDecrypted, err := ctx.DecryptRTP(nil, testCase.encryptedRTPPacketWithMKI, nil) assert.NoError(t, err) assert.Equal(t, testCase.decryptedRTPPacket, actualDecrypted) }) }) t.Run("Encrypt RTCP with MKI", func(t *testing.T) { ctx, err := CreateContext( testCase.masterKey, testCase.masterSalt, testCase.profile, MasterKeyIndicator(testCase.mki), ) assert.NoError(t, err) t.Run("New Allocation", func(t *testing.T) { actualEncrypted, err := ctx.EncryptRTCP(nil, testCase.decryptedRTCPPacket, nil) assert.NoError(t, err) assert.Equal(t, testCase.encryptedRTCPPacketWithMKI, actualEncrypted) }) }) t.Run("Decrypt RTCP with MKI", func(t *testing.T) { ctx, err := CreateContext( testCase.masterKey, testCase.masterSalt, testCase.profile, MasterKeyIndicator(testCase.mki), ) assert.NoError(t, err) t.Run("New Allocation", func(t *testing.T) { actualDecrypted, err := ctx.DecryptRTCP(nil, testCase.encryptedRTCPPacketWithMKI, nil) assert.NoError(t, err) assert.Equal(t, testCase.decryptedRTCPPacket, actualDecrypted) }) }) t.Run("Encrypt RTP with NULL cipher", func(t *testing.T) { ctx, err := CreateContext( testCase.masterKey, testCase.masterSalt, testCase.profile, SRTPNoEncryption(), SRTCPNoEncryption(), ) assert.NoError(t, err) t.Run("New Allocation", func(t *testing.T) { actualEncrypted, err := ctx.EncryptRTP(nil, testCase.decryptedRTPPacket, nil) assert.NoError(t, err) assert.Equal(t, testCase.decryptedRTPPacket, actualEncrypted[:len(testCase.decryptedRTPPacket)]) assert.Equal(t, testCase.authenticatedRTPPacket, actualEncrypted) }) }) t.Run("Decrypt RTP with NULL cipher", func(t *testing.T) { ctx, err := CreateContext( testCase.masterKey, testCase.masterSalt, testCase.profile, SRTPNoEncryption(), SRTCPNoEncryption(), ) assert.NoError(t, err) t.Run("New Allocation", func(t *testing.T) { actualDecrypted, err := ctx.DecryptRTP(nil, testCase.authenticatedRTPPacket, nil) assert.NoError(t, err) assert.Equal(t, testCase.decryptedRTPPacket, actualDecrypted) }) }) t.Run("Encrypt RTCP with NULL cipher", func(t *testing.T) { ctx, err := CreateContext( testCase.masterKey, testCase.masterSalt, testCase.profile, SRTPNoEncryption(), SRTCPNoEncryption(), ) assert.NoError(t, err) t.Run("New Allocation", func(t *testing.T) { actualEncrypted, err := ctx.EncryptRTCP(nil, testCase.decryptedRTCPPacket, nil) assert.NoError(t, err) assert.Equal(t, testCase.decryptedRTCPPacket, actualEncrypted[:len(testCase.decryptedRTCPPacket)]) assert.Equal(t, testCase.authenticatedRTCPPacket, actualEncrypted) }) }) t.Run("Decrypt RTCP with NULL cipher", func(t *testing.T) { ctx, err := CreateContext( testCase.masterKey, testCase.masterSalt, testCase.profile, SRTPNoEncryption(), SRTCPNoEncryption(), ) assert.NoError(t, err) t.Run("New Allocation", func(t *testing.T) { actualDecrypted, err := ctx.DecryptRTCP(nil, testCase.authenticatedRTCPPacket, nil) assert.NoError(t, err) assert.Equal(t, testCase.decryptedRTCPPacket, actualDecrypted) }) }) t.Run("Encrypt RTP with NULL cipher and MKI", func(t *testing.T) { ctx, err := CreateContext( testCase.masterKey, testCase.masterSalt, testCase.profile, SRTPNoEncryption(), SRTCPNoEncryption(), MasterKeyIndicator(testCase.mki), ) assert.NoError(t, err) t.Run("New Allocation", func(t *testing.T) { actualEncrypted, err := ctx.EncryptRTP(nil, testCase.decryptedRTPPacket, nil) assert.NoError(t, err) assert.Equal(t, testCase.decryptedRTPPacket, actualEncrypted[:len(testCase.decryptedRTPPacket)]) assert.Equal(t, testCase.authenticatedRTPPacketWithMKI, actualEncrypted) }) }) t.Run("Decrypt RTP with NULL cipher and MKI", func(t *testing.T) { ctx, err := CreateContext( testCase.masterKey, testCase.masterSalt, testCase.profile, SRTPNoEncryption(), SRTCPNoEncryption(), MasterKeyIndicator(testCase.mki), ) assert.NoError(t, err) t.Run("New Allocation", func(t *testing.T) { actualDecrypted, err := ctx.DecryptRTP(nil, testCase.authenticatedRTPPacketWithMKI, nil) assert.NoError(t, err) assert.Equal(t, testCase.decryptedRTPPacket, actualDecrypted) }) }) t.Run("Encrypt RTCP with NULL cipher and MKI", func(t *testing.T) { ctx, err := CreateContext( testCase.masterKey, testCase.masterSalt, testCase.profile, SRTPNoEncryption(), SRTCPNoEncryption(), MasterKeyIndicator(testCase.mki), ) assert.NoError(t, err) t.Run("New Allocation", func(t *testing.T) { actualEncrypted, err := ctx.EncryptRTCP(nil, testCase.decryptedRTCPPacket, nil) assert.NoError(t, err) assert.Equal(t, testCase.decryptedRTCPPacket, actualEncrypted[:len(testCase.decryptedRTCPPacket)]) assert.Equal(t, testCase.authenticatedRTCPPacketWithMKI, actualEncrypted) }) }) t.Run("Decrypt RTCP with NULL cipher and MKI", func(t *testing.T) { ctx, err := CreateContext( testCase.masterKey, testCase.masterSalt, testCase.profile, SRTPNoEncryption(), SRTCPNoEncryption(), MasterKeyIndicator(testCase.mki), ) assert.NoError(t, err) t.Run("New Allocation", func(t *testing.T) { actualDecrypted, err := ctx.DecryptRTCP(nil, testCase.authenticatedRTCPPacketWithMKI, nil) assert.NoError(t, err) assert.Equal(t, testCase.decryptedRTCPPacket, actualDecrypted) }) }) srtpAuthTagLen, err := testCase.profile.AuthTagRTPLen() assert.NoError(t, err) if srtpAuthTagLen != 0 { t.Run("Encrypt RTP with changed RTP auth tag len", func(t *testing.T) { ctx, err := CreateContext(testCase.masterKey, testCase.masterSalt, testCase.profile, SRTPAuthenticationTagLength(srtpAuthTagLen-2)) assert.NoError(t, err) t.Run("New Allocation", func(t *testing.T) { actualEncrypted, err := ctx.EncryptRTP(nil, testCase.decryptedRTPPacket, nil) assert.NoError(t, err) assert.Equal(t, testCase.encryptedRTPPacket[:len(testCase.encryptedRTPPacket)-2], actualEncrypted) }) }) t.Run("Decrypt RTP with changed RTP auth tag len", func(t *testing.T) { ctx, err := CreateContext(testCase.masterKey, testCase.masterSalt, testCase.profile, SRTPAuthenticationTagLength(srtpAuthTagLen-2)) assert.NoError(t, err) t.Run("New Allocation", func(t *testing.T) { actualDecrypted, err := ctx.DecryptRTP(nil, testCase.encryptedRTPPacket[:len(testCase.encryptedRTPPacket)-2], nil) assert.NoError(t, err) assert.Equal(t, testCase.decryptedRTPPacket, actualDecrypted) }) }) } }) } } srtp-3.0.9/srtp_cipher_utils_test.go000066400000000000000000000060431511235350500176460ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "crypto/aes" "crypto/cipher" "crypto/hmac" "crypto/sha1" // nolint:gosec ) // deriveSessionKeys should be used in tests only. // RFCs test vectors specifes derived keys to use, // this struct is used to inject them into the cipher in tests. type derivedSessionKeys struct { srtpSessionKey []byte srtpSessionSalt []byte srtpSessionAuthTag []byte srtcpSessionKey []byte srtcpSessionSalt []byte srtcpSessionAuthTag []byte } func newSrtpCipherAesCmHmacSha1WithDerivedKeys( profile ProtectionProfile, keys derivedSessionKeys, encryptSRTP, encryptSRTCP bool, ) (*srtpCipherAesCmHmacSha1, error) { if profile == ProtectionProfileNullHmacSha1_80 || profile == ProtectionProfileNullHmacSha1_32 { encryptSRTP = false encryptSRTCP = false } srtpCipher := &srtpCipherAesCmHmacSha1{ protectionProfileWithArgs: protectionProfileWithArgs{ProtectionProfile: profile}, srtpEncrypted: encryptSRTP, srtcpEncrypted: encryptSRTCP, } var err error if srtpCipher.srtpBlock, err = aes.NewCipher(keys.srtpSessionKey); err != nil { return nil, err } if srtpCipher.srtcpBlock, err = aes.NewCipher(keys.srtcpSessionKey); err != nil { return nil, err } srtpCipher.srtpSessionSalt = keys.srtpSessionSalt srtpCipher.srtcpSessionSalt = keys.srtcpSessionSalt srtpCipher.srtcpSessionAuth = hmac.New(sha1.New, keys.srtcpSessionAuthTag) srtpCipher.srtpSessionAuth = hmac.New(sha1.New, keys.srtpSessionAuthTag) return srtpCipher, nil } func newSrtpCipherAeadAesGcmWithDerivedKeys( profile ProtectionProfile, keys derivedSessionKeys, encryptSRTP, encryptSRTCP bool, ) (*srtpCipherAeadAesGcm, error) { srtpCipher := &srtpCipherAeadAesGcm{ protectionProfileWithArgs: protectionProfileWithArgs{ProtectionProfile: profile}, srtpEncrypted: encryptSRTP, srtcpEncrypted: encryptSRTCP, } srtpBlock, err := aes.NewCipher(keys.srtpSessionKey) if err != nil { return nil, err } srtpCipher.srtpCipher, err = cipher.NewGCM(srtpBlock) if err != nil { return nil, err } srtcpBlock, err := aes.NewCipher(keys.srtcpSessionKey) if err != nil { return nil, err } srtpCipher.srtcpCipher, err = cipher.NewGCM(srtcpBlock) if err != nil { return nil, err } srtpCipher.srtpSessionSalt = keys.srtpSessionSalt srtpCipher.srtcpSessionSalt = keys.srtcpSessionSalt return srtpCipher, nil } // createContextWithCipher creates a new SRTP Context with a pre-created cipher. This is used for testing purposes only. func createContextWithCipher(profile ProtectionProfile, cipher srtpCipher) (*Context, error) { ctx := &Context{ srtpSSRCStates: map[uint32]*srtpSSRCState{}, srtcpSSRCStates: map[uint32]*srtcpSSRCState{}, profile: profile, mkis: map[string]srtpCipher{}, cipher: cipher, } err := SRTPNoReplayProtection()(ctx) if err != nil { return nil, err } err = SRTCPNoReplayProtection()(ctx) if err != nil { return nil, err } return ctx, nil } srtp-3.0.9/srtp_cryptex.go000066400000000000000000000164001511235350500156110ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "encoding/binary" "github.com/pion/rtp" ) /* RFC 9335: Completely Encrypting RTP Header Extensions and Contributing Sources Section 6.2. Encryption Procedure When this mechanism [Cryptex] is active, the SRTP packet is protected as follows: 0 1 2 3 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+<+ |V=2|P|X| CC |M| PT | sequence number | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | timestamp | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | synchronization source (SSRC) identifier | | +>+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ | | | contributing source (CSRC) identifiers | | | | .... | | +>+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | X | 0xC0 or 0xC2 | 0xDE | length | | +>+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | RFC 8285 header extensions | | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | payload ... | | | | +-------------------------------+ | | | | RTP padding | RTP pad count | | +>+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+<+ | ~ SRTP Master Key Identifier (MKI) (OPTIONAL) ~ | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | : authentication tag (RECOMMENDED) : | | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | | | +- Encrypted Portion Authenticated Portion ---+ Figure 1: A Protected SRTP Packet Note that, as required by [RFC8285], the 4 bytes at the start of the extension block are not encrypted. Specifically, the Encrypted Portion MUST include any CSRC identifiers, any RTP header extension (except for the first 4 bytes), and the RTP payload. */ const ( minSrtpHeaderSize = 12 // Minimum size of the SRTP header (12 bytes for RTP header without CSRCs and extensions) extensionHeaderSize = 4 // Size of the header extension (4 bytes for profile and length fields) ) func isCryptexPacket(header *rtp.Header) bool { return header.Extension && (header.ExtensionProfile == rtp.CryptexProfileOneByte || header.ExtensionProfile == rtp.CryptexProfileTwoByte) } func moveHeaderExtensionBeforeCSRCs(header *rtp.Header, buf []byte) { if len(header.CSRC) == 0 || !header.Extension { return } var tmp [extensionHeaderSize]byte csrcLen := len(header.CSRC) * 4 copy(tmp[:], buf[minSrtpHeaderSize+csrcLen:minSrtpHeaderSize+csrcLen+extensionHeaderSize]) copy(buf[minSrtpHeaderSize+extensionHeaderSize:], buf[minSrtpHeaderSize:minSrtpHeaderSize+csrcLen]) copy(buf[minSrtpHeaderSize:], tmp[:]) } func moveCSRCsBeforeHeaderExtension(header *rtp.Header, buf []byte) { if len(header.CSRC) == 0 || !header.Extension { return } var tmp [extensionHeaderSize]byte csrcLen := len(header.CSRC) * 4 copy(tmp[:], buf[minSrtpHeaderSize:minSrtpHeaderSize+extensionHeaderSize]) copy(buf[minSrtpHeaderSize:], buf[minSrtpHeaderSize+extensionHeaderSize:minSrtpHeaderSize+csrcLen+extensionHeaderSize]) copy(buf[minSrtpHeaderSize+csrcLen:], tmp[:]) } func encryptCryptexRTP(dst, plaintext []byte, sameBuffer bool, header *rtp.Header, encrypt func(dst, plaintext []byte, headerLen int) error, ) error { moveHeaderExtensionBeforeCSRCs(header, plaintext) // Update Header Extension Profile to Cryptex one if header.ExtensionProfile == rtp.ExtensionProfileOneByte { binary.BigEndian.PutUint16(plaintext[minSrtpHeaderSize:], rtp.CryptexProfileOneByte) } else { binary.BigEndian.PutUint16(plaintext[minSrtpHeaderSize:], rtp.CryptexProfileTwoByte) } err := encrypt(dst, plaintext, minSrtpHeaderSize+extensionHeaderSize) if err != nil { binary.BigEndian.PutUint16(plaintext[minSrtpHeaderSize:], header.ExtensionProfile) moveCSRCsBeforeHeaderExtension(header, plaintext) return err } if !sameBuffer { copy(dst, plaintext[:minSrtpHeaderSize+extensionHeaderSize]) binary.BigEndian.PutUint16(plaintext[minSrtpHeaderSize:], header.ExtensionProfile) moveCSRCsBeforeHeaderExtension(header, plaintext) } moveCSRCsBeforeHeaderExtension(header, dst) return nil } func decryptCryptexRTP(dst, ciphertext []byte, sameBuffer bool, header *rtp.Header, headerLen int, decrypt func(dst, ciphertext []byte, headerLen int) error, ) error { moveHeaderExtensionBeforeCSRCs(header, ciphertext) err := decrypt(dst, ciphertext, minSrtpHeaderSize+extensionHeaderSize) if err != nil { moveCSRCsBeforeHeaderExtension(header, ciphertext) return err } if !sameBuffer { copy(dst, ciphertext[:minSrtpHeaderSize+extensionHeaderSize]) moveCSRCsBeforeHeaderExtension(header, dst) } moveCSRCsBeforeHeaderExtension(header, ciphertext) // Update Header Extension Profile offset := minSrtpHeaderSize + len(header.CSRC)*4 if header.ExtensionProfile == rtp.CryptexProfileOneByte { binary.BigEndian.PutUint16(dst[offset:], rtp.ExtensionProfileOneByte) } else { binary.BigEndian.PutUint16(dst[offset:], rtp.ExtensionProfileTwoByte) } // Unmarshal decrypted header extension. n, err := header.Unmarshal(dst) if err != nil { return err } if n != headerLen { return errHeaderLengthMismatch } return nil } // RFC 9335, section 5.1: If the packet contains CSRCs but no header extensions, an empty extension block // consisting of the 0xC0DE tag and a 16-bit length field set to zero (explicitly permitted by [RFC3550]) // MUST be appended, and the X bit MUST be set to 1 to indicate an extension block is present. func needsEmptyExtensionHeader(useCryptex bool, header *rtp.Header) bool { return useCryptex && len(header.CSRC) > 0 && !header.Extension } // insertEmptyExtensionHeader inserts an empty extension header into the RTP packet. It assumes that the dst is big // enough to hold extra data. func insertEmptyExtensionHeader(dst, plaintext []byte, sameBuffer bool, header *rtp.Header) []byte { header.Extension = true header.ExtensionProfile = rtp.ExtensionProfileOneByte header.Extensions = nil var emptyExtHdr [extensionHeaderSize]byte binary.BigEndian.PutUint16(emptyExtHdr[:], rtp.ExtensionProfileOneByte) offset := minSrtpHeaderSize + len(header.CSRC)*4 plaintextLen := len(plaintext) if sameBuffer { plaintext = plaintext[:plaintextLen+extensionHeaderSize] copy(plaintext[offset+extensionHeaderSize:], plaintext[offset:plaintextLen]) copy(plaintext[offset:], emptyExtHdr[:]) } else { newPlaintext := dst[:plaintextLen+extensionHeaderSize] copy(newPlaintext, plaintext[:offset]) copy(newPlaintext[offset:], emptyExtHdr[:]) copy(newPlaintext[offset+extensionHeaderSize:], plaintext[offset:plaintextLen]) plaintext = newPlaintext } plaintext[0] |= 0x10 // Set the X bit to indicate an extension block is present return plaintext } srtp-3.0.9/srtp_cryptex_test.go000066400000000000000000000362621511235350500166600ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "slices" "testing" "github.com/pion/rtp" "github.com/stretchr/testify/assert" ) type testRfcCryptex struct { profile ProtectionProfile masterKey []byte masterSalt []byte scenarios map[cryptexScenario]testRfcCryptexScenario } type testRfcCryptexScenario struct { decrypted []byte encrypted []byte } type cryptexScenario int const ( cryptexScenarioOneByteExt cryptexScenario = iota cryptexScenarioTwoByteExt cryptexScenarioOneByteExtAndCsrc cryptexScenarioTwoByteExtAndCsrc cryptexScenarioEmptyOneByteExtAndCsrc cryptexScenarioEmptyTwoByteExtAndCsrc ) // nolint:lll func createRfcCryptexTestCiphers(t *testing.T) []testRfcCryptex { t.Helper() // Test Vectors from RFC 9335, Appendix A return []testRfcCryptex{ // A.1. AES-CTR { profile: ProtectionProfileAes128CmHmacSha1_80, masterKey: fromHex(t, `e1f97a0d3e018be0d64fa32c06de4139`), masterSalt: fromHex(t, `0ec675ad498afeebb6960b3aabe6`), scenarios: map[cryptexScenario]testRfcCryptexScenario{ // A.1.1. RTP Packet with One-Byte Header Extension cryptexScenarioOneByteExt: { decrypted: fromHex(t, `900f1235decafbadcafebabebede000151000200abababababababababababababababab`), encrypted: fromHex(t, `900f1235decafbadcafebabec0de0001eb92365251c3e036f8de27e9c27ee3e0b4651d9fbc4218a70244522f34a5`), }, // A.1.2. RTP Packet with Two-Byte Header Extension cryptexScenarioTwoByteExt: { decrypted: fromHex(t, `900f1236decafbadcafebabe1000000105020002abababababababababababababababab`), encrypted: fromHex(t, `900f1236decafbadcafebabec2de00014ed9cc4e6a712b3096c5ca77339d4204ce0d77396cab69585fbce38194a5`), }, // A.1.3. RTP Packet with One-Byte Header Extension and CSRC Fields cryptexScenarioOneByteExtAndCsrc: { decrypted: fromHex(t, `920f1238decafbadcafebabe0001e2400000b26ebede000151000200abababababababababababababababab`), encrypted: fromHex(t, `920f1238decafbadcafebabe8bb6e12b5cff16ddc0de000192838c8c09e58393e1de3a9a74734d6745671338c3acf11da2df8423bee0`), }, // A.1.4. RTP Packet with Two-Byte Header Extension and CSRC Fields cryptexScenarioTwoByteExtAndCsrc: { decrypted: fromHex(t, `920f1239decafbadcafebabe0001e2400000b26e1000000105020002abababababababababababababababab`), encrypted: fromHex(t, `920f1239decafbadcafebabef70e513eb90b9b25c2de0001bbed4848faa644665f3d7f34125914e9f4d0ae923c6f479b95a0f7b53133`), }, // A.1.5. RTP Packet with Empty One-Byte Header Extension and CSRC Fields cryptexScenarioEmptyOneByteExtAndCsrc: { decrypted: fromHex(t, `920f123adecafbadcafebabe0001e2400000b26ebede0000abababababababababababababababab`), encrypted: fromHex(t, `920f123adecafbadcafebabe7130b6abfe2ab0e3c0de0000e3d9f64b25c9e74cb4cf8e43fb92e3781c2c0ceab6b3a499a14c`), }, // A.1.6. RTP Packet with Empty Two-Byte Header Extension and CSRC Fields cryptexScenarioEmptyTwoByteExtAndCsrc: { decrypted: fromHex(t, `920f123bdecafbadcafebabe0001e2400000b26e10000000abababababababababababababababab`), encrypted: fromHex(t, `920f123bdecafbadcafebabecbf24c124330e1c8c2de0000599dd45bc9d687b603e8b59d771fd38e88b170e0cd31e125eabe`), }, }, }, // A.2. AES-GCM { profile: ProtectionProfileAeadAes128Gcm, masterKey: fromHex(t, `000102030405060708090a0b0c0d0e0f`), masterSalt: fromHex(t, `a0a1a2a3a4a5a6a7a8a9aaab`), scenarios: map[cryptexScenario]testRfcCryptexScenario{ // A.2.1. RTP Packet with One-Byte Header Extension cryptexScenarioOneByteExt: { decrypted: fromHex(t, `900f1235decafbadcafebabebede000151000200abababababababababababababababab`), encrypted: fromHex(t, `900f1235decafbadcafebabec0de000139972dc9572c4d99e8fc355de743fb2e94f9d8ff54e72f4193bbc5c74ffab0fa9fa0fbeb`), }, // A.2.2. RTP Packet with Two-Byte Header Extension cryptexScenarioTwoByteExt: { decrypted: fromHex(t, `900f1236decafbadcafebabe1000000105020002abababababababababababababababab`), encrypted: fromHex(t, `900f1236decafbadcafebabec2de0001bb75a4c545cd1f413bdb7daa2b1e3263de313667c963249081b35a65f5cb6c88b394235f`), }, // A.2.3. RTP Packet with One-Byte Header Extension and CSRC Fields cryptexScenarioOneByteExtAndCsrc: { decrypted: fromHex(t, `920f1238decafbadcafebabe0001e2400000b26ebede000151000200abababababababababababababababab`), encrypted: fromHex(t, `920f1238decafbadcafebabe63bbccc4a7f695c4c0de00018ad7c71fac70a80c92866b4c6ba98546ef913586e95ffaaffe956885bb0647a8bc094ac8`), }, // A.2.4. RTP Packet with Two-Byte Header Extension and CSRC Fields cryptexScenarioTwoByteExtAndCsrc: { decrypted: fromHex(t, `920f1239decafbadcafebabe0001e2400000b26e1000000105020002abababababababababababababababab`), encrypted: fromHex(t, `920f1239decafbadcafebabe3680524f8d312b00c2de0001c78d120038422bc111a7187a18246f980c059cc6bc9df8b626394eca344e4b05d80fea83`), }, // A.2.5. RTP Packet with Empty One-Byte Header Extension and CSRC Fields cryptexScenarioEmptyOneByteExtAndCsrc: { decrypted: fromHex(t, `920f123adecafbadcafebabe0001e2400000b26ebede0000abababababababababababababababab`), encrypted: fromHex(t, `920f123adecafbadcafebabe15b6bb4337906fffc0de0000b7b964537a2b03ab7ba5389ce93317126b5d974df30c6884dcb651c5e120c1da`), }, // A.2.6. RTP Packet with Empty Two-Byte Header Extension and CSRC Fields cryptexScenarioEmptyTwoByteExtAndCsrc: { decrypted: fromHex(t, `920f123bdecafbadcafebabe0001e2400000b26e10000000abababababababababababababababab`), encrypted: fromHex(t, `920f123bdecafbadcafebabedcb38c9e48bf95f4c2de000061ee432cf920317076613258d3ce4236c06ac429681ad08413512dc98b5207d8`), }, }, }, } } func TestCryptexRFC(t *testing.T) { cryptexScenarioNames := map[cryptexScenario]string{ cryptexScenarioOneByteExt: "One-Byte Header Extension", cryptexScenarioTwoByteExt: "Two-Byte Header Extension", cryptexScenarioOneByteExtAndCsrc: "One-Byte Header Extension and CSRC Fields", cryptexScenarioTwoByteExtAndCsrc: "Two-Byte Header Extension and CSRC Fields", cryptexScenarioEmptyOneByteExtAndCsrc: "Empty One-Byte Header Extension and CSRC Fields", cryptexScenarioEmptyTwoByteExtAndCsrc: "Empty Two-Byte Header Extension and CSRC Fields", } // Test using test vectors from RFC 9335, Appendix A for _, profile := range createRfcCryptexTestCiphers(t) { t.Run(profile.profile.String(), func(t *testing.T) { for scenario, data := range profile.scenarios { t.Run(cryptexScenarioNames[scenario], func(t *testing.T) { t.Run("Encrypt RTP", func(t *testing.T) { ctx, err := CreateContext(profile.masterKey, profile.masterSalt, profile.profile, Cryptex(CryptexModeEnabled)) assert.NoError(t, err) t.Run("New Allocation", func(t *testing.T) { var header rtp.Header decrypted := slices.Clone(data.decrypted) actualEncrypted, err := ctx.EncryptRTP(nil, decrypted, &header) assert.NoError(t, err) assert.Equal(t, data.encrypted, actualEncrypted) assert.Equal(t, decrypted, data.decrypted, "The decrypted packet should not be modified during encryption") }) t.Run("Same buffer", func(t *testing.T) { buffer := make([]byte, 0, 1000) src, dst := buffer, buffer src = append(src, data.decrypted...) assert.True(t, isSameBuffer(dst, src)) var header rtp.Header actualEncrypted, err := ctx.EncryptRTP(dst, src, &header) assert.NoError(t, err) assert.Equal(t, data.encrypted, actualEncrypted) assert.True(t, isSameBuffer(actualEncrypted, src)) }) }) t.Run("Decrypt RTP", func(t *testing.T) { ctx, err := CreateContext(profile.masterKey, profile.masterSalt, profile.profile, Cryptex(CryptexModeEnabled)) assert.NoError(t, err) t.Run("New Allocation", func(t *testing.T) { var header rtp.Header encrypted := slices.Clone(data.encrypted) actualDecrypted, err := ctx.DecryptRTP(nil, encrypted, &header) assert.NoError(t, err) assert.Equal(t, data.decrypted, actualDecrypted) assert.Equal(t, encrypted, data.encrypted, "The encrypted packet should not be modified during decryption") assert.True(t, header.Extension, "Header should have an extension") if int(scenario)%2 == 0 { assert.Equal(t, uint16(rtp.ExtensionProfileOneByte), header.ExtensionProfile, "Header should have a one-byte extension profile") } else { assert.Equal(t, uint16(rtp.ExtensionProfileTwoByte), header.ExtensionProfile, "Header should have a two-byte extension profile") } }) t.Run("Same buffer", func(t *testing.T) { buffer := make([]byte, 0, 1000) src, dst := buffer, buffer src = append(src, data.encrypted...) assert.True(t, isSameBuffer(dst, src)) var header rtp.Header actualDecrypted, err := ctx.DecryptRTP(dst, src, &header) assert.NoError(t, err) assert.Equal(t, data.decrypted, actualDecrypted) assert.True(t, header.Extension, "Header should have an extension") if int(scenario)%2 == 0 { assert.Equal(t, uint16(rtp.ExtensionProfileOneByte), header.ExtensionProfile, "Header should have a one-byte extension profile") } else { assert.Equal(t, uint16(rtp.ExtensionProfileTwoByte), header.ExtensionProfile, "Header should have a two-byte extension profile") } assert.True(t, isSameBuffer(actualDecrypted, src)) }) }) if scenario == cryptexScenarioEmptyOneByteExtAndCsrc { t.Run("Encrypt RTP with CSRCs and no ExtHdr", func(t *testing.T) { // When RTP packet has CSRCs but no header extension, we should add empty one-byte header // extension. Remove the header extension from the decrypted packet to simulate this. var rtpPacket rtp.Packet err := rtpPacket.Unmarshal(data.decrypted) assert.NoError(t, err) rtpPacket.Header.Extension = false rtpPacket.Header.ExtensionProfile = 0 rtpPacket.Header.Extensions = nil rtpBytes, err := rtpPacket.Marshal() assert.NoError(t, err) ctx, err := CreateContext(profile.masterKey, profile.masterSalt, profile.profile, Cryptex(CryptexModeEnabled)) assert.NoError(t, err) t.Run("New Allocation", func(t *testing.T) { var header rtp.Header decrypted := slices.Clone(rtpBytes) actualEncrypted, err := ctx.EncryptRTP(nil, decrypted, &header) assert.NoError(t, err) assert.Equal(t, data.encrypted, actualEncrypted) assert.Equal(t, decrypted, rtpBytes, "The decrypted packet should not be modified during encryption") }) t.Run("Same buffer", func(t *testing.T) { buffer := make([]byte, 0, 1000) src, dst := buffer, buffer src = append(src, rtpBytes...) assert.True(t, isSameBuffer(dst, src)) var header rtp.Header actualEncrypted, err := ctx.EncryptRTP(dst, src, &header) assert.NoError(t, err) assert.Equal(t, data.encrypted, actualEncrypted) assert.True(t, isSameBuffer(actualEncrypted, src)) }) }) } }) } }) } } func TestCryptexUnsupportedHeaderExtension(t *testing.T) { // Test that Cryptex does not support header extensions other than one-byte and two-byte. ctx, err := CreateContext(make([]byte, 16), make([]byte, 14), ProtectionProfileAes128CmHmacSha1_80, Cryptex(CryptexModeEnabled)) assert.NoError(t, err) rtpPacket := rtp.Packet{ Header: rtp.Header{ Version: 2, Extension: true, ExtensionProfile: 1, }, } err = rtpPacket.Header.SetExtension(0, []byte{0x01, 0x02, 0x03, 0x04}) assert.NoError(t, err) rtpBytes, err := rtpPacket.Marshal() assert.NoError(t, err) _, err = ctx.EncryptRTP(nil, rtpBytes, nil) assert.ErrorIs(t, err, errUnsupportedHeaderExtension) } func TestCryptexModes(t *testing.T) { rtpPacket := rtp.Packet{ Header: rtp.Header{ Version: 2, Extension: true, ExtensionProfile: rtp.ExtensionProfileOneByte, }, } err := rtpPacket.Header.SetExtension(1, []byte{0x01, 0x02, 0x03, 0x04}) assert.NoError(t, err) rtpBytes, err := rtpPacket.Marshal() assert.NoError(t, err) createCtx := func(t *testing.T, profile ProtectionProfile, opts ...ContextOption) *Context { t.Helper() keyLen, _ := profile.KeyLen() saltLen, _ := profile.SaltLen() ctx, err := CreateContext(make([]byte, keyLen), make([]byte, saltLen), profile, opts...) assert.NoError(t, err) return ctx } profiles := []ProtectionProfile{ ProtectionProfileAes128CmHmacSha1_32, ProtectionProfileAes128CmHmacSha1_80, ProtectionProfileAes256CmHmacSha1_32, ProtectionProfileAes256CmHmacSha1_80, ProtectionProfileAeadAes128Gcm, ProtectionProfileAeadAes256Gcm, } for _, profile := range profiles { t.Run(profile.String(), func(t *testing.T) { ctxNoCryptex := createCtx(t, profile) ctxCryptex := createCtx(t, profile, Cryptex(CryptexModeEnabled)) srtpNoCryptex, err := ctxNoCryptex.EncryptRTP(nil, rtpBytes, nil) assert.NoError(t, err) srtpCryptex, err := ctxCryptex.EncryptRTP(nil, rtpBytes, nil) assert.NoError(t, err) t.Run("Encrypt with Cryptex Disabled", func(t *testing.T) { ctx1 := createCtx(t, profile, Cryptex(CryptexModeDisabled)) encrypted, err := ctx1.EncryptRTP(nil, rtpBytes, nil) assert.NoError(t, err) var header rtp.Packet err = header.Unmarshal(encrypted) assert.NoError(t, err) assert.Equal(t, uint16(rtp.ExtensionProfileOneByte), header.Header.ExtensionProfile) }) t.Run("Decrypt with Cryptex Disabled", func(t *testing.T) { ctx1 := createCtx(t, profile, Cryptex(CryptexModeDisabled)) _, err := ctx1.DecryptRTP(nil, srtpNoCryptex, nil) assert.NoError(t, err) _, err = ctx1.DecryptRTP(nil, srtpCryptex, nil) assert.ErrorIs(t, err, errCryptexDisabled) }) t.Run("Encrypt with Cryptex Enabled", func(t *testing.T) { ctx1 := createCtx(t, profile, Cryptex(CryptexModeEnabled)) encrypted, err := ctx1.EncryptRTP(nil, rtpBytes, nil) assert.NoError(t, err) var header rtp.Packet err = header.Unmarshal(encrypted) assert.NoError(t, err) assert.Equal(t, uint16(rtp.CryptexProfileOneByte), header.Header.ExtensionProfile) }) t.Run("Decrypt with Cryptex Enabled", func(t *testing.T) { ctx1 := createCtx(t, profile, Cryptex(CryptexModeEnabled)) _, err := ctx1.DecryptRTP(nil, srtpNoCryptex, nil) assert.NoError(t, err) _, err = ctx1.DecryptRTP(nil, srtpCryptex, nil) assert.NoError(t, err) }) t.Run("Encrypt with Cryptex Required", func(t *testing.T) { ctx1 := createCtx(t, profile, Cryptex(CryptexModeRequired)) encrypted, err := ctx1.EncryptRTP(nil, rtpBytes, nil) assert.NoError(t, err) var header rtp.Packet err = header.Unmarshal(encrypted) assert.NoError(t, err) assert.Equal(t, uint16(rtp.CryptexProfileOneByte), header.Header.ExtensionProfile) }) t.Run("Decrypt with Cryptex Required", func(t *testing.T) { ctx1 := createCtx(t, profile, Cryptex(CryptexModeRequired)) _, err := ctx1.DecryptRTP(nil, srtpNoCryptex, nil) assert.ErrorIs(t, err, errUnencryptedHeaderExtAndCSRCs) _, err = ctx1.DecryptRTP(nil, srtpCryptex, nil) assert.NoError(t, err) }) }) } } srtp-3.0.9/srtp_test.go000066400000000000000000000721201511235350500150730ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "testing" "github.com/pion/rtp" "github.com/pion/transport/v3/replaydetector" "github.com/stretchr/testify/assert" ) const ( profileCTR = ProtectionProfileAes128CmHmacSha1_80 profileGCM = ProtectionProfileAeadAes128Gcm defaultSsrc = 0 ) type rtpTestCase struct { sequenceNumber uint16 encryptedCTR []byte encryptedGCM []byte } func (tc rtpTestCase) encrypted(tb testing.TB, profile ProtectionProfile) []byte { tb.Helper() switch profile { case profileCTR: return tc.encryptedCTR case profileGCM: return tc.encryptedGCM default: assert.Fail(tb, "Invalid profile") return nil } } func testKeyLen(t *testing.T, profile ProtectionProfile) { t.Helper() keyLen, err := profile.KeyLen() assert.NoError(t, err) saltLen, err := profile.SaltLen() assert.NoError(t, err) _, err = CreateContext([]byte{}, make([]byte, saltLen), profile) assert.Error(t, err, "CreateContext failed with a 0 length key") _, err = CreateContext(make([]byte, keyLen), []byte{}, profile) assert.Error(t, err, "CreateContext accepted a 0 length salt") _, err = CreateContext(make([]byte, keyLen), make([]byte, saltLen), profile) assert.NoError(t, err, "CreateContext failed with valid key and salt") } func TestKeyLen(t *testing.T) { t.Run("CTR", func(t *testing.T) { testKeyLen(t, profileCTR) }) t.Run("GCM", func(t *testing.T) { testKeyLen(t, profileGCM) }) } func TestValidPacketCounter(t *testing.T) { masterKey := []byte{0x0d, 0xcd, 0x21, 0x3e, 0x4c, 0xbc, 0xf2, 0x8f, 0x01, 0x7f, 0x69, 0x94, 0x40, 0x1e, 0x28, 0x89} masterSalt := []byte{0x62, 0x77, 0x60, 0x38, 0xc0, 0x6d, 0xc9, 0x41, 0x9f, 0x6d, 0xd9, 0x43, 0x3e, 0x7c} srtpSessionSalt, err := aesCmKeyDerivation(labelSRTPSalt, masterKey, masterSalt, 0, len(masterSalt)) assert.NoError(t, err) s := &srtpSSRCState{ssrc: 4160032510} expectedCounter := []byte{ 0xcf, 0x90, 0x1e, 0xa5, 0xda, 0xd3, 0x2c, 0x15, 0x00, 0xa2, 0x24, 0xae, 0xae, 0xaf, 0x00, 0x00, } counter := generateCounter(32846, uint32(s.index>>16), s.ssrc, srtpSessionSalt) //nolint:gosec // G115 assert.Equal(t, counter[:], expectedCounter) } func TestRolloverCount(t *testing.T) { //nolint:cyclop ssrcState := &srtpSSRCState{ssrc: defaultSsrc} // Set initial seqnum roc, diff, ovf := ssrcState.nextRolloverCount(65530) assert.Empty(t, roc, "Initial rollover counter must be 0") assert.False(t, ovf, "Should not overflow") ssrcState.updateRolloverCount(65530, diff, false, 0) // Invalid packets never update ROC ssrcState.nextRolloverCount(0) ssrcState.nextRolloverCount(0x4000) ssrcState.nextRolloverCount(0x8000) ssrcState.nextRolloverCount(0xFFFF) ssrcState.nextRolloverCount(0) // We rolled over to 0 roc, diff, ovf = ssrcState.nextRolloverCount(0) assert.Equal(t, uint32(1), roc, "rolloverCounter must be incremented after wrapping") assert.False(t, ovf, "Should not overflow") ssrcState.updateRolloverCount(0, diff, false, 0) roc, diff, ovf = ssrcState.nextRolloverCount(65530) assert.Empty(t, roc, "rolloverCounter was not updated when it rolled back, failed to handle out of order") assert.False(t, ovf, "Should not overflow") ssrcState.updateRolloverCount(65530, diff, false, 0) roc, diff, ovf = ssrcState.nextRolloverCount(5) assert.Equal(t, uint32(1), roc, "rolloverCounter was not updated when it rolled over initial, to handle out of order") assert.False(t, ovf, "Should not overflow") ssrcState.updateRolloverCount(5, diff, false, 0) _, diff, _ = ssrcState.nextRolloverCount(6) ssrcState.updateRolloverCount(6, diff, false, 0) _, diff, _ = ssrcState.nextRolloverCount(7) ssrcState.updateRolloverCount(7, diff, false, 0) roc, diff, _ = ssrcState.nextRolloverCount(8) assert.Equal(t, uint32(1), roc, "rolloverCounter was improperly updated for non-significant packets") ssrcState.updateRolloverCount(8, diff, false, 0) // valid packets never update ROC roc, diff, ovf = ssrcState.nextRolloverCount(0x4000) assert.Equal(t, uint32(1), roc, "rolloverCounter was improperly updated for non-significant packets") assert.False(t, ovf, "Should not overflow") ssrcState.updateRolloverCount(0x4000, diff, false, 0) roc, diff, ovf = ssrcState.nextRolloverCount(0x8000) assert.Equal(t, uint32(1), roc, "rolloverCounter was improperly updated for non-significant packets") assert.False(t, ovf, "Should not overflow") ssrcState.updateRolloverCount(0x8000, diff, false, 0) roc, diff, ovf = ssrcState.nextRolloverCount(0xFFFF) assert.Equal(t, uint32(1), roc, "rolloverCounter was improperly updated for non-significant packets") assert.False(t, ovf, "Should not overflow") ssrcState.updateRolloverCount(0xFFFF, diff, false, 0) roc, _, ovf = ssrcState.nextRolloverCount(0) assert.Equal(t, uint32(2), roc, "rolloverCounter must be incremented after wrapping") assert.False(t, ovf, "Should not overflow") } func TestRolloverCountOverflow(t *testing.T) { s := &srtpSSRCState{ ssrc: defaultSsrc, index: maxROC << 16, } s.updateRolloverCount(0xFFFF, 0, false, 0) _, _, ovf := s.nextRolloverCount(0) assert.True(t, ovf, "Should overflow") } func buildTestContext(profile ProtectionProfile, opts ...ContextOption) (*Context, error) { keyLen, err := profile.KeyLen() if err != nil { return nil, err } saltLen, err := profile.SaltLen() if err != nil { return nil, err } masterKey := []byte{0x0d, 0xcd, 0x21, 0x3e, 0x4c, 0xbc, 0xf2, 0x8f, 0x01, 0x7f, 0x69, 0x94, 0x40, 0x1e, 0x28, 0x89} masterKey = masterKey[:keyLen] masterSalt := []byte{0x62, 0x77, 0x60, 0x38, 0xc0, 0x6d, 0xc9, 0x41, 0x9f, 0x6d, 0xd9, 0x43, 0x3e, 0x7c} masterSalt = masterSalt[:saltLen] return CreateContext(masterKey, masterSalt, profile, opts...) } func TestRTPInvalidAuth(t *testing.T) { masterKey := []byte{0x0d, 0xcd, 0x21, 0x3e, 0x4c, 0xbc, 0xf2, 0x8f, 0x01, 0x7f, 0x69, 0x94, 0x40, 0x1e, 0x28, 0x89} invalidSalt := []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} encryptContext, err := buildTestContext(profileCTR) assert.NoError(t, err) invalidContext, err := CreateContext(masterKey, invalidSalt, profileCTR) assert.NoError(t, err) for _, testCase := range rtpTestCases() { pkt := &rtp.Packet{Payload: rtpTestCaseDecrypted(), Header: rtp.Header{SequenceNumber: testCase.sequenceNumber}} pktRaw, err := pkt.Marshal() assert.NoError(t, err) out, err := encryptContext.EncryptRTP(nil, pktRaw, nil) assert.NoError(t, err) _, err = invalidContext.DecryptRTP(nil, out, nil) assert.Errorf(t, err, "Managed to decrypt with incorrect salt for packet with SeqNum: %d", testCase.sequenceNumber) } } func rtpTestCaseDecrypted() []byte { return []byte{0x00, 0x01, 0x02, 0x03, 0x04, 0x05} } func rtpTestCases() []rtpTestCase { return []rtpTestCase{ { sequenceNumber: 5000, encryptedCTR: []byte{0x6d, 0xd3, 0x7e, 0xd5, 0x99, 0xb7, 0x2d, 0x28, 0xb1, 0xf3, 0xa1, 0xf0, 0xc, 0xfb, 0xfd, 0x8}, encryptedGCM: []byte{ 0x05, 0x39, 0x62, 0xbb, 0x50, 0x2a, 0x08, 0x19, 0xc7, 0xcc, 0xc9, 0x24, 0xb8, 0xd9, 0x7a, 0xe5, 0xad, 0x99, 0x06, 0xc7, 0x3b, 0x00, }, }, { sequenceNumber: 5001, encryptedCTR: []byte{ 0xda, 0x47, 0x0b, 0x2a, 0x74, 0x53, 0x65, 0xbd, 0x2f, 0xeb, 0xdc, 0x4b, 0x6d, 0x23, 0xf3, 0xde, }, encryptedGCM: []byte{ 0xb0, 0xbc, 0xfc, 0xb0, 0x15, 0x2c, 0xa0, 0x15, 0xb5, 0xa8, 0xcd, 0x0d, 0x65, 0xfa, 0x98, 0xb3, 0x09, 0xb1, 0xf8, 0x4b, 0x1c, 0xfa, }, }, { sequenceNumber: 5002, encryptedCTR: []byte{ 0x6e, 0xa7, 0x69, 0x8d, 0x24, 0x6d, 0xdc, 0xbf, 0xec, 0x02, 0x1c, 0xd1, 0x60, 0x76, 0xc1, 0xe, }, encryptedGCM: []byte{ 0x5e, 0x20, 0x6a, 0xbf, 0x58, 0x7e, 0x24, 0xc0, 0x15, 0x94, 0x7a, 0xe2, 0x49, 0x25, 0xd4, 0xd4, 0x08, 0xe2, 0xf1, 0x47, 0x7a, 0x33, }, }, { sequenceNumber: 5003, encryptedCTR: []byte{ 0x24, 0x7e, 0x96, 0xc8, 0x7d, 0x33, 0xa2, 0x92, 0x8d, 0x13, 0x8d, 0xe0, 0x76, 0x9f, 0x8, 0xdc, }, encryptedGCM: []byte{ 0xb0, 0x63, 0x14, 0xe7, 0xd2, 0x29, 0xca, 0x92, 0x8c, 0x97, 0x25, 0xd2, 0x50, 0x69, 0x6e, 0x1b, 0x04, 0xb9, 0x37, 0xa5, 0xa1, 0xc5, }, }, { sequenceNumber: 5004, encryptedCTR: []byte{ 0x75, 0x43, 0x28, 0xe4, 0x3a, 0x77, 0x59, 0x9b, 0x2e, 0xdf, 0x7b, 0x12, 0x68, 0xb, 0x57, 0x49, }, encryptedGCM: []byte{ 0xb2, 0x4f, 0x19, 0x53, 0x79, 0x8a, 0x9b, 0x9e, 0xe5, 0x22, 0x93, 0x14, 0x50, 0x8a, 0x8c, 0xd5, 0xfc, 0x61, 0xbf, 0x95, 0xd1, 0xfb, }, }, { sequenceNumber: 65535, // upper boundary encryptedCTR: []byte{ 0xaf, 0xf7, 0xc2, 0x70, 0x37, 0x20, 0x83, 0x9c, 0x2c, 0x63, 0x85, 0x15, 0xe, 0x44, 0xca, 0x36, }, encryptedGCM: []byte{ 0x40, 0x44, 0x6c, 0xd1, 0x33, 0x5f, 0xca, 0x9b, 0x2e, 0xa3, 0xe5, 0x03, 0xd7, 0x82, 0x36, 0xd8, 0xb7, 0xe8, 0x97, 0x3c, 0xe6, 0xb6, }, }, } } func testRTPLifecyleNewAlloc(t *testing.T, profile ProtectionProfile) { t.Helper() assertT := assert.New(t) authTagLen, err := profile.AuthTagRTPLen() assertT.NoError(err) for _, testCase := range rtpTestCases() { encryptContext, err := buildTestContext(profile) assertT.NoError(err) decryptContext, err := buildTestContext(profile) assertT.NoError(err) decryptedPkt := &rtp.Packet{ Payload: rtpTestCaseDecrypted(), Header: rtp.Header{SequenceNumber: testCase.sequenceNumber}, } decryptedRaw, err := decryptedPkt.Marshal() assertT.NoError(err) encryptedPkt := &rtp.Packet{ Payload: testCase.encrypted(t, profile), Header: rtp.Header{SequenceNumber: testCase.sequenceNumber}, } encryptedRaw, err := encryptedPkt.Marshal() assertT.NoError(err) actualEncrypted, err := encryptContext.EncryptRTP(nil, decryptedRaw, nil) assertT.NoError(err) assertT.Equalf(actualEncrypted, encryptedRaw, "RTP packet with SeqNum invalid encryption: %d", testCase.sequenceNumber) actualDecrypted, err := decryptContext.DecryptRTP(nil, encryptedRaw, nil) assertT.NoError(err) assertT.NotEqual(encryptedRaw[:len(encryptedRaw)-authTagLen], actualDecrypted, "DecryptRTP improperly encrypted in place") assertT.Equalf(actualDecrypted, decryptedRaw, "RTP packet with SeqNum invalid decryption: %d", testCase.sequenceNumber) } } func TestRTPLifecycleNewAlloc(t *testing.T) { t.Run("CTR", func(t *testing.T) { testRTPLifecyleNewAlloc(t, profileCTR) }) t.Run("GCM", func(t *testing.T) { testRTPLifecyleNewAlloc(t, profileGCM) }) } func testRTPLifecyleInPlace(t *testing.T, profile ProtectionProfile) { //nolint:cyclop t.Helper() assertT := assert.New(t) for _, testCase := range rtpTestCases() { encryptContext, err := buildTestContext(profile) assertT.NoError(err) decryptContext, err := buildTestContext(profile) assertT.NoError(err) decryptHeader := &rtp.Header{} decryptedPkt := &rtp.Packet{ Payload: rtpTestCaseDecrypted(), Header: rtp.Header{SequenceNumber: testCase.sequenceNumber}, } decryptedRaw, err := decryptedPkt.Marshal() assertT.NoError(err) encryptHeader := &rtp.Header{} encryptedPkt := &rtp.Packet{ Payload: testCase.encrypted(t, profile), Header: rtp.Header{SequenceNumber: testCase.sequenceNumber}, } encryptedRaw, err := encryptedPkt.Marshal() assertT.NoError(err) // Copy packet, asserts that everything was done in place slack := 10 if profile == profileGCM { slack = 16 } encryptInput := make([]byte, len(decryptedRaw), len(decryptedRaw)+slack) copy(encryptInput, decryptedRaw) actualEncrypted, err := encryptContext.EncryptRTP(encryptInput, encryptInput, encryptHeader) assertT.NoError(err) assertT.Same(&encryptInput[0], &actualEncrypted[0], "DecryptRTP failed to decrypt in place") assertT.Equal(testCase.sequenceNumber, encryptHeader.SequenceNumber, "EncryptRTP failed to populate input rtp.Header") assertT.Equalf(actualEncrypted, encryptedRaw, "RTP packet with SeqNum invalid encryption: %d", testCase.sequenceNumber) // Copy packet, asserts that everything was done in place decryptInput := make([]byte, len(encryptedRaw)) copy(decryptInput, encryptedRaw) actualDecrypted, err := decryptContext.DecryptRTP(decryptInput, decryptInput, decryptHeader) assertT.NoError(err) assertT.Same(&decryptInput[0], &actualDecrypted[0], "DecryptRTP failed to decrypt in place") assertT.Equal(testCase.sequenceNumber, decryptHeader.SequenceNumber, "DecryptRTP failed to populate input rtp.Header") assertT.Equalf(actualDecrypted, decryptedRaw, "RTP packet with SeqNum invalid decryption: %d", testCase.sequenceNumber) } } func TestRTPLifecycleInPlace(t *testing.T) { t.Run("CTR", func(t *testing.T) { testRTPLifecyleInPlace(t, profileCTR) }) t.Run("GCM", func(t *testing.T) { testRTPLifecyleInPlace(t, profileGCM) }) } func testRTPReplayProtection(t *testing.T, profile ProtectionProfile) { //nolint:cyclop t.Helper() assertT := assert.New(t) for _, testCase := range rtpTestCases() { encryptContext, err := buildTestContext(profile) assertT.NoError(err) decryptContext, err := buildTestContext( profile, SRTPReplayProtection(64), ) assertT.NoError(err) decryptHeader := &rtp.Header{} decryptedPkt := &rtp.Packet{ Payload: rtpTestCaseDecrypted(), Header: rtp.Header{SequenceNumber: testCase.sequenceNumber}, } decryptedRaw, err := decryptedPkt.Marshal() assertT.NoError(err) encryptHeader := &rtp.Header{} encryptedPkt := &rtp.Packet{ Payload: testCase.encrypted(t, profile), Header: rtp.Header{SequenceNumber: testCase.sequenceNumber}, } encryptedRaw, err := encryptedPkt.Marshal() assertT.NoError(err) // Copy packet, asserts that everything was done in place slack := 10 if profile == profileGCM { slack = 16 } encryptInput := make([]byte, len(decryptedRaw), len(decryptedRaw)+slack) copy(encryptInput, decryptedRaw) actualEncrypted, err := encryptContext.EncryptRTP(encryptInput, encryptInput, encryptHeader) assertT.NoError(err) assertT.Same(&encryptInput[0], &actualEncrypted[0], "EncryptRTP failed to encrypt in place") assertT.Equal(testCase.sequenceNumber, encryptHeader.SequenceNumber, "EncryptRTP failed to populate input rtp.Header") assertT.Equalf(actualEncrypted, encryptedRaw, "RTP packet with SeqNum invalid encryption: %d", testCase.sequenceNumber) // Copy packet, asserts that everything was done in place decryptInput := make([]byte, len(encryptedRaw)) copy(decryptInput, encryptedRaw) actualDecrypted, err := decryptContext.DecryptRTP(decryptInput, decryptInput, decryptHeader) assertT.NoError(err) assertT.Same(&decryptInput[0], &actualDecrypted[0], "DecryptRTP failed to decrypt in place") assertT.Equal(testCase.sequenceNumber, decryptHeader.SequenceNumber, "DecryptRTP failed to populate input rtp.Header") assertT.Equalf(actualDecrypted, decryptedRaw, "RTP packet with SeqNum invalid decryption: %d", testCase.sequenceNumber) _, errReplay := decryptContext.DecryptRTP(decryptInput, decryptInput, decryptHeader) assertT.ErrorIs(errReplay, errDuplicated) } } func TestRTPReplayProtection(t *testing.T) { t.Run("CTR", func(t *testing.T) { testRTPReplayProtection(t, profileCTR) }) t.Run("GCM", func(t *testing.T) { testRTPReplayProtection(t, profileGCM) }) } func TestRTPReplayDetectorFactory(t *testing.T) { assertT := assert.New(t) profile := profileCTR data := rtpTestCases()[0] var cntFactory int decryptContext, err := buildTestContext( profile, SRTPReplayDetectorFactory(func() replaydetector.ReplayDetector { cntFactory++ return &nopReplayDetector{} }), ) assertT.NoError(err) pkt := &rtp.Packet{ Payload: data.encrypted(t, profile), Header: rtp.Header{SequenceNumber: data.sequenceNumber}, } in, err := pkt.Marshal() assertT.NoError(err) _, err = decryptContext.DecryptRTP(nil, in, nil) assertT.NoError(err) assertT.Equal(1, cntFactory) } func benchmarkEncryptRTP(b *testing.B, profile ProtectionProfile, size int) { b.Helper() encryptContext, err := buildTestContext(profile) assert.NoError(b, err) pkt := &rtp.Packet{Payload: make([]byte, size)} pktRaw, err := pkt.Marshal() assert.NoError(b, err) b.SetBytes(int64(len(pktRaw))) b.ResetTimer() for i := 0; i < b.N; i++ { _, err = encryptContext.EncryptRTP(nil, pktRaw, nil) assert.NoError(b, err) } } func BenchmarkEncryptRTP(b *testing.B) { b.Run("CTR-100", func(b *testing.B) { benchmarkEncryptRTP(b, profileCTR, 100) }) b.Run("CTR-1000", func(b *testing.B) { benchmarkEncryptRTP(b, profileCTR, 1000) }) b.Run("GCM-100", func(b *testing.B) { benchmarkEncryptRTP(b, profileGCM, 100) }) b.Run("GCM-1000", func(b *testing.B) { benchmarkEncryptRTP(b, profileGCM, 1000) }) } func benchmarkEncryptRTPInPlace(b *testing.B, profile ProtectionProfile, size int) { b.Helper() encryptContext, err := buildTestContext(profile) assert.NoError(b, err) pkt := &rtp.Packet{Payload: make([]byte, size)} pktRaw, err := pkt.Marshal() assert.NoError(b, err) buf := make([]byte, 0, len(pktRaw)+10) b.SetBytes(int64(len(pktRaw))) b.ResetTimer() for i := 0; i < b.N; i++ { buf, err = encryptContext.EncryptRTP(buf[:0], pktRaw, nil) assert.NoError(b, err) } } func BenchmarkEncryptRTPInPlace(b *testing.B) { b.Run("CTR-100", func(b *testing.B) { benchmarkEncryptRTPInPlace(b, profileCTR, 100) }) b.Run("CTR-1000", func(b *testing.B) { benchmarkEncryptRTPInPlace(b, profileCTR, 1000) }) b.Run("GCM-100", func(b *testing.B) { benchmarkEncryptRTPInPlace(b, profileGCM, 100) }) b.Run("GCM-1000", func(b *testing.B) { benchmarkEncryptRTPInPlace(b, profileGCM, 1000) }) } func benchmarkDecryptRTP(b *testing.B, profile ProtectionProfile) { b.Helper() sequenceNumber := uint16(5000) encrypted := rtpTestCases()[0].encrypted(b, profile) encryptedPkt := &rtp.Packet{ Payload: encrypted, Header: rtp.Header{ SequenceNumber: sequenceNumber, }, } encryptedRaw, err := encryptedPkt.Marshal() assert.NoError(b, err) context, err := buildTestContext(profile) assert.NoError(b, err) b.SetBytes(int64(len(encryptedRaw))) b.ResetTimer() for i := 0; i < b.N; i++ { _, err := context.DecryptRTP(nil, encryptedRaw, nil) assert.NoError(b, err) } } func BenchmarkDecryptRTP(b *testing.B) { b.Run("CTR", func(b *testing.B) { benchmarkDecryptRTP(b, profileCTR) }) b.Run("GCM", func(b *testing.B) { benchmarkDecryptRTP(b, profileGCM) }) } func TestRolloverCount2(t *testing.T) { //nolint:cyclop srtpState := &srtpSSRCState{ssrc: defaultSsrc} roc, diff, ovf := srtpState.nextRolloverCount(30123) assert.Equal(t, uint32(0), roc, "Initial rolloverCounter must be 0") assert.False(t, ovf, "Should not overflow") srtpState.updateRolloverCount(30123, diff, false, 0) roc, diff, ovf = srtpState.nextRolloverCount(62892) // 30123 + (1 << 15) + 1 assert.Equal(t, uint32(0), roc, "Initial rolloverCounter must be 0") assert.False(t, ovf, "Should not overflow") srtpState.updateRolloverCount(62892, diff, false, 0) roc, diff, ovf = srtpState.nextRolloverCount(204) assert.Equal(t, uint32(1), roc, "rolloverCounter was not updated after it crossed 0") assert.False(t, ovf, "Should not overflow") srtpState.updateRolloverCount(62892, diff, false, 0) roc, diff, ovf = srtpState.nextRolloverCount(64535) assert.Equal(t, uint32(0), roc, "rolloverCounter was not updated when it rolled back, failed to handle out of order") assert.False(t, ovf, "Should not overflow") srtpState.updateRolloverCount(64535, diff, false, 0) roc, diff, ovf = srtpState.nextRolloverCount(205) assert.Equal(t, uint32(1), roc, "rolloverCounter was improperly updated for non-significant packets") assert.False(t, ovf, "Should not overflow") srtpState.updateRolloverCount(205, diff, false, 0) roc, diff, ovf = srtpState.nextRolloverCount(1) assert.Equal(t, uint32(1), roc, "rolloverCounter was improperly updated for non-significant packets") assert.False(t, ovf, "Should not overflow") srtpState.updateRolloverCount(1, diff, false, 0) roc, diff, ovf = srtpState.nextRolloverCount(64532) assert.Equal(t, uint32(0), roc, "rolloverCounter was improperly updated for non-significant packets") assert.False(t, ovf, "Should not overflow") srtpState.updateRolloverCount(64532, diff, false, 0) roc, diff, ovf = srtpState.nextRolloverCount(65534) assert.Equal(t, uint32(0), roc, "rolloverCounter was improperly updated for non-significant packets") assert.False(t, ovf, "Should not overflow") srtpState.updateRolloverCount(65534, diff, false, 0) roc, diff, ovf = srtpState.nextRolloverCount(64532) assert.Equal(t, uint32(0), roc, "rolloverCounter was improperly updated for non-significant packets") assert.False(t, ovf, "Should not overflow") srtpState.updateRolloverCount(65532, diff, false, 0) roc, diff, ovf = srtpState.nextRolloverCount(205) assert.Equal(t, uint32(1), roc, "index was not updated after it crossed 0") assert.False(t, ovf, "Should not overflow") srtpState.updateRolloverCount(65532, diff, false, 0) } func TestProtectionProfileAes128CmHmacSha1_32(t *testing.T) { masterKey := []byte{0x0d, 0xcd, 0x21, 0x3e, 0x4c, 0xbc, 0xf2, 0x8f, 0x01, 0x7f, 0x69, 0x94, 0x40, 0x1e, 0x28, 0x89} masterSalt := []byte{0x62, 0x77, 0x60, 0x38, 0xc0, 0x6d, 0xc9, 0x41, 0x9f, 0x6d, 0xd9, 0x43, 0x3e, 0x7c} encryptContext, err := CreateContext(masterKey, masterSalt, ProtectionProfileAes128CmHmacSha1_32) assert.NoError(t, err) decryptContext, err := CreateContext(masterKey, masterSalt, ProtectionProfileAes128CmHmacSha1_32) assert.NoError(t, err) pkt := &rtp.Packet{Payload: rtpTestCaseDecrypted(), Header: rtp.Header{SequenceNumber: 5000}} pktRaw, err := pkt.Marshal() assert.NoError(t, err) out, err := encryptContext.EncryptRTP(nil, pktRaw, nil) assert.NoError(t, err) decrypted, err := decryptContext.DecryptRTP(nil, out, nil) assert.NoError(t, err) assert.Equal(t, pktRaw, decrypted, "Decrypted RTP packet does not match original") } func TestRTPDecryptShotenedPacket(t *testing.T) { profiles := map[string]ProtectionProfile{ "CTR": profileCTR, "GCM": profileGCM, } for name, profile := range profiles { profile := profile t.Run(name, func(t *testing.T) { for _, testCase := range rtpTestCases() { decryptContext, err := buildTestContext(profile) assert.NoError(t, err) encryptedPkt := &rtp.Packet{ Payload: testCase.encrypted(t, profile), Header: rtp.Header{SequenceNumber: testCase.sequenceNumber}, } encryptedRaw, err := encryptedPkt.Marshal() assert.NoError(t, err) for i := 1; i < len(encryptedRaw)-1; i++ { packet := encryptedRaw[:i] assert.NotPanics(t, func() { _, _ = decryptContext.DecryptRTP(nil, packet, nil) }, "Panic on length %d/%d", i, len(encryptedRaw)) } } }) } } func TestRTPMaxPackets(t *testing.T) { profiles := map[string]ProtectionProfile{ "CTR": profileCTR, "GCM": profileGCM, } for name, profile := range profiles { profile := profile t.Run(name, func(t *testing.T) { context, err := buildTestContext(profile) assert.NoError(t, err) context.SetROC(1, (1<<32)-1) pkt0 := &rtp.Packet{ Header: rtp.Header{ SSRC: 1, SequenceNumber: 0xffff, }, Payload: []byte{0, 1}, } raw0, err0 := pkt0.Marshal() assert.NoError(t, err0) _, errEnc := context.EncryptRTP(nil, raw0, nil) assert.NoError(t, errEnc) pkt1 := &rtp.Packet{ Header: rtp.Header{ SSRC: 1, SequenceNumber: 0x0, }, Payload: []byte{0, 1}, } raw1, err1 := pkt1.Marshal() assert.NoError(t, err1) _, errEnc = context.EncryptRTP(nil, raw1, nil) assert.ErrorIs(t, errEnc, errExceededMaxPackets) }) } } func TestRTPBurstLossWithSetROC(t *testing.T) { //nolint:cyclop profiles := map[string]ProtectionProfile{ "CTR": profileCTR, "GCM": profileGCM, } for name, profile := range profiles { profile := profile t.Run(name, func(t *testing.T) { assertT := assert.New(t) encryptContext, err := buildTestContext(profile) assertT.NoError(err) type packetWithROC struct { pkt rtp.Packet enc []byte raw []byte roc uint32 } var pkts []*packetWithROC encryptContext.SetROC(1, 3) for i := 0x8C00; i < 0x20400; i += 0x100 { packet := &packetWithROC{ pkt: rtp.Packet{ Payload: []byte{ byte(i >> 16), byte(i >> 8), byte(i), }, Header: rtp.Header{ Marker: true, SSRC: 1, SequenceNumber: uint16(i), //nolint:gosec // G115 }, }, } b, errMarshal := packet.pkt.Marshal() assertT.NoError(errMarshal) packet.raw = b enc, errEnc := encryptContext.EncryptRTP(nil, b, nil) assertT.NoError(errEnc) packet.roc, _ = encryptContext.ROC(1) if 0x9000 < i && i < 0x20100 { continue } packet.enc = enc pkts = append(pkts, packet) } decryptContext, err := buildTestContext(profile) assertT.NoError(err) for _, p := range pkts { decryptContext.SetROC(1, p.roc) pkt, err := decryptContext.DecryptRTP(nil, p.enc, nil) assertT.NoErrorf(err, "roc=%d, seq=%d", p.roc, p.pkt.SequenceNumber) assertT.Equal(p.raw, pkt) } }) } } func TestDecryptInvalidSRTP(t *testing.T) { assertT := assert.New(t) key := []byte{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01} salt := []byte{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01} decryptContext, err := CreateContext(key, salt, ProtectionProfileAes128CmHmacSha1_80) assertT.NoError(err) packet := []byte{ 0x41, 0x02, 0x07, 0xf9, 0xf9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb5, 0x73, 0x19, 0xf6, 0x91, 0xbb, 0x3e, 0xa5, 0x21, 0x07, } _, err = decryptContext.DecryptRTP(nil, packet, nil) assertT.Error(err) } func TestRTPInvalidMKI(t *testing.T) { mki1 := []byte{0x01, 0x02, 0x03, 0x04} mki2 := []byte{0x02, 0x03, 0x04, 0x05} encryptContext, err := buildTestContext(profileCTR, MasterKeyIndicator(mki1)) assert.NoError(t, err) decryptContext, err := buildTestContext(profileCTR, MasterKeyIndicator(mki2)) assert.NoError(t, err) for _, testCase := range rtpTestCases() { pkt := &rtp.Packet{Payload: rtpTestCaseDecrypted(), Header: rtp.Header{SequenceNumber: testCase.sequenceNumber}} pktRaw, err := pkt.Marshal() assert.NoError(t, err) out, err := encryptContext.EncryptRTP(nil, pktRaw, nil) assert.NoError(t, err) _, err = decryptContext.DecryptRTP(nil, out, nil) assert.Errorf(t, err, "Managed to decrypt with incorrect MKI for packet with SeqNum: %d", testCase.sequenceNumber) assert.ErrorIs(t, err, ErrMKINotFound) } } func TestRTPHandleMultipleMKI(t *testing.T) { //nolint:cyclop mki1 := []byte{0x01, 0x02, 0x03, 0x04} mki2 := []byte{0x02, 0x03, 0x04, 0x05} masterKey2 := []byte{0xff, 0xcd, 0x21, 0x3e, 0x4c, 0xbc, 0xf2, 0x8f, 0x01, 0x7f, 0x69, 0x94, 0x40, 0x1e, 0x28, 0x89} masterSalt2 := []byte{0xff, 0x77, 0x60, 0x38, 0xc0, 0x6d, 0xc9, 0x41, 0x9f, 0x6d, 0xd9, 0x43, 0x3e, 0x7c} encryptContext1, err := buildTestContext(profileCTR, MasterKeyIndicator(mki1)) assert.NoError(t, err) encryptContext2, err := CreateContext(masterKey2, masterSalt2, profileCTR, MasterKeyIndicator(mki2)) assert.NoError(t, err) decryptContext, err := buildTestContext(profileCTR, MasterKeyIndicator(mki1)) assert.NoError(t, err) err = decryptContext.AddCipherForMKI(mki2, masterKey2, masterSalt2) assert.NoError(t, err) for _, testCase := range rtpTestCases() { pkt := &rtp.Packet{Payload: rtpTestCaseDecrypted(), Header: rtp.Header{SequenceNumber: testCase.sequenceNumber}} pktRaw, err := pkt.Marshal() assert.NoError(t, err) encrypted1, err := encryptContext1.EncryptRTP(nil, pktRaw, nil) assert.NoError(t, err) encrypted2, err := encryptContext2.EncryptRTP(nil, pktRaw, nil) assert.NoError(t, err) decrypted1, err := decryptContext.DecryptRTP(nil, encrypted1, nil) assert.NoError(t, err) decrypted2, err := decryptContext.DecryptRTP(nil, encrypted2, nil) assert.NoError(t, err) assert.Equal(t, pktRaw, decrypted1) assert.Equal(t, pktRaw, decrypted2) } } func TestRTPSwitchMKI(t *testing.T) { //nolint:cyclop mki1 := []byte{0x01, 0x02, 0x03, 0x04} mki2 := []byte{0x02, 0x03, 0x04, 0x05} masterKey2 := []byte{0xff, 0xcd, 0x21, 0x3e, 0x4c, 0xbc, 0xf2, 0x8f, 0x01, 0x7f, 0x69, 0x94, 0x40, 0x1e, 0x28, 0x89} masterSalt2 := []byte{0xff, 0x77, 0x60, 0x38, 0xc0, 0x6d, 0xc9, 0x41, 0x9f, 0x6d, 0xd9, 0x43, 0x3e, 0x7c} encryptContext, err := buildTestContext(profileCTR, MasterKeyIndicator(mki1)) assert.NoError(t, err) err = encryptContext.AddCipherForMKI(mki2, masterKey2, masterSalt2) assert.NoError(t, err) decryptContext1, err := buildTestContext(profileCTR, MasterKeyIndicator(mki1)) assert.NoError(t, err) decryptContext2, err := CreateContext(masterKey2, masterSalt2, profileCTR, MasterKeyIndicator(mki2)) assert.NoError(t, err) for _, testCase := range rtpTestCases() { pkt := &rtp.Packet{Payload: rtpTestCaseDecrypted(), Header: rtp.Header{SequenceNumber: testCase.sequenceNumber}} pktRaw, err := pkt.Marshal() assert.NoError(t, err) encrypted1, err := encryptContext.EncryptRTP(nil, pktRaw, nil) assert.NoError(t, err) err = encryptContext.SetSendMKI(mki2) assert.NoError(t, err) encrypted2, err := encryptContext.EncryptRTP(nil, pktRaw, nil) assert.NoError(t, err) assert.NotEqual(t, encrypted1, encrypted2) decrypted1, err := decryptContext1.DecryptRTP(nil, encrypted1, nil) assert.NoError(t, err) decrypted2, err := decryptContext2.DecryptRTP(nil, encrypted2, nil) assert.NoError(t, err) assert.Equal(t, pktRaw, decrypted1) assert.Equal(t, pktRaw, decrypted2) err = encryptContext.SetSendMKI(mki1) assert.NoError(t, err) } } srtp-3.0.9/stream.go000066400000000000000000000003621511235350500143360ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp type readStream interface { init(child streamSession, ssrc uint32) error Read(buf []byte) (int, error) GetSSRC() uint32 } srtp-3.0.9/stream_srtcp.go000066400000000000000000000067351511235350500155630ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "errors" "io" "sync" "time" "github.com/pion/rtcp" "github.com/pion/transport/v3/packetio" ) // Limit the buffer size to 100KB. const srtcpBufferSize = 100 * 1000 // ReadStreamSRTCP handles decryption for a single RTCP SSRC. type ReadStreamSRTCP struct { mu sync.Mutex isClosed chan bool session *SessionSRTCP ssrc uint32 isInited bool buffer io.ReadWriteCloser } func (r *ReadStreamSRTCP) write(buf []byte) (n int, err error) { n, err = r.buffer.Write(buf) if errors.Is(err, packetio.ErrFull) { // Silently drop data when the buffer is full. return len(buf), nil } return n, err } // Used by getOrCreateReadStream. func newReadStreamSRTCP() readStream { return &ReadStreamSRTCP{} } // ReadRTCP reads and decrypts full RTCP packet and its header from the nextConn. func (r *ReadStreamSRTCP) ReadRTCP(buf []byte) (int, *rtcp.Header, error) { n, err := r.Read(buf) if err != nil { return 0, nil, err } header := &rtcp.Header{} err = header.Unmarshal(buf[:n]) if err != nil { return 0, nil, err } return n, header, nil } // Read reads and decrypts full RTCP packet from the nextConn. func (r *ReadStreamSRTCP) Read(buf []byte) (int, error) { return r.buffer.Read(buf) } // SetReadDeadline sets the deadline for the Read operation. // Setting to zero means no deadline. func (r *ReadStreamSRTCP) SetReadDeadline(t time.Time) error { if b, ok := r.buffer.(interface { SetReadDeadline(time.Time) error }); ok { return b.SetReadDeadline(t) } return nil } // Close removes the ReadStream from the session and cleans up any associated state. func (r *ReadStreamSRTCP) Close() error { r.mu.Lock() defer r.mu.Unlock() if !r.isInited { return errStreamNotInited } select { case <-r.isClosed: return errStreamAlreadyClosed default: err := r.buffer.Close() if err != nil { return err } r.session.removeReadStream(r.ssrc) return nil } } func (r *ReadStreamSRTCP) init(child streamSession, ssrc uint32) error { sessionSRTCP, ok := child.(*SessionSRTCP) r.mu.Lock() defer r.mu.Unlock() if !ok { return errFailedTypeAssertion } else if r.isInited { return errStreamAlreadyInited } r.session = sessionSRTCP r.ssrc = ssrc r.isInited = true r.isClosed = make(chan bool) if r.session.bufferFactory != nil { r.buffer = r.session.bufferFactory(packetio.RTCPBufferPacket, ssrc) } else { // Create a buffer and limit it to 100KB buff := packetio.NewBuffer() buff.SetLimitSize(srtcpBufferSize) r.buffer = buff } return nil } // GetSSRC returns the SSRC we are demuxing for. func (r *ReadStreamSRTCP) GetSSRC() uint32 { return r.ssrc } // WriteStreamSRTCP is stream for a single Session that is used to encrypt RTCP. type WriteStreamSRTCP struct { session *SessionSRTCP } // WriteRTCP encrypts a RTCP header and its payload to the nextConn. func (w *WriteStreamSRTCP) WriteRTCP(header *rtcp.Header, payload []byte) (int, error) { headerRaw, err := header.Marshal() if err != nil { return 0, err } return w.session.write(append(headerRaw, payload...)) } // Write encrypts and writes a full RTCP packets to the nextConn. func (w *WriteStreamSRTCP) Write(b []byte) (int, error) { return w.session.write(b) } // SetWriteDeadline sets the deadline for the Write operation. // Setting to zero means no deadline. func (w *WriteStreamSRTCP) SetWriteDeadline(t time.Time) error { return w.session.setWriteDeadline(t) } srtp-3.0.9/stream_srtp.go000066400000000000000000000076561511235350500154230ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "errors" "io" "slices" "sync" "time" "github.com/pion/rtp" "github.com/pion/transport/v3/packetio" ) // Limit the buffer size to 1MB. const srtpBufferSize = 1000 * 1000 // ReadStreamSRTP handles decryption for a single RTP SSRC. type ReadStreamSRTP struct { mu sync.Mutex isClosed chan bool session *SessionSRTP ssrc uint32 isInited bool buffer io.ReadWriteCloser peekedPackets [][]byte } // Used by getOrCreateReadStream. func newReadStreamSRTP() readStream { return &ReadStreamSRTP{} } func (r *ReadStreamSRTP) init(child streamSession, ssrc uint32) error { sessionSRTP, ok := child.(*SessionSRTP) r.mu.Lock() defer r.mu.Unlock() if !ok { return errFailedTypeAssertion } else if r.isInited { return errStreamAlreadyInited } r.session = sessionSRTP r.ssrc = ssrc r.isInited = true r.isClosed = make(chan bool) // Create a buffer with a 1MB limit if r.session.bufferFactory != nil { r.buffer = r.session.bufferFactory(packetio.RTPBufferPacket, ssrc) } else { buff := packetio.NewBuffer() buff.SetLimitSize(srtpBufferSize) r.buffer = buff } return nil } func (r *ReadStreamSRTP) write(buf []byte) (n int, err error) { n, err = r.buffer.Write(buf) if errors.Is(err, packetio.ErrFull) { // Silently drop data when the buffer is full. return len(buf), nil } return n, err } // Peek reads and decrypts full RTP packet from the nextConn. // It is then buffered so that a call to `Read` will return it. func (r *ReadStreamSRTP) Peek(buf []byte) (n int, err error) { n, err = r.buffer.Read(buf) if err == nil { r.peekedPackets = append(r.peekedPackets, slices.Clone(buf[:n])) } return } // Read reads and decrypts full RTP packet from the nextConn. func (r *ReadStreamSRTP) Read(buf []byte) (int, error) { if len(r.peekedPackets) != 0 { if len(r.peekedPackets[0]) > len(buf) { return 0, io.ErrShortBuffer } n := len(r.peekedPackets[0]) copy(buf, r.peekedPackets[0]) r.peekedPackets = r.peekedPackets[1:] return n, nil } return r.buffer.Read(buf) } // ReadRTP reads and decrypts full RTP packet and its header from the nextConn. func (r *ReadStreamSRTP) ReadRTP(buf []byte) (int, *rtp.Header, error) { n, err := r.Read(buf) if err != nil { return 0, nil, err } header := &rtp.Header{} _, err = header.Unmarshal(buf[:n]) if err != nil { return 0, nil, err } return n, header, nil } // SetReadDeadline sets the deadline for the Read operation. // Setting to zero means no deadline. func (r *ReadStreamSRTP) SetReadDeadline(t time.Time) error { if b, ok := r.buffer.(interface { SetReadDeadline(time.Time) error }); ok { return b.SetReadDeadline(t) } return nil } // Close removes the ReadStream from the session and cleans up any associated state. func (r *ReadStreamSRTP) Close() error { r.mu.Lock() defer r.mu.Unlock() if !r.isInited { return errStreamNotInited } select { case <-r.isClosed: return errStreamAlreadyClosed default: err := r.buffer.Close() if err != nil { return err } r.session.removeReadStream(r.ssrc) return nil } } // GetSSRC returns the SSRC we are demuxing for. func (r *ReadStreamSRTP) GetSSRC() uint32 { return r.ssrc } // WriteStreamSRTP is stream for a single Session that is used to encrypt RTP. type WriteStreamSRTP struct { session *SessionSRTP } // WriteRTP encrypts a RTP packet and writes to the connection. func (w *WriteStreamSRTP) WriteRTP(header *rtp.Header, payload []byte) (int, error) { return w.session.writeRTP(header, payload) } // Write encrypts and writes a full RTP packets to the nextConn. func (w *WriteStreamSRTP) Write(b []byte) (int, error) { return w.session.write(b) } // SetWriteDeadline sets the deadline for the Write operation. // Setting to zero means no deadline. func (w *WriteStreamSRTP) SetWriteDeadline(t time.Time) error { return w.session.setWriteDeadline(t) } srtp-3.0.9/stream_srtp_test.go000066400000000000000000000161211511235350500164450ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "io" "net" "sync" "testing" "time" "github.com/pion/rtp" "github.com/pion/transport/v3/packetio" "github.com/stretchr/testify/assert" ) type noopConn struct{ closed chan struct{} } func newNoopConn() *noopConn { return &noopConn{closed: make(chan struct{})} } func (c *noopConn) Read([]byte) (n int, err error) { <-c.closed return 0, io.EOF } func (c *noopConn) Write(b []byte) (n int, err error) { return len(b), nil } func (c *noopConn) Close() error { close(c.closed) return nil } func (c *noopConn) LocalAddr() net.Addr { return nil } func (c *noopConn) RemoteAddr() net.Addr { return nil } func (c *noopConn) SetDeadline(time.Time) error { return nil } func (c *noopConn) SetReadDeadline(time.Time) error { return nil } func (c *noopConn) SetWriteDeadline(time.Time) error { return nil } func TestPeek(t *testing.T) { firstBuffer := []byte{0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA} secondBuffer := []byte{0xBB, 0xBB, 0xBB} thirdBuffer := []byte{0xCC, 0xCC, 0xCC} buffer := packetio.NewBuffer() stream := &ReadStreamSRTP{buffer: buffer} t.Run("Short Peek", func(t *testing.T) { _, err := buffer.Write(firstBuffer) assert.NoError(t, err) readBuff := make([]byte, 1) _, err = stream.Peek(readBuff) assert.Error(t, err, io.ErrShortBuffer) }) t.Run("Short Read", func(t *testing.T) { _, err := buffer.Write(firstBuffer) assert.NoError(t, err) readBuff := make([]byte, 6) n, err := stream.Peek(readBuff) assert.NoError(t, err) assert.Equal(t, n, 6) assert.Equal(t, readBuff, firstBuffer) n, err = stream.Read([]byte{}) assert.Error(t, err, io.ErrShortBuffer) assert.Equal(t, n, 0) assert.Equal(t, readBuff, firstBuffer) n, err = stream.Read(readBuff) assert.NoError(t, err) assert.Equal(t, n, 6) assert.Equal(t, readBuff, firstBuffer) }) t.Run("Single Peek", func(t *testing.T) { _, err := buffer.Write(firstBuffer) assert.NoError(t, err) readBuff := make([]byte, 6) n, err := stream.Peek(readBuff) assert.NoError(t, err) assert.Equal(t, n, 6) assert.Equal(t, readBuff, firstBuffer) n, err = stream.Read(readBuff) assert.NoError(t, err) assert.Equal(t, n, 6) assert.Equal(t, readBuff, firstBuffer) }) t.Run("Multi Peek", func(t *testing.T) { _, err := buffer.Write(firstBuffer) assert.NoError(t, err) _, err = buffer.Write(secondBuffer) assert.NoError(t, err) _, err = buffer.Write(thirdBuffer) assert.NoError(t, err) readBuff := make([]byte, 6) n, err := stream.Peek(readBuff) assert.NoError(t, err) assert.Equal(t, n, 6) assert.Equal(t, readBuff[:n], firstBuffer) n, err = stream.Peek(readBuff) assert.NoError(t, err) assert.Equal(t, n, 3) assert.Equal(t, readBuff[:n], secondBuffer) n, err = stream.Peek(readBuff) assert.NoError(t, err) assert.Equal(t, n, 3) assert.Equal(t, readBuff[:n], thirdBuffer) n, err = stream.Read(readBuff) assert.NoError(t, err) assert.Equal(t, n, 6) assert.Equal(t, readBuff[:n], firstBuffer) n, err = stream.Read(readBuff) assert.NoError(t, err) assert.Equal(t, n, 3) assert.Equal(t, readBuff[:n], secondBuffer) n, err = stream.Read(readBuff) assert.NoError(t, err) assert.Equal(t, n, 3) assert.Equal(t, readBuff[:n], thirdBuffer) }) } func TestBufferFactory(t *testing.T) { wg := sync.WaitGroup{} wg.Add(2) conn := newNoopConn() bf := func(_ packetio.BufferPacketType, _ uint32) io.ReadWriteCloser { wg.Done() return packetio.NewBuffer() } rtpSession, err := NewSessionSRTP(conn, &Config{ Keys: SessionKeys{ LocalMasterKey: make([]byte, 16), LocalMasterSalt: make([]byte, 14), RemoteMasterKey: make([]byte, 16), RemoteMasterSalt: make([]byte, 14), }, BufferFactory: bf, Profile: ProtectionProfileAes128CmHmacSha1_80, }) assert.NoError(t, err) rtcpSession, err := NewSessionSRTCP(conn, &Config{ Keys: SessionKeys{ LocalMasterKey: make([]byte, 16), LocalMasterSalt: make([]byte, 14), RemoteMasterKey: make([]byte, 16), RemoteMasterSalt: make([]byte, 14), }, BufferFactory: bf, Profile: ProtectionProfileAes128CmHmacSha1_80, }) assert.NoError(t, err) _, _ = rtpSession.OpenReadStream(123) _, _ = rtcpSession.OpenReadStream(123) wg.Wait() } func benchmarkWrite(b *testing.B, profile ProtectionProfile, size int) { b.Helper() conn := newNoopConn() keyLen, err := profile.KeyLen() if err != nil { b.Fatal(err) } saltLen, err := profile.SaltLen() if err != nil { b.Fatal(err) } config := &Config{ Keys: SessionKeys{ LocalMasterKey: make([]byte, keyLen), LocalMasterSalt: make([]byte, saltLen), RemoteMasterKey: make([]byte, keyLen), RemoteMasterSalt: make([]byte, saltLen), }, Profile: profile, } session, err := NewSessionSRTP(conn, config) if err != nil { b.Fatal(err) } ws, err := session.OpenWriteStream() if err != nil { b.Fatal(err) } packet := &rtp.Packet{ Header: rtp.Header{ Version: 2, SSRC: 322, }, Payload: make([]byte, size), } packetRaw, err := packet.Marshal() if err != nil { b.Fatal(err) } b.SetBytes(int64(len(packetRaw))) b.ResetTimer() for i := 0; i < b.N; i++ { packet.Header.SequenceNumber++ _, err = ws.Write(packetRaw) if err != nil { b.Fatal(err) } } err = session.Close() if err != nil { b.Fatal(err) } } func BenchmarkWrite(b *testing.B) { b.Run("CTR-100", func(b *testing.B) { benchmarkWrite(b, profileCTR, 100) }) b.Run("CTR-1000", func(b *testing.B) { benchmarkWrite(b, profileCTR, 1000) }) b.Run("GCM-100", func(b *testing.B) { benchmarkWrite(b, profileGCM, 100) }) b.Run("GCM-1000", func(b *testing.B) { benchmarkWrite(b, profileGCM, 1000) }) } func benchmarkWriteRTP(b *testing.B, profile ProtectionProfile, size int) { b.Helper() conn := &noopConn{ closed: make(chan struct{}), } keyLen, err := profile.KeyLen() if err != nil { b.Fatal(err) } saltLen, err := profile.SaltLen() if err != nil { b.Fatal(err) } config := &Config{ Keys: SessionKeys{ LocalMasterKey: make([]byte, keyLen), LocalMasterSalt: make([]byte, saltLen), RemoteMasterKey: make([]byte, keyLen), RemoteMasterSalt: make([]byte, saltLen), }, Profile: profile, } session, err := NewSessionSRTP(conn, config) if err != nil { b.Fatal(err) } ws, err := session.OpenWriteStream() if err != nil { b.Fatal(err) } header := &rtp.Header{ Version: 2, SSRC: 322, } payload := make([]byte, size) b.SetBytes(int64(header.MarshalSize() + len(payload))) b.ResetTimer() for i := 0; i < b.N; i++ { header.SequenceNumber++ _, err = ws.WriteRTP(header, payload) if err != nil { b.Fatal(err) } } err = session.Close() if err != nil { b.Fatal(err) } } func BenchmarkWriteRTP(b *testing.B) { b.Run("CTR-100", func(b *testing.B) { benchmarkWriteRTP(b, profileCTR, 100) }) b.Run("CTR-1000", func(b *testing.B) { benchmarkWriteRTP(b, profileCTR, 1000) }) b.Run("GCM-100", func(b *testing.B) { benchmarkWriteRTP(b, profileGCM, 100) }) b.Run("GCM-1000", func(b *testing.B) { benchmarkWriteRTP(b, profileGCM, 1000) }) } srtp-3.0.9/util.go000066400000000000000000000016111511235350500140160ustar00rootroot00000000000000// SPDX-FileCopyrightText: 2023 The Pion community // SPDX-License-Identifier: MIT package srtp import ( "unsafe" ) // growBufferSize grows the buffer size to the given number of bytes. func growBufferSize(buf []byte, size int) []byte { if size <= cap(buf) { return buf[:size] } buf2 := make([]byte, size) copy(buf2, buf) return buf2 } // isSameBuffer returns true if slices a and b share the same underlying buffer. func isSameBuffer(a, b []byte) bool { // If both are nil, they are technically the same (no buffer) if a == nil && b == nil { return true } // If either is nil, or both have 0 capacity, they can't share backing buffer if cap(a) == 0 || cap(b) == 0 { return false } // Create a slice of length 1 from each if possible aPtr := unsafe.Pointer(&a[:1][0]) // nolint:gosec bPtr := unsafe.Pointer(&b[:1][0]) // nolint:gosec return aPtr == bPtr }