├── .github ├── actions-rs │ └── grcov.yml └── workflows │ ├── cargo.yml │ └── grcov.yml ├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── codecov.yml ├── doc └── webrtc.rs.png └── src ├── cipher ├── cipher_aead_aes_gcm.rs ├── cipher_aes_cm_hmac_sha1.rs └── mod.rs ├── config.rs ├── context ├── context_test.rs ├── mod.rs ├── srtcp.rs ├── srtcp_test.rs ├── srtp.rs └── srtp_test.rs ├── error.rs ├── key_derivation.rs ├── lib.rs ├── option.rs ├── protection_profile.rs ├── session ├── mod.rs ├── session_rtcp_test.rs └── session_rtp_test.rs └── stream.rs /.github/actions-rs/grcov.yml: -------------------------------------------------------------------------------- 1 | branch: true 2 | ignore-not-existing: true 3 | llvm: true 4 | filter: covered 5 | output-type: lcov 6 | output-path: ./lcov.info 7 | source-dir: . 8 | ignore: 9 | - "/*" 10 | - "C:/*" 11 | - "../*" 12 | excl-line: "#\\[derive\\(" 13 | excl-start: "mod tests \\{" 14 | excl-br-line: "#\\[derive\\(" 15 | excl-br-start: "mod tests \\{" -------------------------------------------------------------------------------- /.github/workflows/cargo.yml: -------------------------------------------------------------------------------- 1 | name: cargo 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | check_and_test: 14 | name: Check and test 15 | strategy: 16 | matrix: 17 | os: ['ubuntu-latest', 'macos-latest', 'windows-latest'] 18 | toolchain: 19 | - 1.56.1 # min supported version (https://github.com/webrtc-rs/webrtc/#toolchain) 20 | - stable 21 | runs-on: ${{ matrix.os }} 22 | steps: 23 | - uses: actions/checkout@v3 24 | - name: Cache cargo registry 25 | uses: actions/cache@v3 26 | with: 27 | path: ~/.cargo/registry 28 | key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} 29 | - uses: actions-rs/toolchain@v1 30 | with: 31 | toolchain: ${{ matrix.toolchain }} 32 | profile: minimal 33 | override: true 34 | - uses: actions-rs/cargo@v1 35 | with: 36 | command: check 37 | - uses: actions-rs/cargo@v1 38 | with: 39 | command: test 40 | 41 | rustfmt_and_clippy: 42 | name: Check rustfmt style and run clippy 43 | runs-on: ubuntu-latest 44 | steps: 45 | - uses: actions/checkout@v3 46 | - uses: actions-rs/toolchain@v1 47 | with: 48 | toolchain: stable 49 | profile: minimal 50 | components: clippy, rustfmt 51 | override: true 52 | - name: Cache cargo registry 53 | uses: actions/cache@v3 54 | with: 55 | path: ~/.cargo/registry 56 | key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }} 57 | - name: Run clippy 58 | uses: actions-rs/cargo@v1 59 | with: 60 | command: clippy 61 | args: -- -D warnings 62 | - name: Check formating 63 | uses: actions-rs/cargo@v1 64 | with: 65 | command: fmt 66 | args: --all -- --check 67 | 68 | minimal_versions: 69 | name: Compile and test with minimal versions 70 | strategy: 71 | matrix: 72 | os: ['ubuntu-latest', 'macos-latest', 'windows-latest'] 73 | runs-on: ${{ matrix.os }} 74 | steps: 75 | - uses: actions/checkout@v3 76 | - name: Install latest nightly 77 | uses: actions-rs/toolchain@v1 78 | with: 79 | toolchain: nightly 80 | override: true 81 | - uses: taiki-e/install-action@cargo-hack 82 | - uses: taiki-e/install-action@cargo-minimal-versions 83 | - run: cargo minimal-versions check --workspace --all-features --ignore-private -v 84 | - run: cargo minimal-versions build --workspace --all-features --ignore-private -v 85 | - run: cargo minimal-versions test --workspace --all-features -v 86 | -------------------------------------------------------------------------------- /.github/workflows/grcov.yml: -------------------------------------------------------------------------------- 1 | name: coverage 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | jobs: 13 | grcov: 14 | name: Coverage 15 | runs-on: ${{ matrix.os }} 16 | strategy: 17 | matrix: 18 | os: 19 | - ubuntu-latest 20 | toolchain: 21 | - nightly 22 | cargo_flags: 23 | - "--all-features" 24 | steps: 25 | - name: Checkout source code 26 | uses: actions/checkout@v2 27 | 28 | - name: Install Rust 29 | uses: actions-rs/toolchain@v1 30 | with: 31 | profile: minimal 32 | toolchain: ${{ matrix.toolchain }} 33 | override: true 34 | 35 | - name: Install grcov 36 | uses: actions-rs/install@v0.1 37 | with: 38 | crate: grcov 39 | version: latest 40 | use-tool-cache: true 41 | 42 | - name: Test 43 | uses: actions-rs/cargo@v1 44 | with: 45 | command: test 46 | args: --all --no-fail-fast ${{ matrix.cargo_flags }} 47 | env: 48 | CARGO_INCREMENTAL: "0" 49 | RUSTFLAGS: '-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort -Cdebug-assertions=off' 50 | RUSTDOCFLAGS: '-Zprofile -Ccodegen-units=1 -Copt-level=0 -Clink-dead-code -Coverflow-checks=off -Zpanic_abort_tests -Cpanic=abort -Cdebug-assertions=off' 51 | 52 | - name: Generate coverage data 53 | id: grcov 54 | # uses: actions-rs/grcov@v0.1 55 | run: | 56 | grcov target/debug/ \ 57 | --branch \ 58 | --llvm \ 59 | --source-dir . \ 60 | --output-path lcov.info \ 61 | --ignore='/**' \ 62 | --ignore='C:/**' \ 63 | --ignore='../**' \ 64 | --ignore-not-existing \ 65 | --excl-line "#\\[derive\\(" \ 66 | --excl-br-line "#\\[derive\\(" \ 67 | --excl-start "#\\[cfg\\(test\\)\\]" \ 68 | --excl-br-start "#\\[cfg\\(test\\)\\]" \ 69 | --commit-sha ${{ github.sha }} \ 70 | --service-job-id ${{ github.job }} \ 71 | --service-name "GitHub Actions" \ 72 | --service-number ${{ github.run_id }} 73 | - name: Upload coverage as artifact 74 | uses: actions/upload-artifact@v2 75 | with: 76 | name: lcov.info 77 | # path: ${{ steps.grcov.outputs.report }} 78 | path: lcov.info 79 | 80 | - name: Upload coverage to codecov.io 81 | uses: codecov/codecov-action@v1 82 | with: 83 | # file: ${{ steps.grcov.outputs.report }} 84 | file: lcov.info 85 | fail_ci_if_error: true 86 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | /.idea/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "webrtc-srtp" 3 | version = "0.8.9" 4 | authors = ["Rain Liu "] 5 | edition = "2018" 6 | description = "A pure Rust implementation of SRTP" 7 | license = "MIT/Apache-2.0" 8 | documentation = "https://docs.rs/webrtc-srtp" 9 | homepage = "https://webrtc.rs" 10 | repository = "https://github.com/webrtc-rs/srtp" 11 | 12 | [dependencies] 13 | util = { package = "webrtc-util", version = "0.5.4", default-features = false, features = [ 14 | "conn", 15 | "buffer", 16 | "marshal", 17 | ] } 18 | rtp = "0.6.5" 19 | rtcp = "0.6.5" 20 | byteorder = "1" 21 | bytes = "1" 22 | thiserror = "1.0" 23 | hmac = { version = "0.11.0", features = ["std"] } 24 | sha-1 = "0.9.8" 25 | ctr = "0.8.0" 26 | aes = "0.7.5" 27 | subtle = "2.4" 28 | tokio = { version = "1.19", features = ["full"] } 29 | async-trait = "0.1.56" 30 | log = "0.4" 31 | aead = { version = "0.4.3", features = ["std"] } 32 | aes-gcm = "0.9.4" 33 | 34 | [dev-dependencies] 35 | tokio-test = "0.4.0" # must match the min version of the `tokio` crate above 36 | lazy_static = "1.4.0" 37 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 WebRTC.rs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Crate moved 2 | 3 | As of the 23rd of August 2022 this crate has been migrated to the [`webrtc-rs/webrtc`](http://github.com/webrtc-rs/webrtc/) monorepo. 4 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | codecov: 2 | require_ci_to_pass: yes 3 | max_report_age: off 4 | token: d65de923-7c3d-4836-8d9a-5183b356be4f 5 | 6 | coverage: 7 | precision: 2 8 | round: down 9 | range: 50..90 10 | status: 11 | project: 12 | default: 13 | enabled: no 14 | threshold: 0.2 15 | if_not_found: success 16 | patch: 17 | default: 18 | enabled: no 19 | if_not_found: success 20 | changes: 21 | default: 22 | enabled: no 23 | if_not_found: success 24 | -------------------------------------------------------------------------------- /doc/webrtc.rs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/webrtc-rs/srtp/33121993cd21e2e75adc1559052bf6901eecd95b/doc/webrtc.rs.png -------------------------------------------------------------------------------- /src/cipher/cipher_aead_aes_gcm.rs: -------------------------------------------------------------------------------- 1 | use aes_gcm::{ 2 | aead::{generic_array::GenericArray, Aead, NewAead, Payload}, 3 | Aes128Gcm, Nonce, 4 | }; 5 | use byteorder::{BigEndian, ByteOrder}; 6 | use bytes::{Bytes, BytesMut}; 7 | 8 | use super::Cipher; 9 | use crate::{ 10 | error::{Error, Result}, 11 | key_derivation::*, 12 | }; 13 | use util::marshal::*; 14 | 15 | pub const CIPHER_AEAD_AES_GCM_AUTH_TAG_LEN: usize = 16; 16 | 17 | const RTCP_ENCRYPTION_FLAG: u8 = 0x80; 18 | 19 | /// AEAD Cipher based on AES. 20 | pub(crate) struct CipherAeadAesGcm { 21 | srtp_cipher: aes_gcm::Aes128Gcm, 22 | srtcp_cipher: aes_gcm::Aes128Gcm, 23 | srtp_session_salt: Vec, 24 | srtcp_session_salt: Vec, 25 | } 26 | 27 | impl Cipher for CipherAeadAesGcm { 28 | fn auth_tag_len(&self) -> usize { 29 | CIPHER_AEAD_AES_GCM_AUTH_TAG_LEN 30 | } 31 | 32 | fn encrypt_rtp( 33 | &mut self, 34 | payload: &[u8], 35 | header: &rtp::header::Header, 36 | roc: u32, 37 | ) -> Result { 38 | // Grow the given buffer to fit the output. 39 | let mut writer = 40 | BytesMut::with_capacity(header.marshal_size() + payload.len() + self.auth_tag_len()); 41 | 42 | let data = header.marshal()?; 43 | writer.extend(data); 44 | 45 | let nonce = self.rtp_initialization_vector(header, roc); 46 | 47 | let encrypted = self.srtp_cipher.encrypt( 48 | Nonce::from_slice(&nonce), 49 | Payload { 50 | msg: payload, 51 | aad: &writer, 52 | }, 53 | )?; 54 | 55 | writer.extend(encrypted); 56 | Ok(writer.freeze()) 57 | } 58 | 59 | fn decrypt_rtp( 60 | &mut self, 61 | ciphertext: &[u8], 62 | header: &rtp::header::Header, 63 | roc: u32, 64 | ) -> Result { 65 | if ciphertext.len() < self.auth_tag_len() { 66 | return Err(Error::ErrFailedToVerifyAuthTag); 67 | } 68 | 69 | let nonce = self.rtp_initialization_vector(header, roc); 70 | let payload_offset = header.marshal_size(); 71 | let decrypted_msg: Vec = self.srtp_cipher.decrypt( 72 | Nonce::from_slice(&nonce), 73 | Payload { 74 | msg: &ciphertext[payload_offset..], 75 | aad: &ciphertext[..payload_offset], 76 | }, 77 | )?; 78 | 79 | let mut writer = BytesMut::with_capacity(payload_offset + decrypted_msg.len()); 80 | writer.extend_from_slice(&ciphertext[..payload_offset]); 81 | writer.extend(decrypted_msg); 82 | 83 | Ok(writer.freeze()) 84 | } 85 | 86 | fn encrypt_rtcp(&mut self, decrypted: &[u8], srtcp_index: usize, ssrc: u32) -> Result { 87 | let iv = self.rtcp_initialization_vector(srtcp_index, ssrc); 88 | let aad = self.rtcp_additional_authenticated_data(decrypted, srtcp_index); 89 | 90 | let encrypted_data = self.srtcp_cipher.encrypt( 91 | Nonce::from_slice(&iv), 92 | Payload { 93 | msg: &decrypted[8..], 94 | aad: &aad, 95 | }, 96 | )?; 97 | 98 | let mut writer = BytesMut::with_capacity(encrypted_data.len() + aad.len()); 99 | writer.extend_from_slice(&decrypted[..8]); 100 | writer.extend(encrypted_data); 101 | writer.extend_from_slice(&aad[8..]); 102 | 103 | Ok(writer.freeze()) 104 | } 105 | 106 | fn decrypt_rtcp(&mut self, encrypted: &[u8], srtcp_index: usize, ssrc: u32) -> Result { 107 | if encrypted.len() < self.auth_tag_len() + SRTCP_INDEX_SIZE { 108 | return Err(Error::ErrFailedToVerifyAuthTag); 109 | } 110 | 111 | let nonce = self.rtcp_initialization_vector(srtcp_index, ssrc); 112 | let aad = self.rtcp_additional_authenticated_data(encrypted, srtcp_index); 113 | 114 | let decrypted_data = self.srtcp_cipher.decrypt( 115 | Nonce::from_slice(&nonce), 116 | Payload { 117 | msg: &encrypted[8..(encrypted.len() - SRTCP_INDEX_SIZE)], 118 | aad: &aad, 119 | }, 120 | )?; 121 | 122 | let mut writer = BytesMut::with_capacity(8 + decrypted_data.len()); 123 | writer.extend_from_slice(&encrypted[..8]); 124 | writer.extend(decrypted_data); 125 | 126 | Ok(writer.freeze()) 127 | } 128 | 129 | fn get_rtcp_index(&self, input: &[u8]) -> usize { 130 | let pos = input.len() - 4; 131 | let val = BigEndian::read_u32(&input[pos..]); 132 | 133 | (val & !((RTCP_ENCRYPTION_FLAG as u32) << 24)) as usize 134 | } 135 | } 136 | 137 | impl CipherAeadAesGcm { 138 | /// Create a new AEAD instance. 139 | pub(crate) fn new(master_key: &[u8], master_salt: &[u8]) -> Result { 140 | let srtp_session_key = aes_cm_key_derivation( 141 | LABEL_SRTP_ENCRYPTION, 142 | master_key, 143 | master_salt, 144 | 0, 145 | master_key.len(), 146 | )?; 147 | 148 | let srtp_block = GenericArray::from_slice(&srtp_session_key); 149 | 150 | let srtp_cipher = Aes128Gcm::new(srtp_block); 151 | 152 | let srtcp_session_key = aes_cm_key_derivation( 153 | LABEL_SRTCP_ENCRYPTION, 154 | master_key, 155 | master_salt, 156 | 0, 157 | master_key.len(), 158 | )?; 159 | 160 | let srtcp_block = GenericArray::from_slice(&srtcp_session_key); 161 | 162 | let srtcp_cipher = Aes128Gcm::new(srtcp_block); 163 | 164 | let srtp_session_salt = aes_cm_key_derivation( 165 | LABEL_SRTP_SALT, 166 | master_key, 167 | master_salt, 168 | 0, 169 | master_key.len(), 170 | )?; 171 | 172 | let srtcp_session_salt = aes_cm_key_derivation( 173 | LABEL_SRTCP_SALT, 174 | master_key, 175 | master_salt, 176 | 0, 177 | master_key.len(), 178 | )?; 179 | 180 | Ok(CipherAeadAesGcm { 181 | srtp_cipher, 182 | srtcp_cipher, 183 | srtp_session_salt, 184 | srtcp_session_salt, 185 | }) 186 | } 187 | 188 | /// The 12-octet IV used by AES-GCM SRTP is formed by first concatenating 189 | /// 2 octets of zeroes, the 4-octet SSRC, the 4-octet rollover counter 190 | /// (ROC), and the 2-octet sequence number (SEQ). The resulting 12-octet 191 | /// value is then XORed to the 12-octet salt to form the 12-octet IV. 192 | /// 193 | /// https://tools.ietf.org/html/rfc7714#section-8.1 194 | pub(crate) fn rtp_initialization_vector( 195 | &self, 196 | header: &rtp::header::Header, 197 | roc: u32, 198 | ) -> Vec { 199 | let mut iv = vec![0u8; 12]; 200 | BigEndian::write_u32(&mut iv[2..], header.ssrc); 201 | BigEndian::write_u32(&mut iv[6..], roc); 202 | BigEndian::write_u16(&mut iv[10..], header.sequence_number); 203 | 204 | for (i, v) in iv.iter_mut().enumerate() { 205 | *v ^= self.srtp_session_salt[i]; 206 | } 207 | 208 | iv 209 | } 210 | 211 | /// The 12-octet IV used by AES-GCM SRTCP is formed by first 212 | /// concatenating 2 octets of zeroes, the 4-octet SSRC identifier, 213 | /// 2 octets of zeroes, a single "0" bit, and the 31-bit SRTCP index. 214 | /// The resulting 12-octet value is then XORed to the 12-octet salt to 215 | /// form the 12-octet IV. 216 | /// 217 | /// https://tools.ietf.org/html/rfc7714#section-9.1 218 | pub(crate) fn rtcp_initialization_vector(&self, srtcp_index: usize, ssrc: u32) -> Vec { 219 | let mut iv = vec![0u8; 12]; 220 | 221 | BigEndian::write_u32(&mut iv[2..], ssrc); 222 | BigEndian::write_u32(&mut iv[8..], srtcp_index as u32); 223 | 224 | for (i, v) in iv.iter_mut().enumerate() { 225 | *v ^= self.srtcp_session_salt[i]; 226 | } 227 | 228 | iv 229 | } 230 | 231 | /// In an SRTCP packet, a 1-bit Encryption flag is prepended to the 232 | /// 31-bit SRTCP index to form a 32-bit value we shall call the 233 | /// "ESRTCP word" 234 | /// 235 | /// https://tools.ietf.org/html/rfc7714#section-17 236 | pub(crate) fn rtcp_additional_authenticated_data( 237 | &self, 238 | rtcp_packet: &[u8], 239 | srtcp_index: usize, 240 | ) -> Vec { 241 | let mut aad = vec![0u8; 12]; 242 | 243 | aad[..8].copy_from_slice(&rtcp_packet[..8]); 244 | 245 | BigEndian::write_u32(&mut aad[8..], srtcp_index as u32); 246 | 247 | aad[8] |= RTCP_ENCRYPTION_FLAG; 248 | aad 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/cipher/cipher_aes_cm_hmac_sha1.rs: -------------------------------------------------------------------------------- 1 | use super::Cipher; 2 | use crate::error::Result; 3 | use crate::{error::Error, key_derivation::*, protection_profile::*}; 4 | use util::marshal::*; 5 | 6 | use aes::cipher::generic_array::GenericArray; 7 | use byteorder::{BigEndian, ByteOrder, WriteBytesExt}; 8 | use bytes::{BufMut, Bytes, BytesMut}; 9 | use ctr::cipher::{NewCipher, StreamCipher, StreamCipherSeek}; 10 | use hmac::{Hmac, Mac, NewMac}; 11 | use sha1::Sha1; 12 | use std::io::BufWriter; 13 | use subtle::ConstantTimeEq; 14 | 15 | type HmacSha1 = Hmac; 16 | type Aes128Ctr = ctr::Ctr128BE; 17 | 18 | pub const CIPHER_AES_CM_HMAC_SHA1AUTH_TAG_LEN: usize = 10; 19 | 20 | pub(crate) struct CipherAesCmHmacSha1 { 21 | srtp_session_key: Vec, 22 | srtp_session_salt: Vec, 23 | srtp_session_auth: HmacSha1, 24 | //srtp_session_auth_tag: Vec, 25 | srtcp_session_key: Vec, 26 | srtcp_session_salt: Vec, 27 | srtcp_session_auth: HmacSha1, 28 | //srtcp_session_auth_tag: Vec, 29 | } 30 | 31 | impl CipherAesCmHmacSha1 { 32 | pub fn new(master_key: &[u8], master_salt: &[u8]) -> Result { 33 | let srtp_session_key = aes_cm_key_derivation( 34 | LABEL_SRTP_ENCRYPTION, 35 | master_key, 36 | master_salt, 37 | 0, 38 | master_key.len(), 39 | )?; 40 | let srtcp_session_key = aes_cm_key_derivation( 41 | LABEL_SRTCP_ENCRYPTION, 42 | master_key, 43 | master_salt, 44 | 0, 45 | master_key.len(), 46 | )?; 47 | 48 | let srtp_session_salt = aes_cm_key_derivation( 49 | LABEL_SRTP_SALT, 50 | master_key, 51 | master_salt, 52 | 0, 53 | master_salt.len(), 54 | )?; 55 | let srtcp_session_salt = aes_cm_key_derivation( 56 | LABEL_SRTCP_SALT, 57 | master_key, 58 | master_salt, 59 | 0, 60 | master_salt.len(), 61 | )?; 62 | 63 | let auth_key_len = ProtectionProfile::Aes128CmHmacSha1_80.auth_key_len(); 64 | 65 | let srtp_session_auth_tag = aes_cm_key_derivation( 66 | LABEL_SRTP_AUTHENTICATION_TAG, 67 | master_key, 68 | master_salt, 69 | 0, 70 | auth_key_len, 71 | )?; 72 | let srtcp_session_auth_tag = aes_cm_key_derivation( 73 | LABEL_SRTCP_AUTHENTICATION_TAG, 74 | master_key, 75 | master_salt, 76 | 0, 77 | auth_key_len, 78 | )?; 79 | 80 | let srtp_session_auth = HmacSha1::new_from_slice(&srtp_session_auth_tag) 81 | .map_err(|e| Error::Other(e.to_string()))?; 82 | let srtcp_session_auth = HmacSha1::new_from_slice(&srtcp_session_auth_tag) 83 | .map_err(|e| Error::Other(e.to_string()))?; 84 | 85 | Ok(CipherAesCmHmacSha1 { 86 | srtp_session_key, 87 | srtp_session_salt, 88 | srtp_session_auth, 89 | //srtp_session_auth_tag, 90 | srtcp_session_key, 91 | srtcp_session_salt, 92 | srtcp_session_auth, 93 | //srtcp_session_auth_tag, 94 | }) 95 | } 96 | 97 | /// https://tools.ietf.org/html/rfc3711#section-4.2 98 | /// In the case of SRTP, M SHALL consist of the Authenticated 99 | /// Portion of the packet (as specified in Figure 1) concatenated with 100 | /// the roc, M = Authenticated Portion || roc; 101 | /// 102 | /// The pre-defined authentication transform for SRTP is HMAC-SHA1 103 | /// [RFC2104]. With HMAC-SHA1, the SRTP_PREFIX_LENGTH (Figure 3) SHALL 104 | /// be 0. For SRTP (respectively SRTCP), the HMAC SHALL be applied to 105 | /// the session authentication key and M as specified above, i.e., 106 | /// HMAC(k_a, M). The HMAC output SHALL then be truncated to the n_tag 107 | /// left-most bits. 108 | /// - Authenticated portion of the packet is everything BEFORE MKI 109 | /// - k_a is the session message authentication key 110 | /// - n_tag is the bit-length of the output authentication tag 111 | fn generate_srtp_auth_tag(&mut self, buf: &[u8], roc: u32) -> Result> { 112 | self.srtp_session_auth.reset(); 113 | 114 | self.srtp_session_auth.update(buf); 115 | 116 | // For SRTP only, we need to hash the rollover counter as well. 117 | let mut roc_buf: Vec = vec![]; 118 | { 119 | let mut writer = BufWriter::<&mut Vec>::new(roc_buf.as_mut()); 120 | writer.write_u32::(roc)?; 121 | } 122 | 123 | self.srtp_session_auth.update(&roc_buf); 124 | 125 | let result = self.srtp_session_auth.clone().finalize(); 126 | let code_bytes = result.into_bytes(); 127 | 128 | // Truncate the hash to the first AUTH_TAG_SIZE bytes. 129 | Ok(code_bytes[0..self.auth_tag_len()].to_vec()) 130 | } 131 | 132 | /// https://tools.ietf.org/html/rfc3711#section-4.2 133 | /// 134 | /// The pre-defined authentication transform for SRTP is HMAC-SHA1 135 | /// [RFC2104]. With HMAC-SHA1, the SRTP_PREFIX_LENGTH (Figure 3) SHALL 136 | /// be 0. For SRTP (respectively SRTCP), the HMAC SHALL be applied to 137 | /// the session authentication key and M as specified above, i.e., 138 | /// HMAC(k_a, M). The HMAC output SHALL then be truncated to the n_tag 139 | /// left-most bits. 140 | /// - Authenticated portion of the packet is everything BEFORE MKI 141 | /// - k_a is the session message authentication key 142 | /// - n_tag is the bit-length of the output authentication tag 143 | fn generate_srtcp_auth_tag(&mut self, buf: &[u8]) -> Vec { 144 | self.srtcp_session_auth.reset(); 145 | 146 | self.srtcp_session_auth.update(buf); 147 | 148 | let result = self.srtcp_session_auth.clone().finalize(); 149 | let code_bytes = result.into_bytes(); 150 | 151 | // Truncate the hash to the first AUTH_TAG_SIZE bytes. 152 | code_bytes[0..self.auth_tag_len()].to_vec() 153 | } 154 | } 155 | 156 | impl Cipher for CipherAesCmHmacSha1 { 157 | fn auth_tag_len(&self) -> usize { 158 | CIPHER_AES_CM_HMAC_SHA1AUTH_TAG_LEN 159 | } 160 | 161 | fn get_rtcp_index(&self, input: &[u8]) -> usize { 162 | let tail_offset = input.len() - (self.auth_tag_len() + SRTCP_INDEX_SIZE); 163 | (BigEndian::read_u32(&input[tail_offset..tail_offset + SRTCP_INDEX_SIZE]) & !(1 << 31)) 164 | as usize 165 | } 166 | 167 | fn encrypt_rtp( 168 | &mut self, 169 | payload: &[u8], 170 | header: &rtp::header::Header, 171 | roc: u32, 172 | ) -> Result { 173 | let mut writer = 174 | BytesMut::with_capacity(header.marshal_size() + payload.len() + self.auth_tag_len()); 175 | 176 | // Copy the header unencrypted. 177 | let data = header.marshal()?; 178 | writer.extend(data); 179 | 180 | // Write the plaintext header to the destination buffer. 181 | writer.extend_from_slice(payload); 182 | 183 | // Encrypt the payload 184 | let counter = generate_counter( 185 | header.sequence_number, 186 | roc, 187 | header.ssrc, 188 | &self.srtp_session_salt, 189 | )?; 190 | let key = GenericArray::from_slice(&self.srtp_session_key); 191 | let nonce = GenericArray::from_slice(&counter); 192 | let mut stream = Aes128Ctr::new(key, nonce); 193 | let payload_offset = header.marshal_size(); 194 | stream.apply_keystream(&mut writer[payload_offset..]); 195 | 196 | // Generate the auth tag. 197 | let auth_tag = self.generate_srtp_auth_tag(&writer, roc)?; 198 | writer.extend(auth_tag); 199 | 200 | Ok(writer.freeze()) 201 | } 202 | 203 | fn decrypt_rtp( 204 | &mut self, 205 | encrypted: &[u8], 206 | header: &rtp::header::Header, 207 | roc: u32, 208 | ) -> Result { 209 | if encrypted.len() < self.auth_tag_len() { 210 | return Err(Error::SrtpTooSmall(encrypted.len(), self.auth_tag_len())); 211 | } 212 | 213 | let mut writer = BytesMut::with_capacity(encrypted.len() - self.auth_tag_len()); 214 | 215 | // Split the auth tag and the cipher text into two parts. 216 | let actual_tag = &encrypted[encrypted.len() - self.auth_tag_len()..]; 217 | let cipher_text = &encrypted[..encrypted.len() - self.auth_tag_len()]; 218 | 219 | // Generate the auth tag we expect to see from the ciphertext. 220 | let expected_tag = self.generate_srtp_auth_tag(cipher_text, roc)?; 221 | 222 | // See if the auth tag actually matches. 223 | // We use a constant time comparison to prevent timing attacks. 224 | if actual_tag.ct_eq(&expected_tag).unwrap_u8() != 1 { 225 | return Err(Error::RtpFailedToVerifyAuthTag); 226 | } 227 | 228 | // Write cipher_text to the destination buffer. 229 | writer.extend_from_slice(cipher_text); 230 | 231 | // Decrypt the ciphertext for the payload. 232 | let counter = generate_counter( 233 | header.sequence_number, 234 | roc, 235 | header.ssrc, 236 | &self.srtp_session_salt, 237 | )?; 238 | 239 | let key = GenericArray::from_slice(&self.srtp_session_key); 240 | let nonce = GenericArray::from_slice(&counter); 241 | let mut stream = Aes128Ctr::new(key, nonce); 242 | let payload_offset = header.marshal_size(); 243 | stream.seek(0); 244 | stream.apply_keystream(&mut writer[payload_offset..]); 245 | 246 | Ok(writer.freeze()) 247 | } 248 | 249 | fn encrypt_rtcp(&mut self, decrypted: &[u8], srtcp_index: usize, ssrc: u32) -> Result { 250 | let mut writer = 251 | BytesMut::with_capacity(decrypted.len() + SRTCP_INDEX_SIZE + self.auth_tag_len()); 252 | 253 | // Write the decrypted to the destination buffer. 254 | writer.extend_from_slice(decrypted); 255 | 256 | // Encrypt everything after header 257 | let counter = generate_counter( 258 | (srtcp_index & 0xFFFF) as u16, 259 | (srtcp_index >> 16) as u32, 260 | ssrc, 261 | &self.srtcp_session_salt, 262 | )?; 263 | 264 | let key = GenericArray::from_slice(&self.srtcp_session_key); 265 | let nonce = GenericArray::from_slice(&counter); 266 | let mut stream = Aes128Ctr::new(key, nonce); 267 | 268 | stream.apply_keystream( 269 | &mut writer[rtcp::header::HEADER_LENGTH + rtcp::header::SSRC_LENGTH..], 270 | ); 271 | 272 | // Add SRTCP index and set Encryption bit 273 | writer.put_u32(srtcp_index as u32 | (1u32 << 31)); 274 | 275 | // Generate the auth tag. 276 | let auth_tag = self.generate_srtcp_auth_tag(&writer); 277 | writer.extend(auth_tag); 278 | 279 | Ok(writer.freeze()) 280 | } 281 | 282 | fn decrypt_rtcp(&mut self, encrypted: &[u8], srtcp_index: usize, ssrc: u32) -> Result { 283 | if encrypted.len() < self.auth_tag_len() + SRTCP_INDEX_SIZE { 284 | return Err(Error::SrtcpTooSmall( 285 | encrypted.len(), 286 | self.auth_tag_len() + SRTCP_INDEX_SIZE, 287 | )); 288 | } 289 | 290 | let tail_offset = encrypted.len() - (self.auth_tag_len() + SRTCP_INDEX_SIZE); 291 | 292 | let mut writer = BytesMut::with_capacity(tail_offset); 293 | 294 | writer.extend_from_slice(&encrypted[0..tail_offset]); 295 | 296 | let is_encrypted = encrypted[tail_offset] >> 7; 297 | if is_encrypted == 0 { 298 | return Ok(writer.freeze()); 299 | } 300 | 301 | // Split the auth tag and the cipher text into two parts. 302 | let actual_tag = &encrypted[encrypted.len() - self.auth_tag_len()..]; 303 | let cipher_text = &encrypted[..encrypted.len() - self.auth_tag_len()]; 304 | 305 | // Generate the auth tag we expect to see from the ciphertext. 306 | let expected_tag = self.generate_srtcp_auth_tag(cipher_text); 307 | 308 | // See if the auth tag actually matches. 309 | // We use a constant time comparison to prevent timing attacks. 310 | if actual_tag.ct_eq(&expected_tag).unwrap_u8() != 1 { 311 | return Err(Error::RtcpFailedToVerifyAuthTag); 312 | } 313 | 314 | let counter = generate_counter( 315 | (srtcp_index & 0xFFFF) as u16, 316 | (srtcp_index >> 16) as u32, 317 | ssrc, 318 | &self.srtcp_session_salt, 319 | )?; 320 | 321 | let key = GenericArray::from_slice(&self.srtcp_session_key); 322 | let nonce = GenericArray::from_slice(&counter); 323 | let mut stream = Aes128Ctr::new(key, nonce); 324 | 325 | stream.seek(0); 326 | stream.apply_keystream( 327 | &mut writer[rtcp::header::HEADER_LENGTH + rtcp::header::SSRC_LENGTH..], 328 | ); 329 | 330 | Ok(writer.freeze()) 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /src/cipher/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod cipher_aead_aes_gcm; 2 | pub mod cipher_aes_cm_hmac_sha1; 3 | 4 | use bytes::Bytes; 5 | 6 | use crate::error::Result; 7 | 8 | ///NOTE: Auth tag and AEAD auth tag are placed at the different position in SRTCP 9 | /// 10 | ///In non-AEAD cipher, the authentication tag is placed *after* the ESRTCP word 11 | ///(Encrypted-flag and SRTCP index). 12 | /// 13 | ///> AES_128_CM_HMAC_SHA1_80 14 | ///> | RTCP Header | Encrypted payload |E| SRTCP Index | Auth tag | 15 | ///> ^ |----------| 16 | ///> | ^ 17 | ///> | authTagLen=10 18 | ///> aeadAuthTagLen=0 19 | /// 20 | ///In AEAD cipher, the AEAD authentication tag is embedded in the ciphertext. 21 | ///It is *before* the ESRTCP word (Encrypted-flag and SRTCP index). 22 | /// 23 | ///> AEAD_AES_128_GCM 24 | ///> | RTCP Header | Encrypted payload | AEAD auth tag |E| SRTCP Index | 25 | ///> |---------------| ^ 26 | ///> ^ authTagLen=0 27 | ///> aeadAuthTagLen=16 28 | /// 29 | ///See https://tools.ietf.org/html/rfc7714 for the full specifications. 30 | 31 | /// Cipher represents a implementation of one 32 | /// of the SRTP Specific ciphers. 33 | pub(crate) trait Cipher { 34 | /// Get authenticated tag length. 35 | fn auth_tag_len(&self) -> usize; 36 | 37 | /// Retrieved RTCP index. 38 | fn get_rtcp_index(&self, input: &[u8]) -> usize; 39 | 40 | /// Encrypt RTP payload. 41 | fn encrypt_rtp( 42 | &mut self, 43 | payload: &[u8], 44 | header: &rtp::header::Header, 45 | roc: u32, 46 | ) -> Result; 47 | 48 | /// Decrypt RTP payload. 49 | fn decrypt_rtp( 50 | &mut self, 51 | payload: &[u8], 52 | header: &rtp::header::Header, 53 | roc: u32, 54 | ) -> Result; 55 | 56 | /// Encrypt RTCP payload. 57 | fn encrypt_rtcp(&mut self, payload: &[u8], srtcp_index: usize, ssrc: u32) -> Result; 58 | 59 | /// Decrypt RTCP payload. 60 | fn decrypt_rtcp(&mut self, payload: &[u8], srtcp_index: usize, ssrc: u32) -> Result; 61 | } 62 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Result; 2 | use crate::{option::*, protection_profile::*}; 3 | use util::KeyingMaterialExporter; 4 | 5 | const LABEL_EXTRACTOR_DTLS_SRTP: &str = "EXTRACTOR-dtls_srtp"; 6 | 7 | /// SessionKeys bundles the keys required to setup an SRTP session 8 | #[derive(Default, Debug, Clone)] 9 | pub struct SessionKeys { 10 | pub local_master_key: Vec, 11 | pub local_master_salt: Vec, 12 | pub remote_master_key: Vec, 13 | pub remote_master_salt: Vec, 14 | } 15 | 16 | /// Config is used to configure a session. 17 | /// You can provide either a KeyingMaterialExporter to export keys 18 | /// or directly pass the keys themselves. 19 | /// After a Config is passed to a session it must not be modified. 20 | #[derive(Default)] 21 | pub struct Config { 22 | pub keys: SessionKeys, 23 | pub profile: ProtectionProfile, 24 | //LoggerFactory: logging.LoggerFactory 25 | /// List of local/remote context options. 26 | /// ReplayProtection is enabled on remote context by default. 27 | /// Default replay protection window size is 64. 28 | pub local_rtp_options: Option, 29 | pub remote_rtp_options: Option, 30 | 31 | pub local_rtcp_options: Option, 32 | pub remote_rtcp_options: Option, 33 | } 34 | 35 | impl Config { 36 | /// ExtractSessionKeysFromDTLS allows setting the Config SessionKeys by 37 | /// extracting them from DTLS. This behavior is defined in RFC5764: 38 | /// https://tools.ietf.org/html/rfc5764 39 | pub async fn extract_session_keys_from_dtls( 40 | &mut self, 41 | exporter: impl KeyingMaterialExporter, 42 | is_client: bool, 43 | ) -> Result<()> { 44 | let key_len = self.profile.key_len(); 45 | let salt_len = self.profile.salt_len(); 46 | 47 | let keying_material = exporter 48 | .export_keying_material( 49 | LABEL_EXTRACTOR_DTLS_SRTP, 50 | &[], 51 | (key_len * 2) + (salt_len * 2), 52 | ) 53 | .await?; 54 | 55 | let mut offset = 0; 56 | let client_write_key = keying_material[offset..offset + key_len].to_vec(); 57 | offset += key_len; 58 | 59 | let server_write_key = keying_material[offset..offset + key_len].to_vec(); 60 | offset += key_len; 61 | 62 | let client_write_salt = keying_material[offset..offset + salt_len].to_vec(); 63 | offset += salt_len; 64 | 65 | let server_write_salt = keying_material[offset..offset + salt_len].to_vec(); 66 | 67 | if is_client { 68 | self.keys.local_master_key = client_write_key; 69 | self.keys.local_master_salt = client_write_salt; 70 | self.keys.remote_master_key = server_write_key; 71 | self.keys.remote_master_salt = server_write_salt; 72 | } else { 73 | self.keys.local_master_key = server_write_key; 74 | self.keys.local_master_salt = server_write_salt; 75 | self.keys.remote_master_key = client_write_key; 76 | self.keys.remote_master_salt = client_write_salt; 77 | } 78 | 79 | Ok(()) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/context/context_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::key_derivation::*; 3 | 4 | use bytes::Bytes; 5 | use lazy_static::lazy_static; 6 | 7 | const CIPHER_CONTEXT_ALGO: ProtectionProfile = ProtectionProfile::Aes128CmHmacSha1_80; 8 | const DEFAULT_SSRC: u32 = 0; 9 | 10 | #[test] 11 | fn test_context_roc() -> Result<()> { 12 | let key_len = CIPHER_CONTEXT_ALGO.key_len(); 13 | let salt_len = CIPHER_CONTEXT_ALGO.salt_len(); 14 | 15 | let mut c = Context::new( 16 | &vec![0; key_len], 17 | &vec![0; salt_len], 18 | CIPHER_CONTEXT_ALGO, 19 | None, 20 | None, 21 | )?; 22 | 23 | let roc = c.get_roc(123); 24 | assert!(roc.is_none(), "ROC must return None for unused SSRC"); 25 | 26 | c.set_roc(123, 100); 27 | let roc = c.get_roc(123); 28 | if let Some(r) = roc { 29 | assert_eq!(r, 100, "ROC is set to 100, but returned {}", r) 30 | } else { 31 | assert!(false, "ROC must return value for used SSRC"); 32 | } 33 | 34 | Ok(()) 35 | } 36 | 37 | #[test] 38 | fn test_context_index() -> Result<()> { 39 | let key_len = CIPHER_CONTEXT_ALGO.key_len(); 40 | let salt_len = CIPHER_CONTEXT_ALGO.salt_len(); 41 | 42 | let mut c = Context::new( 43 | &vec![0; key_len], 44 | &vec![0; salt_len], 45 | CIPHER_CONTEXT_ALGO, 46 | None, 47 | None, 48 | )?; 49 | 50 | let index = c.get_index(123); 51 | assert!(index.is_none(), "Index must return None for unused SSRC"); 52 | 53 | c.set_index(123, 100); 54 | let index = c.get_index(123); 55 | if let Some(i) = index { 56 | assert_eq!(i, 100, "Index is set to 100, but returned {}", i); 57 | } else { 58 | assert!(false, "Index must return true for used SSRC") 59 | } 60 | 61 | Ok(()) 62 | } 63 | 64 | #[test] 65 | fn test_key_len() -> Result<()> { 66 | let key_len = CIPHER_CONTEXT_ALGO.key_len(); 67 | let salt_len = CIPHER_CONTEXT_ALGO.salt_len(); 68 | 69 | let result = Context::new(&[], &vec![0; salt_len], CIPHER_CONTEXT_ALGO, None, None); 70 | assert!(result.is_err(), "CreateContext accepted a 0 length key"); 71 | 72 | let result = Context::new(&vec![0; key_len], &[], CIPHER_CONTEXT_ALGO, None, None); 73 | assert!(result.is_err(), "CreateContext accepted a 0 length salt"); 74 | 75 | let result = Context::new( 76 | &vec![0; key_len], 77 | &vec![0; salt_len], 78 | CIPHER_CONTEXT_ALGO, 79 | None, 80 | None, 81 | ); 82 | assert!( 83 | result.is_ok(), 84 | "CreateContext failed with a valid length key and salt" 85 | ); 86 | 87 | Ok(()) 88 | } 89 | 90 | #[test] 91 | fn test_valid_packet_counter() -> Result<()> { 92 | let master_key = vec![ 93 | 0x0d, 0xcd, 0x21, 0x3e, 0x4c, 0xbc, 0xf2, 0x8f, 0x01, 0x7f, 0x69, 0x94, 0x40, 0x1e, 0x28, 94 | 0x89, 95 | ]; 96 | let master_salt = vec![ 97 | 0x62, 0x77, 0x60, 0x38, 0xc0, 0x6d, 0xc9, 0x41, 0x9f, 0x6d, 0xd9, 0x43, 0x3e, 0x7c, 98 | ]; 99 | 100 | let srtp_session_salt = aes_cm_key_derivation( 101 | LABEL_SRTP_SALT, 102 | &master_key, 103 | &master_salt, 104 | 0, 105 | master_salt.len(), 106 | )?; 107 | 108 | let s = SrtpSsrcState { 109 | ssrc: 4160032510, 110 | ..Default::default() 111 | }; 112 | let expected_counter = vec![ 113 | 0xcf, 0x90, 0x1e, 0xa5, 0xda, 0xd3, 0x2c, 0x15, 0x00, 0xa2, 0x24, 0xae, 0xae, 0xaf, 0x00, 114 | 0x00, 115 | ]; 116 | let counter = generate_counter(32846, s.rollover_counter, s.ssrc, &srtp_session_salt)?; 117 | assert_eq!( 118 | counter, expected_counter, 119 | "Session Key {:?} does not match expected {:?}", 120 | counter, expected_counter, 121 | ); 122 | 123 | Ok(()) 124 | } 125 | 126 | #[test] 127 | fn test_rollover_count() -> Result<()> { 128 | let mut s = SrtpSsrcState { 129 | ssrc: DEFAULT_SSRC, 130 | ..Default::default() 131 | }; 132 | 133 | // Set initial seqnum 134 | let roc = s.next_rollover_count(65530); 135 | assert_eq!(roc, 0, "Initial rolloverCounter must be 0"); 136 | s.update_rollover_count(65530); 137 | 138 | // Invalid packets never update ROC 139 | s.next_rollover_count(0); 140 | s.next_rollover_count(0x4000); 141 | s.next_rollover_count(0x8000); 142 | s.next_rollover_count(0xFFFF); 143 | s.next_rollover_count(0); 144 | 145 | // We rolled over to 0 146 | let roc = s.next_rollover_count(0); 147 | assert_eq!(roc, 1, "rolloverCounter was not updated after it crossed 0"); 148 | s.update_rollover_count(0); 149 | 150 | let roc = s.next_rollover_count(65530); 151 | assert_eq!( 152 | roc, 0, 153 | "rolloverCounter was not updated when it rolled back, failed to handle out of order" 154 | ); 155 | s.update_rollover_count(65530); 156 | 157 | let roc = s.next_rollover_count(5); 158 | assert_eq!( 159 | roc, 1, 160 | "rolloverCounter was not updated when it rolled over initial, to handle out of order" 161 | ); 162 | s.update_rollover_count(5); 163 | 164 | s.next_rollover_count(6); 165 | s.update_rollover_count(6); 166 | 167 | s.next_rollover_count(7); 168 | s.update_rollover_count(7); 169 | 170 | let roc = s.next_rollover_count(8); 171 | assert_eq!( 172 | roc, 1, 173 | "rolloverCounter was improperly updated for non-significant packets" 174 | ); 175 | s.update_rollover_count(8); 176 | 177 | // valid packets never update ROC 178 | let roc = s.next_rollover_count(0x4000); 179 | assert_eq!( 180 | roc, 1, 181 | "rolloverCounter was improperly updated for non-significant packets" 182 | ); 183 | s.update_rollover_count(0x4000); 184 | 185 | let roc = s.next_rollover_count(0x8000); 186 | assert_eq!( 187 | roc, 1, 188 | "rolloverCounter was improperly updated for non-significant packets" 189 | ); 190 | s.update_rollover_count(0x8000); 191 | 192 | let roc = s.next_rollover_count(0xFFFF); 193 | assert_eq!( 194 | roc, 1, 195 | "rolloverCounter was improperly updated for non-significant packets" 196 | ); 197 | s.update_rollover_count(0xFFFF); 198 | 199 | let roc = s.next_rollover_count(0); 200 | assert_eq!( 201 | roc, 2, 202 | "rolloverCounter must be incremented after wrapping, got {}", 203 | roc 204 | ); 205 | 206 | Ok(()) 207 | } 208 | 209 | lazy_static! { 210 | static ref MASTER_KEY: Bytes = Bytes::from_static(&[ 211 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 212 | 0x0f, 213 | ]); 214 | static ref MASTER_SALT: Bytes = Bytes::from_static(&[ 215 | 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 216 | ]); 217 | static ref DECRYPTED_RTP_PACKET: Bytes = Bytes::from_static(&[ 218 | 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 219 | 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 220 | ]); 221 | static ref ENCRYPTED_RTP_PACKET: Bytes = Bytes::from_static(&[ 222 | 0x80, 0x0f, 0x12, 0x34, 0xde, 0xca, 0xfb, 0xad, 0xca, 0xfe, 0xba, 0xbe, 0xc5, 0x00, 0x2e, 223 | 0xde, 0x04, 0xcf, 0xdd, 0x2e, 0xb9, 0x11, 0x59, 0xe0, 0x88, 0x0a, 0xa0, 0x6e, 0xd2, 0x97, 224 | 0x68, 0x26, 0xf7, 0x96, 0xb2, 0x01, 0xdf, 0x31, 0x31, 0xa1, 0x27, 0xe8, 0xa3, 0x92, 225 | ]); 226 | static ref DECRYPTED_RTCP_PACKET: Bytes = Bytes::from_static(&[ 227 | 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 228 | 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 0xab, 229 | ]); 230 | static ref ENCRYPTED_RTCP_PACKET: Bytes = Bytes::from_static(&[ 231 | 0x81, 0xc8, 0x00, 0x0b, 0xca, 0xfe, 0xba, 0xbe, 0xc9, 0x8b, 0x8b, 0x5d, 0xf0, 0x39, 0x2a, 232 | 0x55, 0x85, 0x2b, 0x6c, 0x21, 0xac, 0x8e, 0x70, 0x25, 0xc5, 0x2c, 0x6f, 0xbe, 0xa2, 0xb3, 233 | 0xb4, 0x46, 0xea, 0x31, 0x12, 0x3b, 0xa8, 0x8c, 0xe6, 0x1e, 0x80, 0x00, 0x00, 0x01, 234 | ]); 235 | } 236 | 237 | #[test] 238 | fn test_encrypt_rtp() { 239 | let mut ctx = Context::new( 240 | &MASTER_KEY, 241 | &MASTER_SALT, 242 | ProtectionProfile::AeadAes128Gcm, 243 | None, 244 | None, 245 | ) 246 | .expect("Error creating srtp context"); 247 | 248 | let gotten_encrypted_rtp_packet = ctx 249 | .encrypt_rtp(&DECRYPTED_RTP_PACKET) 250 | .expect("Error encrypting rtp payload"); 251 | 252 | assert_eq!(gotten_encrypted_rtp_packet, *ENCRYPTED_RTP_PACKET) 253 | } 254 | 255 | #[test] 256 | fn test_decrypt_rtp() { 257 | let mut ctx = Context::new( 258 | &MASTER_KEY, 259 | &MASTER_SALT, 260 | ProtectionProfile::AeadAes128Gcm, 261 | None, 262 | None, 263 | ) 264 | .expect("Error creating srtp context"); 265 | 266 | let gotten_decrypted_rtp_packet = ctx 267 | .decrypt_rtp(&ENCRYPTED_RTP_PACKET) 268 | .expect("Error decrypting rtp payload"); 269 | 270 | assert_eq!(gotten_decrypted_rtp_packet, *DECRYPTED_RTP_PACKET) 271 | } 272 | 273 | #[test] 274 | fn test_encrypt_rtcp() { 275 | let mut ctx = Context::new( 276 | &MASTER_KEY, 277 | &MASTER_SALT, 278 | ProtectionProfile::AeadAes128Gcm, 279 | None, 280 | None, 281 | ) 282 | .expect("Error creating srtp context"); 283 | 284 | let gotten_encrypted_rtcp_packet = ctx 285 | .encrypt_rtcp(&DECRYPTED_RTCP_PACKET) 286 | .expect("Error encrypting rtcp payload"); 287 | 288 | assert_eq!(gotten_encrypted_rtcp_packet, *ENCRYPTED_RTCP_PACKET) 289 | } 290 | 291 | #[test] 292 | fn test_decrypt_rtcp() { 293 | let mut ctx = Context::new( 294 | &MASTER_KEY, 295 | &MASTER_SALT, 296 | ProtectionProfile::AeadAes128Gcm, 297 | None, 298 | None, 299 | ) 300 | .expect("Error creating srtp context"); 301 | 302 | let gotten_decrypted_rtcp_packet = ctx 303 | .decrypt_rtcp(&ENCRYPTED_RTCP_PACKET) 304 | .expect("Error decrypting rtcp payload"); 305 | 306 | assert_eq!(gotten_decrypted_rtcp_packet, *DECRYPTED_RTCP_PACKET) 307 | } 308 | -------------------------------------------------------------------------------- /src/context/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod context_test; 3 | #[cfg(test)] 4 | mod srtcp_test; 5 | #[cfg(test)] 6 | mod srtp_test; 7 | 8 | use crate::error::Result; 9 | use crate::{ 10 | cipher::cipher_aead_aes_gcm::*, cipher::cipher_aes_cm_hmac_sha1::*, cipher::*, error::Error, 11 | option::*, protection_profile::*, 12 | }; 13 | 14 | use std::collections::HashMap; 15 | use util::replay_detector::*; 16 | 17 | pub mod srtcp; 18 | pub mod srtp; 19 | 20 | const MAX_ROC_DISORDER: u16 = 100; 21 | 22 | /// Encrypt/Decrypt state for a single SRTP SSRC 23 | #[derive(Default)] 24 | pub(crate) struct SrtpSsrcState { 25 | ssrc: u32, 26 | rollover_counter: u32, 27 | rollover_has_processed: bool, 28 | last_sequence_number: u16, 29 | replay_detector: Option>, 30 | } 31 | 32 | /// Encrypt/Decrypt state for a single SRTCP SSRC 33 | #[derive(Default)] 34 | pub(crate) struct SrtcpSsrcState { 35 | srtcp_index: usize, 36 | ssrc: u32, 37 | replay_detector: Option>, 38 | } 39 | 40 | impl SrtpSsrcState { 41 | pub fn next_rollover_count(&self, sequence_number: u16) -> u32 { 42 | let mut roc = self.rollover_counter; 43 | 44 | if !self.rollover_has_processed { 45 | } else if sequence_number == 0 { 46 | // We exactly hit the rollover count 47 | 48 | // Only update rolloverCounter if lastSequenceNumber is greater then MAX_ROCDISORDER 49 | // otherwise we already incremented for disorder 50 | if self.last_sequence_number > MAX_ROC_DISORDER { 51 | roc += 1; 52 | } 53 | } else if self.last_sequence_number < MAX_ROC_DISORDER 54 | && sequence_number > (MAX_SEQUENCE_NUMBER - MAX_ROC_DISORDER) 55 | { 56 | // Our last sequence number incremented because we crossed 0, but then our current number was within MAX_ROCDISORDER of the max 57 | // So we fell behind, drop to account for jitter 58 | roc -= 1; 59 | } else if sequence_number < MAX_ROC_DISORDER 60 | && self.last_sequence_number > (MAX_SEQUENCE_NUMBER - MAX_ROC_DISORDER) 61 | { 62 | // our current is within a MAX_ROCDISORDER of 0 63 | // and our last sequence number was a high sequence number, increment to account for jitter 64 | roc += 1; 65 | } 66 | 67 | roc 68 | } 69 | 70 | /// https://tools.ietf.org/html/rfc3550#appendix-A.1 71 | pub fn update_rollover_count(&mut self, sequence_number: u16) { 72 | if !self.rollover_has_processed { 73 | self.rollover_has_processed = true; 74 | } else if sequence_number == 0 { 75 | // We exactly hit the rollover count 76 | 77 | // Only update rolloverCounter if lastSequenceNumber is greater then MAX_ROCDISORDER 78 | // otherwise we already incremented for disorder 79 | if self.last_sequence_number > MAX_ROC_DISORDER { 80 | self.rollover_counter += 1; 81 | } 82 | } else if self.last_sequence_number < MAX_ROC_DISORDER 83 | && sequence_number > (MAX_SEQUENCE_NUMBER - MAX_ROC_DISORDER) 84 | { 85 | // Our last sequence number incremented because we crossed 0, but then our current number was within MAX_ROCDISORDER of the max 86 | // So we fell behind, drop to account for jitter 87 | self.rollover_counter -= 1; 88 | } else if sequence_number < MAX_ROC_DISORDER 89 | && self.last_sequence_number > (MAX_SEQUENCE_NUMBER - MAX_ROC_DISORDER) 90 | { 91 | // our current is within a MAX_ROCDISORDER of 0 92 | // and our last sequence number was a high sequence number, increment to account for jitter 93 | self.rollover_counter += 1; 94 | } 95 | self.last_sequence_number = sequence_number; 96 | } 97 | } 98 | 99 | /// Context represents a SRTP cryptographic context 100 | /// Context can only be used for one-way operations 101 | /// it must either used ONLY for encryption or ONLY for decryption 102 | pub struct Context { 103 | cipher: Box, 104 | 105 | srtp_ssrc_states: HashMap, 106 | srtcp_ssrc_states: HashMap, 107 | 108 | new_srtp_replay_detector: ContextOption, 109 | new_srtcp_replay_detector: ContextOption, 110 | } 111 | 112 | impl Context { 113 | /// CreateContext creates a new SRTP Context 114 | pub fn new( 115 | master_key: &[u8], 116 | master_salt: &[u8], 117 | profile: ProtectionProfile, 118 | srtp_ctx_opt: Option, 119 | srtcp_ctx_opt: Option, 120 | ) -> Result { 121 | let key_len = profile.key_len(); 122 | let salt_len = profile.salt_len(); 123 | 124 | if master_key.len() != key_len { 125 | return Err(Error::SrtpMasterKeyLength(key_len, master_key.len())); 126 | } else if master_salt.len() != salt_len { 127 | return Err(Error::SrtpSaltLength(salt_len, master_salt.len())); 128 | } 129 | 130 | let cipher: Box = match profile { 131 | ProtectionProfile::Aes128CmHmacSha1_80 => { 132 | Box::new(CipherAesCmHmacSha1::new(master_key, master_salt)?) 133 | } 134 | 135 | ProtectionProfile::AeadAes128Gcm => { 136 | Box::new(CipherAeadAesGcm::new(master_key, master_salt)?) 137 | } 138 | }; 139 | 140 | let srtp_ctx_opt = if let Some(ctx_opt) = srtp_ctx_opt { 141 | ctx_opt 142 | } else { 143 | srtp_no_replay_protection() 144 | }; 145 | 146 | let srtcp_ctx_opt = if let Some(ctx_opt) = srtcp_ctx_opt { 147 | ctx_opt 148 | } else { 149 | srtcp_no_replay_protection() 150 | }; 151 | 152 | Ok(Context { 153 | cipher, 154 | srtp_ssrc_states: HashMap::new(), 155 | srtcp_ssrc_states: HashMap::new(), 156 | new_srtp_replay_detector: srtp_ctx_opt, 157 | new_srtcp_replay_detector: srtcp_ctx_opt, 158 | }) 159 | } 160 | 161 | fn get_srtp_ssrc_state(&mut self, ssrc: u32) -> Option<&mut SrtpSsrcState> { 162 | let s = SrtpSsrcState { 163 | ssrc, 164 | replay_detector: Some((self.new_srtp_replay_detector)()), 165 | ..Default::default() 166 | }; 167 | 168 | self.srtp_ssrc_states.entry(ssrc).or_insert(s); 169 | self.srtp_ssrc_states.get_mut(&ssrc) 170 | } 171 | 172 | fn get_srtcp_ssrc_state(&mut self, ssrc: u32) -> Option<&mut SrtcpSsrcState> { 173 | let s = SrtcpSsrcState { 174 | ssrc, 175 | replay_detector: Some((self.new_srtcp_replay_detector)()), 176 | ..Default::default() 177 | }; 178 | self.srtcp_ssrc_states.entry(ssrc).or_insert(s); 179 | self.srtcp_ssrc_states.get_mut(&ssrc) 180 | } 181 | 182 | /// roc returns SRTP rollover counter value of specified SSRC. 183 | fn get_roc(&self, ssrc: u32) -> Option { 184 | self.srtp_ssrc_states.get(&ssrc).map(|s| s.rollover_counter) 185 | } 186 | 187 | /// set_roc sets SRTP rollover counter value of specified SSRC. 188 | fn set_roc(&mut self, ssrc: u32, roc: u32) { 189 | if let Some(s) = self.get_srtp_ssrc_state(ssrc) { 190 | s.rollover_counter = roc; 191 | } 192 | } 193 | 194 | /// index returns SRTCP index value of specified SSRC. 195 | fn get_index(&self, ssrc: u32) -> Option { 196 | self.srtcp_ssrc_states.get(&ssrc).map(|s| s.srtcp_index) 197 | } 198 | 199 | /// set_index sets SRTCP index value of specified SSRC. 200 | fn set_index(&mut self, ssrc: u32, index: usize) { 201 | if let Some(s) = self.get_srtcp_ssrc_state(ssrc) { 202 | s.srtcp_index = index; 203 | } 204 | } 205 | } 206 | -------------------------------------------------------------------------------- /src/context/srtcp.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::error::Result; 3 | use util::marshal::*; 4 | 5 | use bytes::Bytes; 6 | 7 | impl Context { 8 | /// DecryptRTCP decrypts a RTCP packet with an encrypted payload 9 | pub fn decrypt_rtcp(&mut self, encrypted: &[u8]) -> Result { 10 | let mut buf = encrypted; 11 | rtcp::header::Header::unmarshal(&mut buf)?; 12 | 13 | let index = self.cipher.get_rtcp_index(encrypted); 14 | let ssrc = u32::from_be_bytes([encrypted[4], encrypted[5], encrypted[6], encrypted[7]]); 15 | 16 | { 17 | if let Some(state) = self.get_srtcp_ssrc_state(ssrc) { 18 | if let Some(replay_detector) = &mut state.replay_detector { 19 | if !replay_detector.check(index as u64) { 20 | return Err(Error::SrtcpSsrcDuplicated(ssrc, index)); 21 | } 22 | } 23 | } else { 24 | return Err(Error::SsrcMissingFromSrtcp(ssrc)); 25 | } 26 | } 27 | 28 | let dst = self.cipher.decrypt_rtcp(encrypted, index, ssrc)?; 29 | 30 | { 31 | if let Some(state) = self.get_srtcp_ssrc_state(ssrc) { 32 | if let Some(replay_detector) = &mut state.replay_detector { 33 | replay_detector.accept(); 34 | } 35 | } 36 | } 37 | 38 | Ok(dst) 39 | } 40 | 41 | /// EncryptRTCP marshals and encrypts an RTCP packet, writing to the dst buffer provided. 42 | /// If the dst buffer does not have the capacity to hold `len(plaintext) + 14` bytes, a new one will be allocated and returned. 43 | pub fn encrypt_rtcp(&mut self, decrypted: &[u8]) -> Result { 44 | let mut buf = decrypted; 45 | rtcp::header::Header::unmarshal(&mut buf)?; 46 | 47 | let ssrc = u32::from_be_bytes([decrypted[4], decrypted[5], decrypted[6], decrypted[7]]); 48 | 49 | let index; 50 | { 51 | if let Some(state) = self.get_srtcp_ssrc_state(ssrc) { 52 | state.srtcp_index += 1; 53 | if state.srtcp_index > MAX_SRTCP_INDEX { 54 | state.srtcp_index = 0; 55 | } 56 | index = state.srtcp_index; 57 | } else { 58 | return Err(Error::SsrcMissingFromSrtcp(ssrc)); 59 | } 60 | } 61 | 62 | self.cipher.encrypt_rtcp(decrypted, index, ssrc) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/context/srtcp_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::key_derivation::*; 3 | 4 | use bytes::{Buf, Bytes, BytesMut}; 5 | use lazy_static::lazy_static; 6 | 7 | pub struct RTCPTestCase { 8 | ssrc: u32, 9 | index: usize, 10 | encrypted: Bytes, 11 | decrypted: Bytes, 12 | } 13 | 14 | lazy_static! { 15 | static ref RTCP_TEST_MASTER_KEY: Bytes = Bytes::from_static(&[ 16 | 0xfd, 0xa6, 0x25, 0x95, 0xd7, 0xf6, 0x92, 0x6f, 0x7d, 0x9c, 0x02, 0x4c, 0xc9, 0x20, 0x9f, 17 | 0x34 18 | ]); 19 | 20 | static ref RTCP_TEST_MASTER_SALT: Bytes = Bytes::from_static(&[ 21 | 0xa9, 0x65, 0x19, 0x85, 0x54, 0x0b, 0x47, 0xbe, 0x2f, 0x27, 0xa8, 0xb8, 0x81, 0x23 22 | ]); 23 | 24 | static ref RTCP_TEST_CASES: Vec = vec![ 25 | RTCPTestCase { 26 | ssrc: 0x66ef91ff, 27 | index: 0, 28 | encrypted: Bytes::from_static(&[ 29 | 0x80, 0xc8, 0x00, 0x06, 0x66, 0xef, 0x91, 0xff, 0xcd, 0x34, 0xc5, 0x78, 0xb2, 0x8b, 30 | 0xe1, 0x6b, 0xc5, 0x09, 0xd5, 0x77, 0xe4, 0xce, 0x5f, 0x20, 0x80, 0x21, 0xbd, 0x66, 31 | 0x74, 0x65, 0xe9, 0x5f, 0x49, 0xe5, 0xf5, 0xc0, 0x68, 0x4e, 0xe5, 0x6a, 0x78, 0x07, 32 | 0x75, 0x46, 0xed, 0x90, 0xf6, 0xdc, 0x9d, 0xef, 0x3b, 0xdf, 0xf2, 0x79, 0xa9, 0xd8, 33 | 0x80, 0x00, 0x00, 0x01, 0x60, 0xc0, 0xae, 0xb5, 0x6f, 0x40, 0x88, 0x0e, 0x28, 0xba 34 | ]), 35 | decrypted: Bytes::from_static(&[ 36 | 0x80, 0xc8, 0x00, 0x06, 0x66, 0xef, 0x91, 0xff, 0xdf, 0x48, 0x80, 0xdd, 0x61, 0xa6, 37 | 0x2e, 0xd3, 0xd8, 0xbc, 0xde, 0xbe, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x16, 0x04, 38 | 0x81, 0xca, 0x00, 0x06, 0x66, 0xef, 0x91, 0xff, 0x01, 0x10, 0x52, 0x6e, 0x54, 0x35, 39 | 0x43, 0x6d, 0x4a, 0x68, 0x7a, 0x79, 0x65, 0x74, 0x41, 0x78, 0x77, 0x2b, 0x00, 0x00 40 | ]), 41 | }, 42 | RTCPTestCase{ 43 | ssrc: 0x11111111, 44 | index: 0, 45 | encrypted: Bytes::from_static(&[ 46 | 0x80, 0xc8, 0x00, 0x06, 0x11, 0x11, 0x11, 0x11, 0x17, 0x8c, 0x15, 0xf1, 0x4b, 0x11, 47 | 0xda, 0xf5, 0x74, 0x53, 0x86, 0x2b, 0xc9, 0x07, 0x29, 0x40, 0xbf, 0x22, 0xf6, 0x46, 48 | 0x11, 0xa4, 0xc1, 0x3a, 0xff, 0x5a, 0xbd, 0xd0, 0xf8, 0x8b, 0x38, 0xe4, 0x95, 0x38, 49 | 0x5d, 0xcf, 0x1b, 0xf5, 0x27, 0x77, 0xfb, 0xdb, 0x3f, 0x10, 0x68, 0x99, 0xd8, 0xad, 50 | 0x80, 0x00, 0x00, 0x01, 0x34, 0x3c, 0x2e, 0x83, 0x17, 0x13, 0x93, 0x69, 0xcf, 0xc0 51 | ]), 52 | decrypted: Bytes::from_static(&[ 53 | 0x80, 0xc8, 0x00, 0x06, 0x11, 0x11, 0x11, 0x11, 0xdf, 0x48, 0x80, 0xdd, 0x61, 0xa6, 54 | 0x2e, 0xd3, 0xd8, 0xbc, 0xde, 0xbe, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x16, 0x04, 55 | 0x81, 0xca, 0x00, 0x06, 0x66, 0xef, 0x91, 0xff, 0x01, 0x10, 0x52, 0x6e, 0x54, 0x35, 56 | 0x43, 0x6d, 0x4a, 0x68, 0x7a, 0x79, 0x65, 0x74, 0x41, 0x78, 0x77, 0x2b, 0x00, 0x00 57 | ]), 58 | }, 59 | RTCPTestCase{ 60 | ssrc: 0x11111111, 61 | index: 0x7ffffffe, // Upper boundary of index 62 | encrypted: Bytes::from_static(&[ 63 | 0x80, 0xc8, 0x00, 0x06, 0x11, 0x11, 0x11, 0x11, 0x17, 0x8c, 0x15, 0xf1, 0x4b, 0x11, 64 | 0xda, 0xf5, 0x74, 0x53, 0x86, 0x2b, 0xc9, 0x07, 0x29, 0x40, 0xbf, 0x22, 0xf6, 0x46, 65 | 0x11, 0xa4, 0xc1, 0x3a, 0xff, 0x5a, 0xbd, 0xd0, 0xf8, 0x8b, 0x38, 0xe4, 0x95, 0x38, 66 | 0x5d, 0xcf, 0x1b, 0xf5, 0x27, 0x77, 0xfb, 0xdb, 0x3f, 0x10, 0x68, 0x99, 0xd8, 0xad, 67 | 0xff, 0xff, 0xff, 0xff, 0x5a, 0x99, 0xce, 0xed, 0x9f, 0x2e, 0x4d, 0x9d, 0xfa, 0x97 68 | ]), 69 | decrypted: Bytes::from_static(&[ 70 | 0x80, 0xc8, 0x0, 0x6, 0x11, 0x11, 0x11, 0x11, 0x4, 0x99, 0x47, 0x53, 0xc4, 0x1e, 71 | 0xb9, 0xde, 0x52, 0xa3, 0x1d, 0x77, 0x2f, 0xff, 0xcc, 0x75, 0xbb, 0x6a, 0x29, 0xb8, 72 | 0x1, 0xb7, 0x2e, 0x4b, 0x4e, 0xcb, 0xa4, 0x81, 0x2d, 0x46, 0x4, 0x5e, 0x86, 0x90, 73 | 0x17, 0x4f, 0x4d, 0x78, 0x2f, 0x58, 0xb8, 0x67, 0x91, 0x89, 0xe3, 0x61, 0x1, 0x7d 74 | ]), 75 | }, 76 | RTCPTestCase{ 77 | ssrc: 0x11111111, 78 | index: 0x7fffffff, // Will be wrapped to 0 79 | encrypted: Bytes::from_static(&[ 80 | 0x80, 0xc8, 0x00, 0x06, 0x11, 0x11, 0x11, 0x11, 0x17, 0x8c, 0x15, 0xf1, 0x4b, 0x11, 81 | 0xda, 0xf5, 0x74, 0x53, 0x86, 0x2b, 0xc9, 0x07, 0x29, 0x40, 0xbf, 0x22, 0xf6, 0x46, 82 | 0x11, 0xa4, 0xc1, 0x3a, 0xff, 0x5a, 0xbd, 0xd0, 0xf8, 0x8b, 0x38, 0xe4, 0x95, 0x38, 83 | 0x5d, 0xcf, 0x1b, 0xf5, 0x27, 0x77, 0xfb, 0xdb, 0x3f, 0x10, 0x68, 0x99, 0xd8, 0xad, 84 | 0x80, 0x00, 0x00, 0x00, 0x7d, 0x51, 0xf8, 0x0e, 0x56, 0x40, 0x72, 0x7b, 0x9e, 0x02 85 | ]), 86 | decrypted: Bytes::from_static(&[ 87 | 0x80, 0xc8, 0x0, 0x6, 0x11, 0x11, 0x11, 0x11, 0xda, 0xb5, 0xe0, 0x56, 0x9a, 0x4a, 88 | 0x74, 0xed, 0x8a, 0x54, 0xc, 0xcf, 0xd5, 0x9, 0xb1, 0x40, 0x1, 0x42, 0xc3, 0x9a, 89 | 0x76, 0x0, 0xa9, 0xd4, 0xf7, 0x29, 0x9e, 0x51, 0xfb, 0x3c, 0xc1, 0x74, 0x72, 0xf9, 90 | 0x52, 0xb1, 0x92, 0x31, 0xca, 0x22, 0xab, 0x3e, 0xc5, 0x5f, 0x83, 0x34, 0xf0, 0x28 91 | ]), 92 | }, 93 | ]; 94 | } 95 | 96 | #[test] 97 | fn test_rtcp_lifecycle() -> Result<()> { 98 | let mut encrypt_context = Context::new( 99 | &RTCP_TEST_MASTER_KEY, 100 | &RTCP_TEST_MASTER_SALT, 101 | ProtectionProfile::Aes128CmHmacSha1_80, 102 | None, 103 | None, 104 | )?; 105 | let mut decrypt_context = Context::new( 106 | &RTCP_TEST_MASTER_KEY, 107 | &RTCP_TEST_MASTER_SALT, 108 | ProtectionProfile::Aes128CmHmacSha1_80, 109 | None, 110 | None, 111 | )?; 112 | 113 | for test_case in &*RTCP_TEST_CASES { 114 | let decrypt_result = decrypt_context.decrypt_rtcp(&test_case.encrypted)?; 115 | assert_eq!( 116 | decrypt_result, test_case.decrypted, 117 | "RTCP failed to decrypt" 118 | ); 119 | 120 | encrypt_context.set_index(test_case.ssrc, test_case.index); 121 | let encrypt_result = encrypt_context.encrypt_rtcp(&test_case.decrypted)?; 122 | assert_eq!( 123 | encrypt_result, test_case.encrypted, 124 | "RTCP failed to encrypt" 125 | ); 126 | } 127 | 128 | Ok(()) 129 | } 130 | 131 | #[test] 132 | fn test_rtcp_invalid_auth_tag() -> Result<()> { 133 | let auth_tag_len = ProtectionProfile::Aes128CmHmacSha1_80.auth_tag_len(); 134 | 135 | let mut decrypt_context = Context::new( 136 | &RTCP_TEST_MASTER_KEY, 137 | &RTCP_TEST_MASTER_SALT, 138 | ProtectionProfile::Aes128CmHmacSha1_80, 139 | None, 140 | None, 141 | )?; 142 | 143 | let decrypt_result = decrypt_context.decrypt_rtcp(&RTCP_TEST_CASES[0].encrypted)?; 144 | assert_eq!( 145 | decrypt_result, RTCP_TEST_CASES[0].decrypted, 146 | "RTCP failed to decrypt" 147 | ); 148 | 149 | // Zero out auth tag 150 | let mut rtcp_packet = BytesMut::new(); 151 | rtcp_packet.extend_from_slice(&RTCP_TEST_CASES[0].encrypted); 152 | let rtcp_packet_len = rtcp_packet.len(); 153 | rtcp_packet[rtcp_packet_len - auth_tag_len..].copy_from_slice(&vec![0; auth_tag_len]); 154 | let rtcp_packet = rtcp_packet.freeze(); 155 | let decrypt_result = decrypt_context.decrypt_rtcp(&rtcp_packet); 156 | assert!( 157 | decrypt_result.is_err(), 158 | "Was able to decrypt RTCP packet with invalid Auth Tag" 159 | ); 160 | 161 | Ok(()) 162 | } 163 | 164 | #[test] 165 | fn test_rtcp_replay_detector_separation() -> Result<()> { 166 | let mut decrypt_context = Context::new( 167 | &RTCP_TEST_MASTER_KEY, 168 | &RTCP_TEST_MASTER_SALT, 169 | ProtectionProfile::Aes128CmHmacSha1_80, 170 | None, 171 | Some(srtcp_replay_protection(10)), 172 | )?; 173 | 174 | let rtcp_packet1 = RTCP_TEST_CASES[0].encrypted.clone(); 175 | let decrypt_result1 = decrypt_context.decrypt_rtcp(&rtcp_packet1)?; 176 | assert_eq!( 177 | decrypt_result1, RTCP_TEST_CASES[0].decrypted, 178 | "RTCP failed to decrypt" 179 | ); 180 | 181 | let rtcp_packet2 = RTCP_TEST_CASES[1].encrypted.clone(); 182 | let decrypt_result2 = decrypt_context.decrypt_rtcp(&rtcp_packet2)?; 183 | assert_eq!( 184 | decrypt_result2, RTCP_TEST_CASES[1].decrypted, 185 | "RTCP failed to decrypt" 186 | ); 187 | 188 | let result = decrypt_context.decrypt_rtcp(&rtcp_packet1); 189 | assert!( 190 | result.is_err(), 191 | "Was able to decrypt duplicated RTCP packet" 192 | ); 193 | 194 | let result = decrypt_context.decrypt_rtcp(&rtcp_packet2); 195 | assert!( 196 | result.is_err(), 197 | "Was able to decrypt duplicated RTCP packet" 198 | ); 199 | 200 | Ok(()) 201 | } 202 | 203 | fn get_rtcp_index(encrypted: &Bytes, auth_tag_len: usize) -> u32 { 204 | let tail_offset = encrypted.len() - (auth_tag_len + SRTCP_INDEX_SIZE); 205 | let reader = &mut encrypted.slice(tail_offset..tail_offset + SRTCP_INDEX_SIZE); 206 | //^(1 << 31) 207 | reader.get_u32() & 0x7FFFFFFF 208 | } 209 | 210 | #[test] 211 | fn test_encrypt_rtcp_separation() -> Result<()> { 212 | let mut encrypt_context = Context::new( 213 | &RTCP_TEST_MASTER_KEY, 214 | &RTCP_TEST_MASTER_SALT, 215 | ProtectionProfile::Aes128CmHmacSha1_80, 216 | None, 217 | None, 218 | )?; 219 | 220 | let auth_tag_len = ProtectionProfile::Aes128CmHmacSha1_80.auth_tag_len(); 221 | 222 | let mut decrypt_context = Context::new( 223 | &RTCP_TEST_MASTER_KEY, 224 | &RTCP_TEST_MASTER_SALT, 225 | ProtectionProfile::Aes128CmHmacSha1_80, 226 | None, 227 | Some(srtcp_replay_protection(10)), 228 | )?; 229 | 230 | let inputs = vec![ 231 | RTCP_TEST_CASES[0].decrypted.clone(), 232 | RTCP_TEST_CASES[1].decrypted.clone(), 233 | RTCP_TEST_CASES[0].decrypted.clone(), 234 | RTCP_TEST_CASES[1].decrypted.clone(), 235 | ]; 236 | let mut encrypted_rctps = vec![]; 237 | 238 | for input in &inputs { 239 | let encrypted = encrypt_context.encrypt_rtcp(input)?; 240 | encrypted_rctps.push(encrypted); 241 | } 242 | 243 | for (i, expected_index) in [1, 1, 2, 2].iter().enumerate() { 244 | assert_eq!( 245 | *expected_index, 246 | get_rtcp_index(&encrypted_rctps[i], auth_tag_len), 247 | "RTCP index does not match" 248 | ); 249 | } 250 | 251 | for (i, output) in encrypted_rctps.iter().enumerate() { 252 | let decrypted = decrypt_context.decrypt_rtcp(output)?; 253 | assert_eq!(inputs[i], decrypted); 254 | } 255 | 256 | Ok(()) 257 | } 258 | -------------------------------------------------------------------------------- /src/context/srtp.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::error::Result; 3 | use util::marshal::*; 4 | 5 | use bytes::Bytes; 6 | 7 | impl Context { 8 | pub fn decrypt_rtp_with_header( 9 | &mut self, 10 | encrypted: &[u8], 11 | header: &rtp::header::Header, 12 | ) -> Result { 13 | let roc; 14 | { 15 | if let Some(state) = self.get_srtp_ssrc_state(header.ssrc) { 16 | if let Some(replay_detector) = &mut state.replay_detector { 17 | if !replay_detector.check(header.sequence_number as u64) { 18 | return Err(Error::SrtpSsrcDuplicated( 19 | header.ssrc, 20 | header.sequence_number, 21 | )); 22 | } 23 | } 24 | 25 | roc = state.next_rollover_count(header.sequence_number); 26 | } else { 27 | return Err(Error::SsrcMissingFromSrtp(header.ssrc)); 28 | } 29 | } 30 | 31 | let dst = self.cipher.decrypt_rtp(encrypted, header, roc)?; 32 | { 33 | if let Some(state) = self.get_srtp_ssrc_state(header.ssrc) { 34 | if let Some(replay_detector) = &mut state.replay_detector { 35 | replay_detector.accept(); 36 | } 37 | state.update_rollover_count(header.sequence_number); 38 | } 39 | } 40 | 41 | Ok(dst) 42 | } 43 | 44 | /// DecryptRTP decrypts a RTP packet with an encrypted payload 45 | pub fn decrypt_rtp(&mut self, encrypted: &[u8]) -> Result { 46 | let mut buf = encrypted; 47 | let header = rtp::header::Header::unmarshal(&mut buf)?; 48 | self.decrypt_rtp_with_header(encrypted, &header) 49 | } 50 | 51 | pub fn encrypt_rtp_with_header( 52 | &mut self, 53 | plaintext: &[u8], 54 | header: &rtp::header::Header, 55 | ) -> Result { 56 | let roc; 57 | { 58 | if let Some(state) = self.get_srtp_ssrc_state(header.ssrc) { 59 | roc = state.next_rollover_count(header.sequence_number); 60 | } else { 61 | return Err(Error::SsrcMissingFromSrtp(header.ssrc)); 62 | } 63 | } 64 | 65 | let dst = self 66 | .cipher 67 | .encrypt_rtp(&plaintext[header.marshal_size()..], header, roc)?; 68 | 69 | { 70 | if let Some(state) = self.get_srtp_ssrc_state(header.ssrc) { 71 | state.update_rollover_count(header.sequence_number); 72 | } 73 | } 74 | 75 | Ok(dst) 76 | } 77 | 78 | /// EncryptRTP marshals and encrypts an RTP packet, writing to the dst buffer provided. 79 | /// If the dst buffer does not have the capacity to hold `len(plaintext) + 10` bytes, a new one will be allocated and returned. 80 | pub fn encrypt_rtp(&mut self, plaintext: &[u8]) -> Result { 81 | let mut buf = plaintext; 82 | let header = rtp::header::Header::unmarshal(&mut buf)?; 83 | self.encrypt_rtp_with_header(plaintext, &header) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/context/srtp_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use util::marshal::*; 3 | 4 | use bytes::Bytes; 5 | use lazy_static::lazy_static; 6 | 7 | struct RTPTestCase { 8 | sequence_number: u16, 9 | encrypted: Bytes, 10 | } 11 | 12 | lazy_static! { 13 | static ref RTP_TEST_CASE_DECRYPTED: Bytes = Bytes::from_static(&[0x00, 0x01, 0x02, 0x03, 0x04, 0x05]); 14 | static ref RTP_TEST_CASES: Vec = vec![ 15 | RTPTestCase { 16 | sequence_number: 5000, 17 | encrypted: Bytes::from_static(&[ 18 | 0x6d, 0xd3, 0x7e, 0xd5, 0x99, 0xb7, 0x2d, 0x28, 0xb1, 0xf3, 0xa1, 0xf0, 0xc, 0xfb, 19 | 0xfd, 0x8 20 | ]), 21 | }, 22 | RTPTestCase { 23 | sequence_number: 5001, 24 | encrypted: Bytes::from_static(&[ 25 | 0xda, 0x47, 0xb, 0x2a, 0x74, 0x53, 0x65, 0xbd, 0x2f, 0xeb, 0xdc, 0x4b, 0x6d, 0x23, 26 | 0xf3, 0xde 27 | ]), 28 | }, 29 | RTPTestCase { 30 | sequence_number: 5002, 31 | encrypted: Bytes::from_static(&[ 32 | 0x6e, 0xa7, 0x69, 0x8d, 0x24, 0x6d, 0xdc, 0xbf, 0xec, 0x2, 0x1c, 0xd1, 0x60, 0x76, 33 | 0xc1, 0x0e 34 | ]), 35 | }, 36 | RTPTestCase { 37 | sequence_number: 5003, 38 | encrypted: Bytes::from_static(&[ 39 | 0x24, 0x7e, 0x96, 0xc8, 0x7d, 0x33, 0xa2, 0x92, 0x8d, 0x13, 0x8d, 0xe0, 0x76, 0x9f, 40 | 0x08, 0xdc 41 | ]), 42 | }, 43 | RTPTestCase { 44 | sequence_number: 5004, 45 | encrypted: Bytes::from_static(&[ 46 | 0x75, 0x43, 0x28, 0xe4, 0x3a, 0x77, 0x59, 0x9b, 0x2e, 0xdf, 0x7b, 0x12, 0x68, 0x0b, 47 | 0x57, 0x49 48 | ]), 49 | }, 50 | RTPTestCase{ 51 | sequence_number: 65535, // upper boundary 52 | encrypted: Bytes::from_static(&[ 53 | 0xaf, 0xf7, 0xc2, 0x70, 0x37, 0x20, 0x83, 0x9c, 0x2c, 0x63, 0x85, 0x15, 0x0e, 0x44, 54 | 0xca, 0x36 55 | ]), 56 | }, 57 | ]; 58 | } 59 | 60 | fn build_test_context() -> Result { 61 | let master_key = Bytes::from_static(&[ 62 | 0x0d, 0xcd, 0x21, 0x3e, 0x4c, 0xbc, 0xf2, 0x8f, 0x01, 0x7f, 0x69, 0x94, 0x40, 0x1e, 0x28, 63 | 0x89, 64 | ]); 65 | let master_salt = Bytes::from_static(&[ 66 | 0x62, 0x77, 0x60, 0x38, 0xc0, 0x6d, 0xc9, 0x41, 0x9f, 0x6d, 0xd9, 0x43, 0x3e, 0x7c, 67 | ]); 68 | 69 | Context::new( 70 | &master_key, 71 | &master_salt, 72 | ProtectionProfile::Aes128CmHmacSha1_80, 73 | None, 74 | None, 75 | ) 76 | } 77 | 78 | #[test] 79 | fn test_rtp_invalid_auth() -> Result<()> { 80 | let master_key = Bytes::from_static(&[ 81 | 0x0d, 0xcd, 0x21, 0x3e, 0x4c, 0xbc, 0xf2, 0x8f, 0x01, 0x7f, 0x69, 0x94, 0x40, 0x1e, 0x28, 82 | 0x89, 83 | ]); 84 | let invalid_salt = Bytes::from_static(&[ 85 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 86 | ]); 87 | 88 | let mut encrypt_context = build_test_context()?; 89 | let mut invalid_context = Context::new( 90 | &master_key, 91 | &invalid_salt, 92 | ProtectionProfile::Aes128CmHmacSha1_80, 93 | None, 94 | None, 95 | )?; 96 | 97 | for test_case in &*RTP_TEST_CASES { 98 | let pkt = rtp::packet::Packet { 99 | header: rtp::header::Header { 100 | sequence_number: test_case.sequence_number, 101 | ..Default::default() 102 | }, 103 | payload: RTP_TEST_CASE_DECRYPTED.clone(), 104 | }; 105 | 106 | let pkt_raw = pkt.marshal()?; 107 | let out = encrypt_context.encrypt_rtp(&pkt_raw)?; 108 | 109 | let result = invalid_context.decrypt_rtp(&out); 110 | assert!( 111 | result.is_err(), 112 | "Managed to decrypt with incorrect salt for packet with SeqNum: {}", 113 | test_case.sequence_number 114 | ); 115 | } 116 | 117 | Ok(()) 118 | } 119 | 120 | #[test] 121 | fn test_rtp_lifecyle() -> Result<()> { 122 | let mut encrypt_context = build_test_context()?; 123 | let mut decrypt_context = build_test_context()?; 124 | let auth_tag_len = ProtectionProfile::Aes128CmHmacSha1_80.auth_tag_len(); 125 | 126 | for test_case in RTP_TEST_CASES.iter() { 127 | let decrypted_pkt = rtp::packet::Packet { 128 | header: rtp::header::Header { 129 | sequence_number: test_case.sequence_number, 130 | ..Default::default() 131 | }, 132 | payload: RTP_TEST_CASE_DECRYPTED.clone(), 133 | }; 134 | 135 | let decrypted_raw = decrypted_pkt.marshal()?; 136 | 137 | let encrypted_pkt = rtp::packet::Packet { 138 | header: rtp::header::Header { 139 | sequence_number: test_case.sequence_number, 140 | ..Default::default() 141 | }, 142 | payload: test_case.encrypted.clone(), 143 | }; 144 | 145 | let encrypted_raw = encrypted_pkt.marshal()?; 146 | let actual_encrypted = encrypt_context.encrypt_rtp(&decrypted_raw)?; 147 | assert_eq!( 148 | actual_encrypted, encrypted_raw, 149 | "RTP packet with SeqNum invalid encryption: {}", 150 | test_case.sequence_number 151 | ); 152 | 153 | let actual_decrypted = decrypt_context.decrypt_rtp(&encrypted_raw)?; 154 | assert_ne!( 155 | encrypted_raw[..encrypted_raw.len() - auth_tag_len].to_vec(), 156 | actual_decrypted, 157 | "DecryptRTP improperly encrypted in place" 158 | ); 159 | 160 | assert_eq!( 161 | actual_decrypted, decrypted_raw, 162 | "RTP packet with SeqNum invalid decryption: {}", 163 | test_case.sequence_number, 164 | ) 165 | } 166 | 167 | Ok(()) 168 | } 169 | 170 | //TODO: BenchmarkEncryptRTP 171 | //TODO: BenchmarkEncryptRTPInPlace 172 | //TODO: BenchmarkDecryptRTP 173 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use thiserror::Error; 3 | use tokio::sync::mpsc::error::SendError as MpscSendError; 4 | 5 | pub type Result = std::result::Result; 6 | 7 | #[derive(Error, Debug, PartialEq)] 8 | #[non_exhaustive] 9 | pub enum Error { 10 | #[error("duplicated packet")] 11 | ErrDuplicated, 12 | #[error("SRTP master key is not long enough")] 13 | ErrShortSrtpMasterKey, 14 | #[error("SRTP master salt is not long enough")] 15 | ErrShortSrtpMasterSalt, 16 | #[error("no such SRTP Profile")] 17 | ErrNoSuchSrtpProfile, 18 | #[error("indexOverKdr > 0 is not supported yet")] 19 | ErrNonZeroKdrNotSupported, 20 | #[error("exporter called with wrong label")] 21 | ErrExporterWrongLabel, 22 | #[error("no config provided")] 23 | ErrNoConfig, 24 | #[error("no conn provided")] 25 | ErrNoConn, 26 | #[error("failed to verify auth tag")] 27 | ErrFailedToVerifyAuthTag, 28 | #[error("packet is too short to be rtcp packet")] 29 | ErrTooShortRtcp, 30 | #[error("payload differs")] 31 | ErrPayloadDiffers, 32 | #[error("started channel used incorrectly, should only be closed")] 33 | ErrStartedChannelUsedIncorrectly, 34 | #[error("stream has not been inited, unable to close")] 35 | ErrStreamNotInited, 36 | #[error("stream is already closed")] 37 | ErrStreamAlreadyClosed, 38 | #[error("stream is already inited")] 39 | ErrStreamAlreadyInited, 40 | #[error("failed to cast child")] 41 | ErrFailedTypeAssertion, 42 | 43 | #[error("index_over_kdr > 0 is not supported yet")] 44 | UnsupportedIndexOverKdr, 45 | #[error("SRTP Master Key must be len {0}, got {1}")] 46 | SrtpMasterKeyLength(usize, usize), 47 | #[error("SRTP Salt must be len {0}, got {1}")] 48 | SrtpSaltLength(usize, usize), 49 | #[error("SyntaxError: {0}")] 50 | ExtMapParse(String), 51 | #[error("ssrc {0} not exist in srtp_ssrc_state")] 52 | SsrcMissingFromSrtp(u32), 53 | #[error("srtp ssrc={0} index={1}: duplicated")] 54 | SrtpSsrcDuplicated(u32, u16), 55 | #[error("srtcp ssrc={0} index={1}: duplicated")] 56 | SrtcpSsrcDuplicated(u32, usize), 57 | #[error("ssrc {0} not exist in srtcp_ssrc_state")] 58 | SsrcMissingFromSrtcp(u32), 59 | #[error("Stream with ssrc {0} exists")] 60 | StreamWithSsrcExists(u32), 61 | #[error("Session RTP/RTCP type must be same as input buffer")] 62 | SessionRtpRtcpTypeMismatch, 63 | #[error("Session EOF")] 64 | SessionEof, 65 | #[error("too short SRTP packet: only {0} bytes, expected > {1} bytes")] 66 | SrtpTooSmall(usize, usize), 67 | #[error("too short SRTCP packet: only {0} bytes, expected > {1} bytes")] 68 | SrtcpTooSmall(usize, usize), 69 | #[error("failed to verify rtp auth tag")] 70 | RtpFailedToVerifyAuthTag, 71 | #[error("failed to verify rtcp auth tag")] 72 | RtcpFailedToVerifyAuthTag, 73 | #[error("SessionSRTP has been closed")] 74 | SessionSrtpAlreadyClosed, 75 | #[error("this stream is not a RTPStream")] 76 | InvalidRtpStream, 77 | #[error("this stream is not a RTCPStream")] 78 | InvalidRtcpStream, 79 | 80 | #[error("{0}")] 81 | Io(#[source] IoError), 82 | #[error("{0}")] 83 | KeyingMaterial(#[from] util::KeyingMaterialExporterError), 84 | #[error("mpsc send: {0}")] 85 | MpscSend(String), 86 | #[error("{0}")] 87 | Util(#[from] util::Error), 88 | #[error("{0}")] 89 | Rtcp(#[from] rtcp::Error), 90 | #[error("aes gcm: {0}")] 91 | AesGcm(#[from] aes_gcm::Error), 92 | 93 | #[error("{0}")] 94 | Other(String), 95 | } 96 | 97 | #[derive(Debug, Error)] 98 | #[error("io error: {0}")] 99 | pub struct IoError(#[from] pub io::Error); 100 | 101 | // Workaround for wanting PartialEq for io::Error. 102 | impl PartialEq for IoError { 103 | fn eq(&self, other: &Self) -> bool { 104 | self.0.kind() == other.0.kind() 105 | } 106 | } 107 | 108 | impl From for Error { 109 | fn from(e: io::Error) -> Self { 110 | Error::Io(IoError(e)) 111 | } 112 | } 113 | 114 | // Because Tokio SendError is parameterized, we sadly lose the backtrace. 115 | impl From> for Error { 116 | fn from(e: MpscSendError) -> Self { 117 | Error::MpscSend(e.to_string()) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/key_derivation.rs: -------------------------------------------------------------------------------- 1 | use aes::cipher::generic_array::GenericArray; 2 | use aes::cipher::NewBlockCipher; 3 | use aes::{Aes128, BlockEncrypt}; 4 | 5 | use byteorder::{BigEndian, WriteBytesExt}; 6 | use std::io::BufWriter; 7 | 8 | use crate::error::{Error, Result}; 9 | 10 | pub const LABEL_SRTP_ENCRYPTION: u8 = 0x00; 11 | pub const LABEL_SRTP_AUTHENTICATION_TAG: u8 = 0x01; 12 | pub const LABEL_SRTP_SALT: u8 = 0x02; 13 | pub const LABEL_SRTCP_ENCRYPTION: u8 = 0x03; 14 | pub const LABEL_SRTCP_AUTHENTICATION_TAG: u8 = 0x04; 15 | pub const LABEL_SRTCP_SALT: u8 = 0x05; 16 | 17 | pub(crate) const SRTCP_INDEX_SIZE: usize = 4; 18 | 19 | pub(crate) fn aes_cm_key_derivation( 20 | label: u8, 21 | master_key: &[u8], 22 | master_salt: &[u8], 23 | index_over_kdr: usize, 24 | out_len: usize, 25 | ) -> Result> { 26 | if index_over_kdr != 0 { 27 | // 24-bit "index DIV kdr" must be xored to prf input. 28 | return Err(Error::UnsupportedIndexOverKdr); 29 | } 30 | 31 | // https://tools.ietf.org/html/rfc3711#appendix-B.3 32 | // The input block for AES-CM is generated by exclusive-oring the master salt with the 33 | // concatenation of the encryption key label 0x00 with (index DIV kdr), 34 | // - index is 'rollover count' and DIV is 'divided by' 35 | 36 | let n_master_key = master_key.len(); 37 | let n_master_salt = master_salt.len(); 38 | 39 | let mut prf_in = vec![0u8; n_master_key]; 40 | prf_in[..n_master_salt].copy_from_slice(master_salt); 41 | 42 | prf_in[7] ^= label; 43 | 44 | //The resulting value is then AES encrypted using the master key to get the cipher key. 45 | let key = GenericArray::from_slice(master_key); 46 | let block = Aes128::new(key); 47 | 48 | let mut out = vec![0u8; ((out_len + n_master_key) / n_master_key) * n_master_key]; 49 | for (i, n) in (0..out_len).step_by(n_master_key).enumerate() { 50 | //BigEndian.PutUint16(prfIn[nMasterKey-2:], i) 51 | prf_in[n_master_key - 2] = ((i >> 8) & 0xFF) as u8; 52 | prf_in[n_master_key - 1] = (i & 0xFF) as u8; 53 | 54 | out[n..n + n_master_key].copy_from_slice(&prf_in); 55 | let out_key = GenericArray::from_mut_slice(&mut out[n..n + n_master_key]); 56 | block.encrypt_block(out_key); 57 | } 58 | 59 | Ok(out[..out_len].to_vec()) 60 | } 61 | 62 | /// Generate IV https://tools.ietf.org/html/rfc3711#section-4.1.1 63 | /// where the 128-bit integer value IV SHALL be defined by the SSRC, the 64 | /// SRTP packet index i, and the SRTP session salting key k_s, as below. 65 | /// ROC = a 32-bit unsigned rollover counter (roc), which records how many 66 | /// times the 16-bit RTP sequence number has been reset to zero after 67 | /// passing through 65,535 68 | /// ```nobuild 69 | /// i = 2^16 * roc + SEQ 70 | /// IV = (salt*2 ^ 16) | (ssrc*2 ^ 64) | (i*2 ^ 16) 71 | /// ``` 72 | pub(crate) fn generate_counter( 73 | sequence_number: u16, 74 | rollover_counter: u32, 75 | ssrc: u32, 76 | session_salt: &[u8], 77 | ) -> Result> { 78 | assert!(session_salt.len() <= 16); 79 | 80 | let mut counter: Vec = vec![0; 16]; 81 | { 82 | let mut writer = BufWriter::<&mut [u8]>::new(counter[4..].as_mut()); 83 | writer.write_u32::(ssrc)?; 84 | writer.write_u32::(rollover_counter)?; 85 | writer.write_u32::((sequence_number as u32) << 16)?; 86 | } 87 | 88 | for i in 0..session_salt.len() { 89 | counter[i] ^= session_salt[i]; 90 | } 91 | 92 | Ok(counter) 93 | } 94 | 95 | #[cfg(test)] 96 | mod test { 97 | use super::*; 98 | use crate::protection_profile::*; 99 | 100 | #[test] 101 | fn test_valid_session_keys() -> Result<()> { 102 | // Key Derivation Test Vectors from https://tools.ietf.org/html/rfc3711#appendix-B.3 103 | let master_key = vec![ 104 | 0xE1, 0xF9, 0x7A, 0x0D, 0x3E, 0x01, 0x8B, 0xE0, 0xD6, 0x4F, 0xA3, 0x2C, 0x06, 0xDE, 105 | 0x41, 0x39, 106 | ]; 107 | let master_salt = vec![ 108 | 0x0E, 0xC6, 0x75, 0xAD, 0x49, 0x8A, 0xFE, 0xEB, 0xB6, 0x96, 0x0B, 0x3A, 0xAB, 0xE6, 109 | ]; 110 | 111 | let expected_session_key = vec![ 112 | 0xC6, 0x1E, 0x7A, 0x93, 0x74, 0x4F, 0x39, 0xEE, 0x10, 0x73, 0x4A, 0xFE, 0x3F, 0xF7, 113 | 0xA0, 0x87, 114 | ]; 115 | let expected_session_salt = vec![ 116 | 0x30, 0xCB, 0xBC, 0x08, 0x86, 0x3D, 0x8C, 0x85, 0xD4, 0x9D, 0xB3, 0x4A, 0x9A, 0xE1, 117 | ]; 118 | let expected_session_auth_tag = vec![ 119 | 0xCE, 0xBE, 0x32, 0x1F, 0x6F, 0xF7, 0x71, 0x6B, 0x6F, 0xD4, 0xAB, 0x49, 0xAF, 0x25, 120 | 0x6A, 0x15, 0x6D, 0x38, 0xBA, 0xA4, 121 | ]; 122 | 123 | let session_key = aes_cm_key_derivation( 124 | LABEL_SRTP_ENCRYPTION, 125 | &master_key, 126 | &master_salt, 127 | 0, 128 | master_key.len(), 129 | )?; 130 | assert_eq!( 131 | session_key, expected_session_key, 132 | "Session Key:\n{:?} \ndoes not match expected:\n{:?}\nMaster Key:\n{:?}\nMaster Salt:\n{:?}\n", 133 | session_key, expected_session_key, master_key, master_salt, 134 | ); 135 | 136 | let session_salt = aes_cm_key_derivation( 137 | LABEL_SRTP_SALT, 138 | &master_key, 139 | &master_salt, 140 | 0, 141 | master_salt.len(), 142 | )?; 143 | assert_eq!( 144 | session_salt, expected_session_salt, 145 | "Session Salt {:?} does not match expected {:?}", 146 | session_salt, expected_session_salt 147 | ); 148 | 149 | let auth_key_len = ProtectionProfile::Aes128CmHmacSha1_80.auth_key_len(); 150 | 151 | let session_auth_tag = aes_cm_key_derivation( 152 | LABEL_SRTP_AUTHENTICATION_TAG, 153 | &master_key, 154 | &master_salt, 155 | 0, 156 | auth_key_len, 157 | )?; 158 | assert_eq!( 159 | session_auth_tag, expected_session_auth_tag, 160 | "Session Auth Tag {:?} does not match expected {:?}", 161 | session_auth_tag, expected_session_auth_tag, 162 | ); 163 | 164 | Ok(()) 165 | } 166 | 167 | // This test asserts that calling aesCmKeyDerivation with a non-zero indexOverKdr fails 168 | // Currently this isn't supported, but the API makes sure we can add this in the future 169 | #[test] 170 | fn test_index_over_kdr() -> Result<()> { 171 | let result = aes_cm_key_derivation(LABEL_SRTP_AUTHENTICATION_TAG, &[], &[], 1, 0); 172 | assert!(result.is_err()); 173 | 174 | Ok(()) 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![warn(rust_2018_idioms)] 2 | #![allow(dead_code)] 3 | 4 | mod cipher; 5 | pub mod config; 6 | pub mod context; 7 | mod error; 8 | mod key_derivation; 9 | pub mod option; 10 | pub mod protection_profile; 11 | pub mod session; 12 | pub mod stream; 13 | 14 | pub use error::Error; 15 | -------------------------------------------------------------------------------- /src/option.rs: -------------------------------------------------------------------------------- 1 | use util::replay_detector::*; 2 | 3 | pub type ContextOption = Box Box) + Send + Sync>; 4 | 5 | pub(crate) const MAX_SEQUENCE_NUMBER: u16 = 65535; 6 | pub(crate) const MAX_SRTCP_INDEX: usize = 0x7FFFFFFF; 7 | 8 | /// srtp_replay_protection sets SRTP replay protection window size. 9 | pub fn srtp_replay_protection(window_size: usize) -> ContextOption { 10 | Box::new(move || -> Box { 11 | Box::new(WrappedSlidingWindowDetector::new( 12 | window_size, 13 | MAX_SEQUENCE_NUMBER as u64, 14 | )) 15 | }) 16 | } 17 | 18 | /// Sets SRTCP replay protection window size. 19 | pub fn srtcp_replay_protection(window_size: usize) -> ContextOption { 20 | Box::new(move || -> Box { 21 | Box::new(WrappedSlidingWindowDetector::new( 22 | window_size, 23 | MAX_SRTCP_INDEX as u64, 24 | )) 25 | }) 26 | } 27 | 28 | /// srtp_no_replay_protection disables SRTP replay protection. 29 | pub fn srtp_no_replay_protection() -> ContextOption { 30 | Box::new(|| -> Box { Box::new(NoOpReplayDetector::default()) }) 31 | } 32 | 33 | /// srtcp_no_replay_protection disables SRTCP replay protection. 34 | pub fn srtcp_no_replay_protection() -> ContextOption { 35 | Box::new(|| -> Box { Box::new(NoOpReplayDetector::default()) }) 36 | } 37 | -------------------------------------------------------------------------------- /src/protection_profile.rs: -------------------------------------------------------------------------------- 1 | /// ProtectionProfile specifies Cipher and AuthTag details, similar to TLS cipher suite 2 | #[derive(Debug, Clone, Copy)] 3 | #[repr(u8)] 4 | pub enum ProtectionProfile { 5 | Aes128CmHmacSha1_80 = 0x0001, 6 | AeadAes128Gcm = 0x0007, 7 | } 8 | 9 | impl Default for ProtectionProfile { 10 | fn default() -> Self { 11 | ProtectionProfile::Aes128CmHmacSha1_80 12 | } 13 | } 14 | 15 | impl ProtectionProfile { 16 | pub(crate) fn key_len(&self) -> usize { 17 | match *self { 18 | ProtectionProfile::Aes128CmHmacSha1_80 | ProtectionProfile::AeadAes128Gcm => 16, 19 | } 20 | } 21 | 22 | pub(crate) fn salt_len(&self) -> usize { 23 | match *self { 24 | ProtectionProfile::Aes128CmHmacSha1_80 => 14, 25 | ProtectionProfile::AeadAes128Gcm => 12, 26 | } 27 | } 28 | 29 | pub(crate) fn auth_tag_len(&self) -> usize { 30 | match *self { 31 | ProtectionProfile::Aes128CmHmacSha1_80 => 10, //CIPHER_AES_CM_HMAC_SHA1AUTH_TAG_LEN, 32 | ProtectionProfile::AeadAes128Gcm => 16, //CIPHER_AEAD_AES_GCM_AUTH_TAG_LEN, 33 | } 34 | } 35 | 36 | pub(crate) fn auth_key_len(&self) -> usize { 37 | match *self { 38 | ProtectionProfile::Aes128CmHmacSha1_80 => 20, 39 | ProtectionProfile::AeadAes128Gcm => 0, 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/session/mod.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | mod session_rtcp_test; 3 | #[cfg(test)] 4 | mod session_rtp_test; 5 | 6 | use crate::{ 7 | config::*, 8 | context::*, 9 | error::{Error, Result}, 10 | option::*, 11 | stream::*, 12 | }; 13 | use util::{conn::Conn, marshal::*}; 14 | 15 | use bytes::Bytes; 16 | use std::collections::HashSet; 17 | use std::{ 18 | collections::HashMap, 19 | marker::{Send, Sync}, 20 | sync::Arc, 21 | }; 22 | use tokio::sync::{mpsc, Mutex}; 23 | 24 | const DEFAULT_SESSION_SRTP_REPLAY_PROTECTION_WINDOW: usize = 64; 25 | const DEFAULT_SESSION_SRTCP_REPLAY_PROTECTION_WINDOW: usize = 64; 26 | 27 | /// Session implements io.ReadWriteCloser and provides a bi-directional SRTP session 28 | /// SRTP itself does not have a design like this, but it is common in most applications 29 | /// for local/remote to each have their own keying material. This provides those patterns 30 | /// instead of making everyone re-implement 31 | pub struct Session { 32 | local_context: Arc>, 33 | streams_map: Arc>>>, 34 | new_stream_rx: Arc>>>, 35 | close_stream_tx: mpsc::Sender, 36 | close_session_tx: mpsc::Sender<()>, 37 | pub(crate) udp_tx: Arc, 38 | is_rtp: bool, 39 | } 40 | 41 | impl Session { 42 | pub async fn new( 43 | conn: Arc, 44 | config: Config, 45 | is_rtp: bool, 46 | ) -> Result { 47 | let local_context = Context::new( 48 | &config.keys.local_master_key, 49 | &config.keys.local_master_salt, 50 | config.profile, 51 | config.local_rtp_options, 52 | config.local_rtcp_options, 53 | )?; 54 | 55 | let mut remote_context = Context::new( 56 | &config.keys.remote_master_key, 57 | &config.keys.remote_master_salt, 58 | config.profile, 59 | if config.remote_rtp_options.is_none() { 60 | Some(srtp_replay_protection( 61 | DEFAULT_SESSION_SRTP_REPLAY_PROTECTION_WINDOW, 62 | )) 63 | } else { 64 | config.remote_rtp_options 65 | }, 66 | if config.remote_rtcp_options.is_none() { 67 | Some(srtcp_replay_protection( 68 | DEFAULT_SESSION_SRTCP_REPLAY_PROTECTION_WINDOW, 69 | )) 70 | } else { 71 | config.remote_rtcp_options 72 | }, 73 | )?; 74 | 75 | let streams_map = Arc::new(Mutex::new(HashMap::new())); 76 | let (mut new_stream_tx, new_stream_rx) = mpsc::channel(8); 77 | let (close_stream_tx, mut close_stream_rx) = mpsc::channel(8); 78 | let (close_session_tx, mut close_session_rx) = mpsc::channel(8); 79 | let udp_tx = Arc::clone(&conn); 80 | let udp_rx = Arc::clone(&conn); 81 | let cloned_streams_map = Arc::clone(&streams_map); 82 | let cloned_close_stream_tx = close_stream_tx.clone(); 83 | 84 | tokio::spawn(async move { 85 | let mut buf = vec![0u8; 8192]; 86 | 87 | loop { 88 | let incoming_stream = Session::incoming( 89 | &udp_rx, 90 | &mut buf, 91 | &cloned_streams_map, 92 | &cloned_close_stream_tx, 93 | &mut new_stream_tx, 94 | &mut remote_context, 95 | is_rtp, 96 | ); 97 | let close_stream = close_stream_rx.recv(); 98 | let close_session = close_session_rx.recv(); 99 | 100 | tokio::select! { 101 | result = incoming_stream => match result{ 102 | Ok(()) => {}, 103 | Err(err) => log::info!("{}", err), 104 | }, 105 | opt = close_stream => if let Some(ssrc) = opt { 106 | Session::close_stream(&cloned_streams_map, ssrc).await 107 | }, 108 | _ = close_session => break 109 | } 110 | } 111 | }); 112 | 113 | Ok(Session { 114 | local_context: Arc::new(Mutex::new(local_context)), 115 | streams_map, 116 | new_stream_rx: Arc::new(Mutex::new(new_stream_rx)), 117 | close_stream_tx, 118 | close_session_tx, 119 | udp_tx, 120 | is_rtp, 121 | }) 122 | } 123 | 124 | async fn close_stream(streams_map: &Arc>>>, ssrc: u32) { 125 | let mut streams = streams_map.lock().await; 126 | streams.remove(&ssrc); 127 | } 128 | 129 | async fn incoming( 130 | udp_rx: &Arc, 131 | buf: &mut [u8], 132 | streams_map: &Arc>>>, 133 | close_stream_tx: &mpsc::Sender, 134 | new_stream_tx: &mut mpsc::Sender>, 135 | remote_context: &mut Context, 136 | is_rtp: bool, 137 | ) -> Result<()> { 138 | let n = udp_rx.recv(buf).await?; 139 | if n == 0 { 140 | return Err(Error::SessionEof); 141 | } 142 | 143 | let decrypted = if is_rtp { 144 | remote_context.decrypt_rtp(&buf[0..n])? 145 | } else { 146 | remote_context.decrypt_rtcp(&buf[0..n])? 147 | }; 148 | 149 | let mut buf = &decrypted[..]; 150 | let ssrcs = if is_rtp { 151 | vec![rtp::header::Header::unmarshal(&mut buf)?.ssrc] 152 | } else { 153 | let pkts = rtcp::packet::unmarshal(&mut buf)?; 154 | destination_ssrc(&pkts) 155 | }; 156 | 157 | for ssrc in ssrcs { 158 | let (stream, is_new) = 159 | Session::get_or_create_stream(streams_map, close_stream_tx.clone(), is_rtp, ssrc) 160 | .await; 161 | if is_new { 162 | log::trace!( 163 | "srtp session got new {} stream {}", 164 | if is_rtp { "rtp" } else { "rtcp" }, 165 | ssrc 166 | ); 167 | new_stream_tx.send(Arc::clone(&stream)).await?; 168 | } 169 | 170 | match stream.buffer.write(&decrypted).await { 171 | Ok(_) => {} 172 | Err(err) => { 173 | // Silently drop data when the buffer is full. 174 | if util::Error::ErrBufferFull != err { 175 | return Err(err.into()); 176 | } 177 | } 178 | } 179 | } 180 | 181 | Ok(()) 182 | } 183 | 184 | async fn get_or_create_stream( 185 | streams_map: &Arc>>>, 186 | close_stream_tx: mpsc::Sender, 187 | is_rtp: bool, 188 | ssrc: u32, 189 | ) -> (Arc, bool) { 190 | let mut streams = streams_map.lock().await; 191 | 192 | if let Some(stream) = streams.get(&ssrc) { 193 | (Arc::clone(stream), false) 194 | } else { 195 | let stream = Arc::new(Stream::new(ssrc, close_stream_tx, is_rtp)); 196 | streams.insert(ssrc, Arc::clone(&stream)); 197 | (stream, true) 198 | } 199 | } 200 | 201 | /// open on the given SSRC to create a stream, it can be used 202 | /// if you want a certain SSRC, but don't want to wait for Accept 203 | pub async fn open(&self, ssrc: u32) -> Arc { 204 | let (stream, _) = Session::get_or_create_stream( 205 | &self.streams_map, 206 | self.close_stream_tx.clone(), 207 | self.is_rtp, 208 | ssrc, 209 | ) 210 | .await; 211 | 212 | stream 213 | } 214 | 215 | /// accept returns a stream to handle RTCP for a single SSRC 216 | pub async fn accept(&self) -> Result> { 217 | let mut new_stream_rx = self.new_stream_rx.lock().await; 218 | let result = new_stream_rx.recv().await; 219 | if let Some(stream) = result { 220 | Ok(stream) 221 | } else { 222 | Err(Error::SessionSrtpAlreadyClosed) 223 | } 224 | } 225 | 226 | pub async fn close(&self) -> Result<()> { 227 | self.close_session_tx.send(()).await?; 228 | 229 | Ok(()) 230 | } 231 | 232 | pub async fn write(&self, buf: &Bytes, is_rtp: bool) -> Result { 233 | if self.is_rtp != is_rtp { 234 | return Err(Error::SessionRtpRtcpTypeMismatch); 235 | } 236 | 237 | let encrypted = { 238 | let mut local_context = self.local_context.lock().await; 239 | 240 | if is_rtp { 241 | local_context.encrypt_rtp(buf)? 242 | } else { 243 | local_context.encrypt_rtcp(buf)? 244 | } 245 | }; 246 | 247 | Ok(self.udp_tx.send(&encrypted).await?) 248 | } 249 | 250 | pub async fn write_rtp(&self, pkt: &rtp::packet::Packet) -> Result { 251 | let raw = pkt.marshal()?; 252 | self.write(&raw, true).await 253 | } 254 | 255 | pub async fn write_rtcp( 256 | &self, 257 | pkt: &(dyn rtcp::packet::Packet + Send + Sync), 258 | ) -> Result { 259 | let raw = pkt.marshal()?; 260 | self.write(&raw, false).await 261 | } 262 | } 263 | 264 | /// create a list of Destination SSRCs 265 | /// that's a superset of all Destinations in the slice. 266 | fn destination_ssrc(pkts: &[Box]) -> Vec { 267 | let mut ssrc_set = HashSet::new(); 268 | for p in pkts { 269 | for ssrc in p.destination_ssrc() { 270 | ssrc_set.insert(ssrc); 271 | } 272 | } 273 | ssrc_set.into_iter().collect() 274 | } 275 | -------------------------------------------------------------------------------- /src/session/session_rtcp_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::error::Result; 3 | use crate::protection_profile::*; 4 | 5 | use rtcp::payload_feedbacks::*; 6 | use util::conn::conn_pipe::*; 7 | 8 | use bytes::{Bytes, BytesMut}; 9 | use std::sync::Arc; 10 | use tokio::sync::{mpsc, Mutex}; 11 | 12 | async fn build_session_srtcp_pair() -> Result<(Session, Session)> { 13 | let (ua, ub) = pipe(); 14 | 15 | let ca = Config { 16 | profile: ProtectionProfile::Aes128CmHmacSha1_80, 17 | keys: SessionKeys { 18 | local_master_key: vec![ 19 | 0xE1, 0xF9, 0x7A, 0x0D, 0x3E, 0x01, 0x8B, 0xE0, 0xD6, 0x4F, 0xA3, 0x2C, 0x06, 0xDE, 20 | 0x41, 0x39, 21 | ], 22 | local_master_salt: vec![ 23 | 0x0E, 0xC6, 0x75, 0xAD, 0x49, 0x8A, 0xFE, 0xEB, 0xB6, 0x96, 0x0B, 0x3A, 0xAB, 0xE6, 24 | ], 25 | remote_master_key: vec![ 26 | 0xE1, 0xF9, 0x7A, 0x0D, 0x3E, 0x01, 0x8B, 0xE0, 0xD6, 0x4F, 0xA3, 0x2C, 0x06, 0xDE, 27 | 0x41, 0x39, 28 | ], 29 | remote_master_salt: vec![ 30 | 0x0E, 0xC6, 0x75, 0xAD, 0x49, 0x8A, 0xFE, 0xEB, 0xB6, 0x96, 0x0B, 0x3A, 0xAB, 0xE6, 31 | ], 32 | }, 33 | 34 | local_rtp_options: None, 35 | remote_rtp_options: None, 36 | 37 | local_rtcp_options: None, 38 | remote_rtcp_options: None, 39 | }; 40 | 41 | let cb = Config { 42 | profile: ProtectionProfile::Aes128CmHmacSha1_80, 43 | keys: SessionKeys { 44 | local_master_key: vec![ 45 | 0xE1, 0xF9, 0x7A, 0x0D, 0x3E, 0x01, 0x8B, 0xE0, 0xD6, 0x4F, 0xA3, 0x2C, 0x06, 0xDE, 46 | 0x41, 0x39, 47 | ], 48 | local_master_salt: vec![ 49 | 0x0E, 0xC6, 0x75, 0xAD, 0x49, 0x8A, 0xFE, 0xEB, 0xB6, 0x96, 0x0B, 0x3A, 0xAB, 0xE6, 50 | ], 51 | remote_master_key: vec![ 52 | 0xE1, 0xF9, 0x7A, 0x0D, 0x3E, 0x01, 0x8B, 0xE0, 0xD6, 0x4F, 0xA3, 0x2C, 0x06, 0xDE, 53 | 0x41, 0x39, 54 | ], 55 | remote_master_salt: vec![ 56 | 0x0E, 0xC6, 0x75, 0xAD, 0x49, 0x8A, 0xFE, 0xEB, 0xB6, 0x96, 0x0B, 0x3A, 0xAB, 0xE6, 57 | ], 58 | }, 59 | 60 | local_rtp_options: None, 61 | remote_rtp_options: None, 62 | 63 | local_rtcp_options: None, 64 | remote_rtcp_options: None, 65 | }; 66 | 67 | let sa = Session::new(Arc::new(ua), ca, false).await?; 68 | let sb = Session::new(Arc::new(ub), cb, false).await?; 69 | 70 | Ok((sa, sb)) 71 | } 72 | 73 | const TEST_SSRC: u32 = 5000; 74 | 75 | #[tokio::test] 76 | async fn test_session_srtcp_accept() -> Result<()> { 77 | let (sa, sb) = build_session_srtcp_pair().await?; 78 | 79 | let rtcp_packet = picture_loss_indication::PictureLossIndication { 80 | media_ssrc: TEST_SSRC, 81 | ..Default::default() 82 | }; 83 | 84 | let test_payload = rtcp_packet.marshal()?; 85 | sa.write_rtcp(&rtcp_packet).await?; 86 | 87 | let read_stream = sb.accept().await?; 88 | let ssrc = read_stream.get_ssrc(); 89 | assert_eq!( 90 | ssrc, TEST_SSRC, 91 | "SSRC mismatch during accept exp({}) actual({})", 92 | TEST_SSRC, ssrc 93 | ); 94 | 95 | let mut read_buffer = BytesMut::with_capacity(test_payload.len()); 96 | read_buffer.resize(test_payload.len(), 0u8); 97 | read_stream.read(&mut read_buffer).await?; 98 | 99 | assert_eq!( 100 | &test_payload[..], 101 | &read_buffer[..], 102 | "Sent buffer does not match the one received exp({:?}) actual({:?})", 103 | &test_payload[..], 104 | &read_buffer[..] 105 | ); 106 | 107 | sa.close().await?; 108 | sb.close().await?; 109 | 110 | Ok(()) 111 | } 112 | 113 | #[tokio::test] 114 | async fn test_session_srtcp_listen() -> Result<()> { 115 | let (sa, sb) = build_session_srtcp_pair().await?; 116 | 117 | let rtcp_packet = picture_loss_indication::PictureLossIndication { 118 | media_ssrc: TEST_SSRC, 119 | ..Default::default() 120 | }; 121 | 122 | let test_payload = rtcp_packet.marshal()?; 123 | let read_stream = sb.open(TEST_SSRC).await; 124 | 125 | sa.write_rtcp(&rtcp_packet).await?; 126 | 127 | let mut read_buffer = BytesMut::with_capacity(test_payload.len()); 128 | read_buffer.resize(test_payload.len(), 0u8); 129 | read_stream.read(&mut read_buffer).await?; 130 | 131 | assert_eq!( 132 | &test_payload[..], 133 | &read_buffer[..], 134 | "Sent buffer does not match the one received exp({:?}) actual({:?})", 135 | &test_payload[..], 136 | &read_buffer[..] 137 | ); 138 | 139 | sa.close().await?; 140 | sb.close().await?; 141 | 142 | Ok(()) 143 | } 144 | 145 | fn encrypt_srtcp( 146 | context: &mut Context, 147 | pkt: &(dyn rtcp::packet::Packet + Send + Sync), 148 | ) -> Result { 149 | let decrypted = pkt.marshal()?; 150 | let encrypted = context.encrypt_rtcp(&decrypted)?; 151 | Ok(encrypted) 152 | } 153 | 154 | const PLI_PACKET_SIZE: usize = 8; 155 | 156 | async fn get_sender_ssrc(read_stream: &Arc) -> Result { 157 | let auth_tag_size = ProtectionProfile::Aes128CmHmacSha1_80.auth_tag_len(); 158 | 159 | let mut read_buffer = BytesMut::with_capacity(PLI_PACKET_SIZE + auth_tag_size); 160 | read_buffer.resize(PLI_PACKET_SIZE + auth_tag_size, 0u8); 161 | 162 | let (n, _) = read_stream.read_rtcp(&mut read_buffer).await?; 163 | let mut reader = &read_buffer[0..n]; 164 | let pli = picture_loss_indication::PictureLossIndication::unmarshal(&mut reader)?; 165 | 166 | Ok(pli.sender_ssrc) 167 | } 168 | 169 | #[tokio::test] 170 | async fn test_session_srtcp_replay_protection() -> Result<()> { 171 | let (sa, sb) = build_session_srtcp_pair().await?; 172 | 173 | let read_stream = sb.open(TEST_SSRC).await; 174 | 175 | // Generate test packets 176 | let mut packets = vec![]; 177 | let mut expected_ssrc = vec![]; 178 | { 179 | let mut local_context = sa.local_context.lock().await; 180 | for i in 0..0x10u32 { 181 | expected_ssrc.push(i); 182 | 183 | let packet = picture_loss_indication::PictureLossIndication { 184 | media_ssrc: TEST_SSRC, 185 | sender_ssrc: i, 186 | }; 187 | 188 | let encrypted = encrypt_srtcp(&mut local_context, &packet)?; 189 | 190 | packets.push(encrypted); 191 | } 192 | } 193 | 194 | let (done_tx, mut done_rx) = mpsc::channel::<()>(1); 195 | 196 | let received_ssrc = Arc::new(Mutex::new(vec![])); 197 | let cloned_received_ssrc = Arc::clone(&received_ssrc); 198 | let count = expected_ssrc.len(); 199 | 200 | tokio::spawn(async move { 201 | let mut i = 0; 202 | while i < count { 203 | match get_sender_ssrc(&read_stream).await { 204 | Ok(ssrc) => { 205 | let mut r = cloned_received_ssrc.lock().await; 206 | r.push(ssrc); 207 | 208 | i += 1; 209 | } 210 | Err(_) => break, 211 | } 212 | } 213 | 214 | drop(done_tx); 215 | }); 216 | 217 | // Write with replay attack 218 | for packet in &packets { 219 | sa.udp_tx.send(packet).await?; 220 | 221 | // Immediately replay 222 | sa.udp_tx.send(packet).await?; 223 | } 224 | for packet in &packets { 225 | // Delayed replay 226 | sa.udp_tx.send(packet).await?; 227 | } 228 | 229 | done_rx.recv().await; 230 | 231 | sa.close().await?; 232 | sb.close().await?; 233 | 234 | { 235 | let received_ssrc = received_ssrc.lock().await; 236 | assert_eq!(&expected_ssrc[..], &received_ssrc[..]); 237 | } 238 | 239 | Ok(()) 240 | } 241 | -------------------------------------------------------------------------------- /src/session/session_rtp_test.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::error::Result; 3 | use crate::protection_profile::*; 4 | 5 | use bytes::{Bytes, BytesMut}; 6 | use std::{collections::HashMap, sync::Arc}; 7 | use tokio::{ 8 | net::UdpSocket, 9 | sync::{mpsc, Mutex}, 10 | }; 11 | 12 | async fn build_session_srtp_pair() -> Result<(Session, Session)> { 13 | let ua = UdpSocket::bind("127.0.0.1:0").await?; 14 | let ub = UdpSocket::bind("127.0.0.1:0").await?; 15 | 16 | ua.connect(ub.local_addr()?).await?; 17 | ub.connect(ua.local_addr()?).await?; 18 | 19 | let ca = Config { 20 | profile: ProtectionProfile::Aes128CmHmacSha1_80, 21 | keys: SessionKeys { 22 | local_master_key: vec![ 23 | 0xE1, 0xF9, 0x7A, 0x0D, 0x3E, 0x01, 0x8B, 0xE0, 0xD6, 0x4F, 0xA3, 0x2C, 0x06, 0xDE, 24 | 0x41, 0x39, 25 | ], 26 | local_master_salt: vec![ 27 | 0x0E, 0xC6, 0x75, 0xAD, 0x49, 0x8A, 0xFE, 0xEB, 0xB6, 0x96, 0x0B, 0x3A, 0xAB, 0xE6, 28 | ], 29 | remote_master_key: vec![ 30 | 0xE1, 0xF9, 0x7A, 0x0D, 0x3E, 0x01, 0x8B, 0xE0, 0xD6, 0x4F, 0xA3, 0x2C, 0x06, 0xDE, 31 | 0x41, 0x39, 32 | ], 33 | remote_master_salt: vec![ 34 | 0x0E, 0xC6, 0x75, 0xAD, 0x49, 0x8A, 0xFE, 0xEB, 0xB6, 0x96, 0x0B, 0x3A, 0xAB, 0xE6, 35 | ], 36 | }, 37 | 38 | local_rtp_options: None, 39 | remote_rtp_options: None, 40 | 41 | local_rtcp_options: None, 42 | remote_rtcp_options: None, 43 | }; 44 | 45 | let cb = Config { 46 | profile: ProtectionProfile::Aes128CmHmacSha1_80, 47 | keys: SessionKeys { 48 | local_master_key: vec![ 49 | 0xE1, 0xF9, 0x7A, 0x0D, 0x3E, 0x01, 0x8B, 0xE0, 0xD6, 0x4F, 0xA3, 0x2C, 0x06, 0xDE, 50 | 0x41, 0x39, 51 | ], 52 | local_master_salt: vec![ 53 | 0x0E, 0xC6, 0x75, 0xAD, 0x49, 0x8A, 0xFE, 0xEB, 0xB6, 0x96, 0x0B, 0x3A, 0xAB, 0xE6, 54 | ], 55 | remote_master_key: vec![ 56 | 0xE1, 0xF9, 0x7A, 0x0D, 0x3E, 0x01, 0x8B, 0xE0, 0xD6, 0x4F, 0xA3, 0x2C, 0x06, 0xDE, 57 | 0x41, 0x39, 58 | ], 59 | remote_master_salt: vec![ 60 | 0x0E, 0xC6, 0x75, 0xAD, 0x49, 0x8A, 0xFE, 0xEB, 0xB6, 0x96, 0x0B, 0x3A, 0xAB, 0xE6, 61 | ], 62 | }, 63 | 64 | local_rtp_options: None, 65 | remote_rtp_options: None, 66 | 67 | local_rtcp_options: None, 68 | remote_rtcp_options: None, 69 | }; 70 | 71 | let sa = Session::new(Arc::new(ua), ca, true).await?; 72 | let sb = Session::new(Arc::new(ub), cb, true).await?; 73 | 74 | Ok((sa, sb)) 75 | } 76 | 77 | const TEST_SSRC: u32 = 5000; 78 | const RTP_HEADER_SIZE: usize = 12; 79 | 80 | #[tokio::test] 81 | async fn test_session_srtp_accept() -> Result<()> { 82 | let test_payload = Bytes::from_static(&[0x00, 0x01, 0x03, 0x04]); 83 | let mut read_buffer = BytesMut::with_capacity(RTP_HEADER_SIZE + test_payload.len()); 84 | read_buffer.resize(RTP_HEADER_SIZE + test_payload.len(), 0u8); 85 | let (sa, sb) = build_session_srtp_pair().await?; 86 | 87 | let packet = rtp::packet::Packet { 88 | header: rtp::header::Header { 89 | ssrc: TEST_SSRC, 90 | ..Default::default() 91 | }, 92 | payload: test_payload.clone(), 93 | }; 94 | sa.write_rtp(&packet).await?; 95 | 96 | let read_stream = sb.accept().await?; 97 | let ssrc = read_stream.get_ssrc(); 98 | assert_eq!( 99 | ssrc, TEST_SSRC, 100 | "SSRC mismatch during accept exp({}) actual({})", 101 | TEST_SSRC, ssrc 102 | ); 103 | 104 | read_stream.read(&mut read_buffer).await?; 105 | 106 | assert_eq!( 107 | &test_payload[..], 108 | &read_buffer[RTP_HEADER_SIZE..], 109 | "Sent buffer does not match the one received exp({:?}) actual({:?})", 110 | &test_payload[..], 111 | &read_buffer[RTP_HEADER_SIZE..] 112 | ); 113 | 114 | sa.close().await?; 115 | sb.close().await?; 116 | 117 | Ok(()) 118 | } 119 | 120 | #[tokio::test] 121 | async fn test_session_srtp_listen() -> Result<()> { 122 | let test_payload = Bytes::from_static(&[0x00, 0x01, 0x03, 0x04]); 123 | let mut read_buffer = BytesMut::with_capacity(RTP_HEADER_SIZE + test_payload.len()); 124 | read_buffer.resize(RTP_HEADER_SIZE + test_payload.len(), 0u8); 125 | let (sa, sb) = build_session_srtp_pair().await?; 126 | 127 | let packet = rtp::packet::Packet { 128 | header: rtp::header::Header { 129 | ssrc: TEST_SSRC, 130 | ..Default::default() 131 | }, 132 | payload: test_payload.clone(), 133 | }; 134 | 135 | let read_stream = sb.open(TEST_SSRC).await; 136 | 137 | sa.write_rtp(&packet).await?; 138 | 139 | read_stream.read(&mut read_buffer).await?; 140 | 141 | assert_eq!( 142 | &test_payload[..], 143 | &read_buffer[RTP_HEADER_SIZE..], 144 | "Sent buffer does not match the one received exp({:?}) actual({:?})", 145 | &test_payload[..], 146 | &read_buffer[RTP_HEADER_SIZE..] 147 | ); 148 | 149 | sa.close().await?; 150 | sb.close().await?; 151 | 152 | Ok(()) 153 | } 154 | 155 | #[tokio::test] 156 | async fn test_session_srtp_multi_ssrc() -> Result<()> { 157 | let ssrcs = vec![5000, 5001, 5002]; 158 | let test_payload = Bytes::from_static(&[0x00, 0x01, 0x03, 0x04]); 159 | let mut read_buffer = BytesMut::with_capacity(RTP_HEADER_SIZE + test_payload.len()); 160 | read_buffer.resize(RTP_HEADER_SIZE + test_payload.len(), 0u8); 161 | let (sa, sb) = build_session_srtp_pair().await?; 162 | 163 | let mut read_streams = HashMap::new(); 164 | for ssrc in &ssrcs { 165 | let read_stream = sb.open(*ssrc).await; 166 | read_streams.insert(*ssrc, read_stream); 167 | } 168 | 169 | for ssrc in &ssrcs { 170 | let packet = rtp::packet::Packet { 171 | header: rtp::header::Header { 172 | ssrc: *ssrc, 173 | ..Default::default() 174 | }, 175 | payload: test_payload.clone(), 176 | }; 177 | sa.write_rtp(&packet).await?; 178 | 179 | if let Some(read_stream) = read_streams.get_mut(ssrc) { 180 | read_stream.read(&mut read_buffer).await?; 181 | 182 | assert_eq!( 183 | &test_payload[..], 184 | &read_buffer[RTP_HEADER_SIZE..], 185 | "Sent buffer does not match the one received exp({:?}) actual({:?})", 186 | &test_payload[..], 187 | &read_buffer[RTP_HEADER_SIZE..] 188 | ); 189 | } else { 190 | assert!(false, "ssrc {} not found", *ssrc); 191 | } 192 | } 193 | 194 | sa.close().await?; 195 | sb.close().await?; 196 | 197 | Ok(()) 198 | } 199 | 200 | fn encrypt_srtp(context: &mut Context, pkt: &rtp::packet::Packet) -> Result { 201 | let decrypted = pkt.marshal()?; 202 | let encrypted = context.encrypt_rtp(&decrypted)?; 203 | Ok(encrypted) 204 | } 205 | 206 | async fn payload_srtp( 207 | read_stream: &Arc, 208 | header_size: usize, 209 | expected_payload: &[u8], 210 | ) -> Result { 211 | let mut read_buffer = BytesMut::with_capacity(header_size + expected_payload.len()); 212 | read_buffer.resize(header_size + expected_payload.len(), 0u8); 213 | 214 | let (n, hdr) = read_stream.read_rtp(&mut read_buffer).await?; 215 | 216 | assert_eq!( 217 | expected_payload, 218 | &read_buffer[header_size..n], 219 | "Sent buffer does not match the one received exp({:?}) actual({:?})", 220 | expected_payload, 221 | &read_buffer[header_size..n] 222 | ); 223 | 224 | Ok(hdr.sequence_number) 225 | } 226 | 227 | #[tokio::test] 228 | async fn test_session_srtp_replay_protection() -> Result<()> { 229 | let test_payload = Bytes::from_static(&[0x00, 0x01, 0x03, 0x04]); 230 | 231 | let (sa, sb) = build_session_srtp_pair().await?; 232 | 233 | let read_stream = sb.open(TEST_SSRC).await; 234 | 235 | // Generate test packets 236 | let mut packets = vec![]; 237 | let mut expected_sequence_number = vec![]; 238 | { 239 | let mut local_context = sa.local_context.lock().await; 240 | let mut i = 0xFFF0u16; 241 | while i != 0x10 { 242 | expected_sequence_number.push(i); 243 | 244 | let packet = rtp::packet::Packet { 245 | header: rtp::header::Header { 246 | ssrc: TEST_SSRC, 247 | sequence_number: i, 248 | ..Default::default() 249 | }, 250 | payload: test_payload.clone(), 251 | }; 252 | 253 | let encrypted = encrypt_srtp(&mut local_context, &packet)?; 254 | 255 | packets.push(encrypted); 256 | 257 | if i == 0xFFFF { 258 | i = 0; 259 | } else { 260 | i += 1; 261 | } 262 | } 263 | } 264 | 265 | let (done_tx, mut done_rx) = mpsc::channel::<()>(1); 266 | 267 | let received_sequence_number = Arc::new(Mutex::new(vec![])); 268 | let cloned_received_sequence_number = Arc::clone(&received_sequence_number); 269 | let count = expected_sequence_number.len(); 270 | 271 | tokio::spawn(async move { 272 | let mut i = 0; 273 | while i < count { 274 | match payload_srtp(&read_stream, RTP_HEADER_SIZE, &test_payload).await { 275 | Ok(seq) => { 276 | let mut r = cloned_received_sequence_number.lock().await; 277 | r.push(seq); 278 | 279 | i += 1; 280 | } 281 | Err(err) => { 282 | assert!(false, "{}", err); 283 | break; 284 | } 285 | } 286 | } 287 | 288 | drop(done_tx); 289 | }); 290 | 291 | // Write with replay attack 292 | for packet in &packets { 293 | sa.udp_tx.send(packet).await?; 294 | 295 | // Immediately replay 296 | sa.udp_tx.send(packet).await?; 297 | } 298 | for packet in &packets { 299 | // Delayed replay 300 | sa.udp_tx.send(packet).await?; 301 | } 302 | 303 | done_rx.recv().await; 304 | 305 | sa.close().await?; 306 | sb.close().await?; 307 | 308 | { 309 | let received_sequence_number = received_sequence_number.lock().await; 310 | assert_eq!(&expected_sequence_number[..], &received_sequence_number[..]); 311 | } 312 | 313 | Ok(()) 314 | } 315 | -------------------------------------------------------------------------------- /src/stream.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, Result}; 2 | use util::{marshal::*, Buffer}; 3 | 4 | use tokio::sync::mpsc; 5 | 6 | /// Limit the buffer size to 1MB 7 | pub const SRTP_BUFFER_SIZE: usize = 1000 * 1000; 8 | 9 | /// Limit the buffer size to 100KB 10 | pub const SRTCP_BUFFER_SIZE: usize = 100 * 1000; 11 | 12 | /// Stream handles decryption for a single RTP/RTCP SSRC 13 | #[derive(Debug)] 14 | pub struct Stream { 15 | ssrc: u32, 16 | tx: mpsc::Sender, 17 | pub(crate) buffer: Buffer, 18 | is_rtp: bool, 19 | } 20 | 21 | impl Stream { 22 | /// Create a new stream 23 | pub fn new(ssrc: u32, tx: mpsc::Sender, is_rtp: bool) -> Self { 24 | Stream { 25 | ssrc, 26 | tx, 27 | // Create a buffer with a 1MB limit 28 | buffer: Buffer::new( 29 | 0, 30 | if is_rtp { 31 | SRTP_BUFFER_SIZE 32 | } else { 33 | SRTCP_BUFFER_SIZE 34 | }, 35 | ), 36 | is_rtp, 37 | } 38 | } 39 | 40 | /// GetSSRC returns the SSRC we are demuxing for 41 | pub fn get_ssrc(&self) -> u32 { 42 | self.ssrc 43 | } 44 | 45 | /// Check if RTP is a stream. 46 | pub fn is_rtp_stream(&self) -> bool { 47 | self.is_rtp 48 | } 49 | 50 | /// Read reads and decrypts full RTP packet from the nextConn 51 | pub async fn read(&self, buf: &mut [u8]) -> Result { 52 | Ok(self.buffer.read(buf, None).await?) 53 | } 54 | 55 | /// ReadRTP reads and decrypts full RTP packet and its header from the nextConn 56 | pub async fn read_rtp(&self, buf: &mut [u8]) -> Result<(usize, rtp::header::Header)> { 57 | if !self.is_rtp { 58 | return Err(Error::InvalidRtpStream); 59 | } 60 | 61 | let n = self.buffer.read(buf, None).await?; 62 | let mut b = &buf[..n]; 63 | let header = rtp::header::Header::unmarshal(&mut b)?; 64 | 65 | Ok((n, header)) 66 | } 67 | 68 | /// read_rtcp reads and decrypts full RTP packet and its header from the nextConn 69 | pub async fn read_rtcp(&self, buf: &mut [u8]) -> Result<(usize, rtcp::header::Header)> { 70 | if self.is_rtp { 71 | return Err(Error::InvalidRtcpStream); 72 | } 73 | 74 | let n = self.buffer.read(buf, None).await?; 75 | let mut b = &buf[..n]; 76 | let header = rtcp::header::Header::unmarshal(&mut b)?; 77 | 78 | Ok((n, header)) 79 | } 80 | 81 | /// Close removes the ReadStream from the session and cleans up any associated state 82 | pub async fn close(&self) -> Result<()> { 83 | self.buffer.close().await; 84 | let _ = self.tx.send(self.ssrc).await; 85 | Ok(()) 86 | } 87 | } 88 | --------------------------------------------------------------------------------