├── .cargo └── config.toml ├── .github └── workflows │ ├── PULL_REQUEST_TEMPLATE.md │ ├── build_linux.yml │ ├── build_windows.yml │ ├── clippy.yml │ ├── fmt.yml │ └── linelint.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── SECURITY.md ├── SUPPORT.md ├── examples ├── Cargo.lock ├── Cargo.toml ├── README.md └── bin │ ├── certs │ ├── RootCA.crt │ ├── RootCA.key │ ├── RootCA.pem │ ├── localhost.crt │ ├── localhost.key │ ├── localhost.pem │ ├── rustls-client.pfx │ └── rustls-server.pfx │ ├── sample_internet_client.rs │ ├── sample_internet_client_platform.rs │ ├── sample_local_client.rs │ ├── sample_server.rs │ └── sample_windows_client_server.rs └── rustls-symcrypt ├── Cargo.lock ├── Cargo.toml ├── README.md ├── src ├── cipher_suites.rs ├── ecdh.rs ├── hash.rs ├── hmac.rs ├── lib.rs ├── signer.rs ├── tls12.rs ├── tls13.rs └── verify.rs └── tests ├── certs ├── RootCA.crt ├── RootCA.key ├── RootCA.pem ├── localhost.crt ├── localhost.key └── localhost.pem └── full_test.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.aarch64-unknown-linux-gnu] 2 | linker = "aarch64-linux-gnu-gcc" 3 | -------------------------------------------------------------------------------- /.github/workflows/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Description of Changes: 2 | 3 | 4 | 5 | ### Breaking Changes if any: 6 | 7 | 8 | 9 | 10 | ### ✅ Admin Checklist 11 | - [ ] Review the PR description and ensure all necessary details are included. 12 | - [ ] Update/add unit tests for changed code. 13 | - [ ] Update/add documentation for new or changed APIs. 14 | - [ ] Run `cargo test --all-features` on `Windows` and `WSL`. 15 | -------------------------------------------------------------------------------- /.github/workflows/build_linux.yml: -------------------------------------------------------------------------------- 1 | name: Build Linux 2 | 3 | # TOOD: Investigate full tests + benchmarking 4 | 5 | on: 6 | push: 7 | branches: [ "main" ] 8 | pull_request: 9 | 10 | env: 11 | CARGO_TERM_COLOR: always 12 | FEATURES_TO_TEST: chacha, x25519 13 | 14 | jobs: 15 | build: 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | include: 20 | - target: x86_64-unknown-linux-gnu 21 | os: ubuntu-latest 22 | runs-on: ubuntu-latest 23 | run-tests: true 24 | - target: aarch64-unknown-linux-gnu 25 | os: ubuntu-24.04-arm 26 | runs-on: ubuntu-24.04-arm 27 | run-tests: true 28 | 29 | runs-on: ${{ matrix.os }} 30 | name: ${{ matrix.target }} 31 | env: 32 | CARGO_BUILD_TARGET: ${{ matrix.target }} 33 | 34 | steps: 35 | # Checkout rustls-symcrypt code 36 | - name: Checkout rustls-symcrypt 37 | uses: actions/checkout@v4 38 | with: 39 | submodules: true 40 | 41 | - uses: Swatinem/rust-cache@v2 42 | 43 | # Download SymCrypt via PMC 44 | - name: Install SymCrypt via PMC 45 | shell: bash 46 | run: | 47 | curl -sSL -O https://packages.microsoft.com/config/ubuntu/24.04/packages-microsoft-prod.deb 48 | sudo dpkg -i packages-microsoft-prod.deb 49 | sudo apt-get update 50 | sudo apt-get install -y symcrypt 51 | 52 | - name: Install host target 53 | shell: pwsh 54 | run: | 55 | rustup target add ${{ matrix.target }} 56 | if ("${{ matrix.target }}" -match "aarch64-unknown-linux-gnu") { 57 | sudo apt update 58 | sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu 59 | } 60 | 61 | - name: Debug build 62 | run: cargo build --locked --verbose --target ${{ matrix.target }} 63 | 64 | - name: Release build 65 | run: cargo build --release --locked --verbose --target ${{ matrix.target }} 66 | 67 | # TODO: re-enable when static linking is ready. 68 | # - name: Run tests (Debug, dynamic) 69 | # if: matrix.run-tests 70 | # run: cargo test --locked --verbose --all-features --target ${{ matrix.target }} 71 | 72 | # - name: Run tests (Release, dynamic) 73 | # if: matrix.run-tests 74 | # run: cargo test --release --locked --verbose --all-features --target ${{ matrix.target }} 75 | 76 | - name: Run test (Debug, static) 77 | if: matrix.run-tests 78 | run: cargo test --features ${{ env.FEATURES_TO_TEST }} --target ${{ matrix.target }} 79 | 80 | - name: Run test (Release, static) 81 | if: matrix.run-tests 82 | run: cargo test --release --features ${{ env.FEATURES_TO_TEST }} --target ${{ matrix.target }} 83 | -------------------------------------------------------------------------------- /.github/workflows/build_windows.yml: -------------------------------------------------------------------------------- 1 | name: Build Windows 2 | 3 | # TOOD: Investigate full tests + benchmarking 4 | 5 | on: 6 | push: 7 | branches: [ "main" ] 8 | pull_request: 9 | 10 | env: 11 | CARGO_TERM_COLOR: always 12 | FEATURES_TO_TEST: chacha, x25519 13 | 14 | jobs: 15 | build: 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | include: 20 | - target: x86_64-pc-windows-msvc 21 | os: windows-latest 22 | runs-on: windows-latest 23 | symcrypt: "https://github.com/microsoft/SymCrypt/releases/download/v103.8.0/symcrypt-windows-amd64-release-103.8.0-53be637d.zip" 24 | run-tests: true 25 | - target: aarch64-pc-windows-msvc 26 | os: windows-latest 27 | runs-on: windows-latest 28 | symcrypt: "https://github.com/microsoft/SymCrypt/releases/download/v103.8.0/symcrypt-windows-arm64-release-103.8.0-53be637d.zip" 29 | run-tests: false # Windows doesn't support ARM64 emulation 30 | runs-on: ${{ matrix.os }} 31 | name: ${{ matrix.target }} 32 | env: 33 | CARGO_BUILD_TARGET: ${{ matrix.target }} 34 | 35 | 36 | steps: 37 | - uses: Swatinem/rust-cache@v2 38 | 39 | # Checkout rustls-symcrypt code 40 | - name: Checkout rustls-symcrypt 41 | uses: actions/checkout@v4 42 | with: 43 | submodules: true 44 | 45 | 46 | # Install host architecture, required for cross-compilation since there is no arm64-windows-msvc runner 47 | - name: Install host target 48 | shell: pwsh 49 | run: | 50 | rustup target add ${{ matrix.target }} 51 | 52 | - name: Download SymCrypt and Set Environment Variables 53 | shell: pwsh 54 | run: | 55 | Invoke-WebRequest -Uri ${{ matrix.symcrypt }} -OutFile symcrypt.zip 56 | New-Item -ItemType Directory -Force -Path symcrypt 57 | Expand-Archive -Path symcrypt.zip -DestinationPath symcrypt 58 | echo "$env:GITHUB_WORKSPACE\symcrypt\dll" >> $env:GITHUB_PATH 59 | echo "SYMCRYPT_LIB_PATH=$env:GITHUB_WORKSPACE\symcrypt\dll" >> $env:GITHUB_ENV 60 | echo "PATH=$env:GITHUB_WORKSPACE\symcrypt\dll;$env:PATH" >> $env:GITHUB_ENV 61 | 62 | - name: Debug build 63 | run: cargo build --workspace --exclude examples --locked --verbose --target ${{ matrix.target }} 64 | 65 | - name: Release build 66 | run: cargo build --workspace --exclude examples --release --locked --verbose --target ${{ matrix.target }} 67 | 68 | # TODO: re-enable when static linking is ready. 69 | # - name: Run tests (Debug, dynamic) 70 | # if: matrix.run-tests 71 | # run: cargo test --workspace --exclude examples --locked --verbose --all-features --target ${{ matrix.target }} 72 | 73 | # - name: Run tests (Release, dynamic) 74 | # if: matrix.run-tests 75 | # run: cargo test --workspace --exclude examples --release --locked --verbose --all-features --target ${{ matrix.target }} 76 | 77 | # Disabling examples for testing 78 | 79 | - name: Run test (Debug, static) 80 | if: matrix.run-tests 81 | run: cargo test --features ${{ env.FEATURES_TO_TEST }} --locked --target ${{ matrix.target }} --test full_test 82 | 83 | - name: Run test (Release, static) 84 | if: matrix.run-tests 85 | run: cargo test --release --features ${{ env.FEATURES_TO_TEST }} --locked --target ${{ matrix.target }} --test full_test 86 | -------------------------------------------------------------------------------- /.github/workflows/clippy.yml: -------------------------------------------------------------------------------- 1 | name: Cargo Clippy Check 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | SYMCRYPT_LIB_PATH: "/dev/null" # Dummy value to bypass the env variable check on Windows 11 | 12 | 13 | jobs: 14 | clippy: 15 | name: Clippy Check 16 | runs-on: ${{ matrix.os }} 17 | 18 | strategy: 19 | matrix: 20 | os: [ubuntu-latest, windows-latest] # Run on Linux and Windows 21 | 22 | steps: 23 | - name: Checkout sources 24 | uses: actions/checkout@v4 25 | 26 | - name: Install Rust toolchain 27 | uses: dtolnay/rust-toolchain@stable 28 | with: 29 | components: clippy 30 | 31 | - name: Run Clippy 32 | run: cargo clippy --all-targets --all-features -- -D warnings 33 | -------------------------------------------------------------------------------- /.github/workflows/fmt.yml: -------------------------------------------------------------------------------- 1 | name: Cargo Fmt Check 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | format: 13 | name: Format Check 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout sources 18 | uses: actions/checkout@v4 19 | 20 | - name: Install Rust toolchain 21 | uses: dtolnay/rust-toolchain@stable 22 | with: 23 | components: rustfmt 24 | 25 | - name: Check formatting 26 | run: cargo fmt --all -- --check 27 | -------------------------------------------------------------------------------- /.github/workflows/linelint.yml: -------------------------------------------------------------------------------- 1 | name: linelint 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | 8 | jobs: 9 | linelint: 10 | runs-on: ubuntu-latest 11 | name: Check if all files end in newline 12 | steps: 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | - name: Linelint 16 | uses: fernandrone/linelint@0.0.6 17 | id: linelint 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | rustls/symcrypt/.vscode/settings.json 2 | examples/target 3 | rustls-symcrypt/lcov.info 4 | rustls-symcrypt/.vscode/settings.json 5 | rustls-symcrypt/target 6 | .vscode/ 7 | /target 8 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "rustls-symcrypt", 4 | "examples", 5 | ] 6 | resolver = "2" 7 | 8 | [workspace.package] 9 | edition = "2021" 10 | rust-version = "1.64.0" 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 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 | # SymCrypt Provider for Rustls 2 | 3 | This crate provides integration for using [rust-symcrypt](https://github.com/microsoft/rust-symcrypt) cryptographic functionalities with [rustls](https://github.com/rustls/rustls), by implementing the required traits specified by `rustls`. 4 | 5 | 6 | ### Supported Configurations 7 | 8 | | Operating Environment | Architecture | Dynamic Linking | 9 | | --------------------- | ----------------- | ----------- | 10 | | Windows user mode | AMD64, ARM64 | ✅ | 11 | | Ubuntu (Tested via WSL) | AMD64, ARM64 | ✅ | 12 | | Azure Linux 3 | AMD64, ARM64 | ✅ | 13 | | Azure Linux 2 | AMD64, ARM64 | ❌ | 14 | 15 | ## Dependencies 16 | 17 | This crate depends on the [symcrypt](https://github.com/microsoft/rust-symcrypt) crate and requires you have the necessary `symcrypt` binaries for your architecture. 18 | Refer to the [rust-symcrypt Quick Start Guide](https://github.com/microsoft/rust-symcrypt/tree/main/rust-symcrypt#quick-start-guide) to download the required binaries. 19 | 20 | ## Usage 21 | 22 | Add `rustls-symcrypt` to your `Cargo.toml`: 23 | **Note:** If you wish to enable `x25519` or `chacha` you may add it as a feature at this time. 24 | 25 | ```toml 26 | [dependencies] 27 | rustls = { version = "0.23.0", features = ["tls12", "std", "custom-provider"], default-features = false } 28 | rustls_symcrypt = "0.2.1" 29 | # To enable the chacha feature: 30 | # rustls_symcrypt = {version = "0.2.1", features = ["chacha"]} 31 | ``` 32 | 33 | ## Supported Ciphers 34 | 35 | Supported cipher suites are listed below, ordered by preference. IE: The default configuration prioritizes `TLS13_AES_256_GCM_SHA384` over `TLS13_AES_128_GCM_SHA256`. 36 | 37 | ### TLS 1.3 38 | 39 | ```ignore 40 | TLS13_AES_256_GCM_SHA384 41 | TLS13_AES_128_GCM_SHA256 42 | TLS13_CHACHA20_POLY1305_SHA256 // Enabled with the `chacha` feature 43 | ``` 44 | 45 | **Note:** `TLS13_CHACHA20_POLY1305_SHA256` is disabled by default. Enable the `chacha` feature in your `Cargo.toml` to use this cipher suite. 46 | 47 | ### TLS 1.2 48 | 49 | ```ignore 50 | TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 51 | TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 52 | TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 // Enabled with the `chacha` feature 53 | TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 54 | TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 55 | TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 // Enabled with the `chacha` feature 56 | ``` 57 | 58 | ## Supported Key Exchanges 59 | 60 | Key exchanges are listed below, ordered by preference. IE: `SECP384R1` is preferred over `SECP256R1`. 61 | 62 | ```ignore 63 | SECP384R1 64 | SECP256R1 65 | X25519 // Enabled with the `x25519` feature 66 | ``` 67 | 68 | **Note:** `X25519` is disabled by default. To enable, add `x25519` feature in your `Cargo.toml`. 69 | 70 | ## Example Code 71 | 72 | The `examples` directory showcases how to use the `rustls-symcrypt` provider with `rustls` for both a client configuration and a server configuration by taking advantage of `rustls::ClientConfig::builder_with_provider()`. 73 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet) and [Xamarin](https://github.com/xamarin). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # TODO: The maintainer of this repo has not yet edited this file 2 | 3 | **REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project? 4 | 5 | - **No CSS support:** Fill out this template with information about how to file issues and get help. 6 | - **Yes CSS support:** Fill out an intake form at [aka.ms/onboardsupport](https://aka.ms/onboardsupport). CSS will work with/help you to determine next steps. 7 | - **Not sure?** Fill out an intake as though the answer were "Yes". CSS will help you decide. 8 | 9 | *Then remove this first heading from this SUPPORT.MD file before publishing your repo.* 10 | 11 | # Support 12 | 13 | ## How to file issues and get help 14 | 15 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 16 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 17 | feature request as a new Issue. 18 | 19 | For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE 20 | FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER 21 | CHANNEL. WHERE WILL YOU HELP PEOPLE?**. 22 | 23 | ## Microsoft Support Policy 24 | 25 | Support for this **PROJECT or PRODUCT** is limited to the resources listed above. 26 | -------------------------------------------------------------------------------- /examples/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anyhow" 16 | version = "1.0.95" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" 19 | 20 | [[package]] 21 | name = "autocfg" 22 | version = "1.4.0" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 25 | 26 | [[package]] 27 | name = "aws-lc-rs" 28 | version = "1.12.2" 29 | source = "registry+https://github.com/rust-lang/crates.io-index" 30 | checksum = "4c2b7ddaa2c56a367ad27a094ad8ef4faacf8a617c2575acb2ba88949df999ca" 31 | dependencies = [ 32 | "aws-lc-sys", 33 | "paste", 34 | "zeroize", 35 | ] 36 | 37 | [[package]] 38 | name = "aws-lc-sys" 39 | version = "0.25.1" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "54ac4f13dad353b209b34cbec082338202cbc01c8f00336b55c750c13ac91f8f" 42 | dependencies = [ 43 | "bindgen", 44 | "cc", 45 | "cmake", 46 | "dunce", 47 | "fs_extra", 48 | "paste", 49 | ] 50 | 51 | [[package]] 52 | name = "base16ct" 53 | version = "0.2.0" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" 56 | 57 | [[package]] 58 | name = "base64ct" 59 | version = "1.6.0" 60 | source = "registry+https://github.com/rust-lang/crates.io-index" 61 | checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" 62 | 63 | [[package]] 64 | name = "bindgen" 65 | version = "0.69.5" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "271383c67ccabffb7381723dea0672a673f292304fcb45c01cc648c7a8d58088" 68 | dependencies = [ 69 | "bitflags", 70 | "cexpr", 71 | "clang-sys", 72 | "itertools", 73 | "lazy_static", 74 | "lazycell", 75 | "log", 76 | "prettyplease", 77 | "proc-macro2", 78 | "quote", 79 | "regex", 80 | "rustc-hash", 81 | "shlex", 82 | "syn", 83 | "which", 84 | ] 85 | 86 | [[package]] 87 | name = "bitflags" 88 | version = "2.8.0" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" 91 | 92 | [[package]] 93 | name = "bytes" 94 | version = "1.10.0" 95 | source = "registry+https://github.com/rust-lang/crates.io-index" 96 | checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9" 97 | 98 | [[package]] 99 | name = "cc" 100 | version = "1.2.13" 101 | source = "registry+https://github.com/rust-lang/crates.io-index" 102 | checksum = "c7777341816418c02e033934a09f20dc0ccaf65a5201ef8a450ae0105a573fda" 103 | dependencies = [ 104 | "jobserver", 105 | "libc", 106 | "shlex", 107 | ] 108 | 109 | [[package]] 110 | name = "cesu8" 111 | version = "1.1.0" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" 114 | 115 | [[package]] 116 | name = "cexpr" 117 | version = "0.6.0" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 120 | dependencies = [ 121 | "nom", 122 | ] 123 | 124 | [[package]] 125 | name = "cfg-if" 126 | version = "1.0.0" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 129 | 130 | [[package]] 131 | name = "clang-sys" 132 | version = "1.8.1" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 135 | dependencies = [ 136 | "glob", 137 | "libc", 138 | "libloading", 139 | ] 140 | 141 | [[package]] 142 | name = "cmake" 143 | version = "0.1.54" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "e7caa3f9de89ddbe2c607f4101924c5abec803763ae9534e4f4d7d8f84aa81f0" 146 | dependencies = [ 147 | "cc", 148 | ] 149 | 150 | [[package]] 151 | name = "combine" 152 | version = "4.6.7" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" 155 | dependencies = [ 156 | "bytes", 157 | "memchr", 158 | ] 159 | 160 | [[package]] 161 | name = "const-oid" 162 | version = "0.9.6" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" 165 | 166 | [[package]] 167 | name = "core-foundation" 168 | version = "0.9.4" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 171 | dependencies = [ 172 | "core-foundation-sys", 173 | "libc", 174 | ] 175 | 176 | [[package]] 177 | name = "core-foundation-sys" 178 | version = "0.8.7" 179 | source = "registry+https://github.com/rust-lang/crates.io-index" 180 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 181 | 182 | [[package]] 183 | name = "der" 184 | version = "0.7.9" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" 187 | dependencies = [ 188 | "const-oid", 189 | "zeroize", 190 | ] 191 | 192 | [[package]] 193 | name = "dunce" 194 | version = "1.0.5" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" 197 | 198 | [[package]] 199 | name = "either" 200 | version = "1.13.0" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 203 | 204 | [[package]] 205 | name = "errno" 206 | version = "0.3.10" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" 209 | dependencies = [ 210 | "libc", 211 | "windows-sys 0.59.0", 212 | ] 213 | 214 | [[package]] 215 | name = "fs_extra" 216 | version = "1.3.0" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" 219 | 220 | [[package]] 221 | name = "generic-array" 222 | version = "0.14.7" 223 | source = "registry+https://github.com/rust-lang/crates.io-index" 224 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 225 | dependencies = [ 226 | "typenum", 227 | "version_check", 228 | ] 229 | 230 | [[package]] 231 | name = "getrandom" 232 | version = "0.2.15" 233 | source = "registry+https://github.com/rust-lang/crates.io-index" 234 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 235 | dependencies = [ 236 | "cfg-if", 237 | "libc", 238 | "wasi", 239 | ] 240 | 241 | [[package]] 242 | name = "glob" 243 | version = "0.3.2" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" 246 | 247 | [[package]] 248 | name = "hex" 249 | version = "0.4.3" 250 | source = "registry+https://github.com/rust-lang/crates.io-index" 251 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 252 | 253 | [[package]] 254 | name = "home" 255 | version = "0.5.11" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" 258 | dependencies = [ 259 | "windows-sys 0.59.0", 260 | ] 261 | 262 | [[package]] 263 | name = "itertools" 264 | version = "0.12.1" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" 267 | dependencies = [ 268 | "either", 269 | ] 270 | 271 | [[package]] 272 | name = "jni" 273 | version = "0.19.0" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "c6df18c2e3db7e453d3c6ac5b3e9d5182664d28788126d39b91f2d1e22b017ec" 276 | dependencies = [ 277 | "cesu8", 278 | "combine", 279 | "jni-sys", 280 | "log", 281 | "thiserror", 282 | "walkdir", 283 | ] 284 | 285 | [[package]] 286 | name = "jni-sys" 287 | version = "0.3.0" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" 290 | 291 | [[package]] 292 | name = "jobserver" 293 | version = "0.1.32" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" 296 | dependencies = [ 297 | "libc", 298 | ] 299 | 300 | [[package]] 301 | name = "lazy_static" 302 | version = "1.5.0" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 305 | 306 | [[package]] 307 | name = "lazycell" 308 | version = "1.3.0" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" 311 | 312 | [[package]] 313 | name = "libc" 314 | version = "0.2.169" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" 317 | 318 | [[package]] 319 | name = "libloading" 320 | version = "0.8.6" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" 323 | dependencies = [ 324 | "cfg-if", 325 | "windows-targets", 326 | ] 327 | 328 | [[package]] 329 | name = "linux-raw-sys" 330 | version = "0.4.15" 331 | source = "registry+https://github.com/rust-lang/crates.io-index" 332 | checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" 333 | 334 | [[package]] 335 | name = "log" 336 | version = "0.4.25" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" 339 | 340 | [[package]] 341 | name = "memchr" 342 | version = "2.7.4" 343 | source = "registry+https://github.com/rust-lang/crates.io-index" 344 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 345 | 346 | [[package]] 347 | name = "minimal-lexical" 348 | version = "0.2.1" 349 | source = "registry+https://github.com/rust-lang/crates.io-index" 350 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 351 | 352 | [[package]] 353 | name = "nom" 354 | version = "7.1.3" 355 | source = "registry+https://github.com/rust-lang/crates.io-index" 356 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 357 | dependencies = [ 358 | "memchr", 359 | "minimal-lexical", 360 | ] 361 | 362 | [[package]] 363 | name = "num-bigint" 364 | version = "0.4.6" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" 367 | dependencies = [ 368 | "num-integer", 369 | "num-traits", 370 | ] 371 | 372 | [[package]] 373 | name = "num-integer" 374 | version = "0.1.46" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" 377 | dependencies = [ 378 | "num-traits", 379 | ] 380 | 381 | [[package]] 382 | name = "num-traits" 383 | version = "0.2.19" 384 | source = "registry+https://github.com/rust-lang/crates.io-index" 385 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 386 | dependencies = [ 387 | "autocfg", 388 | ] 389 | 390 | [[package]] 391 | name = "once_cell" 392 | version = "1.20.3" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" 395 | 396 | [[package]] 397 | name = "openssl-probe" 398 | version = "0.1.6" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" 401 | 402 | [[package]] 403 | name = "paste" 404 | version = "1.0.15" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" 407 | 408 | [[package]] 409 | name = "pkcs1" 410 | version = "0.7.5" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" 413 | dependencies = [ 414 | "der", 415 | "pkcs8", 416 | "spki", 417 | ] 418 | 419 | [[package]] 420 | name = "pkcs8" 421 | version = "0.10.2" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" 424 | dependencies = [ 425 | "der", 426 | "spki", 427 | ] 428 | 429 | [[package]] 430 | name = "prettyplease" 431 | version = "0.2.29" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" 434 | dependencies = [ 435 | "proc-macro2", 436 | "syn", 437 | ] 438 | 439 | [[package]] 440 | name = "proc-macro2" 441 | version = "1.0.93" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" 444 | dependencies = [ 445 | "unicode-ident", 446 | ] 447 | 448 | [[package]] 449 | name = "quote" 450 | version = "1.0.38" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" 453 | dependencies = [ 454 | "proc-macro2", 455 | ] 456 | 457 | [[package]] 458 | name = "regex" 459 | version = "1.11.1" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 462 | dependencies = [ 463 | "aho-corasick", 464 | "memchr", 465 | "regex-automata", 466 | "regex-syntax", 467 | ] 468 | 469 | [[package]] 470 | name = "regex-automata" 471 | version = "0.4.9" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 474 | dependencies = [ 475 | "aho-corasick", 476 | "memchr", 477 | "regex-syntax", 478 | ] 479 | 480 | [[package]] 481 | name = "regex-syntax" 482 | version = "0.8.5" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 485 | 486 | [[package]] 487 | name = "ring" 488 | version = "0.17.8" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" 491 | dependencies = [ 492 | "cc", 493 | "cfg-if", 494 | "getrandom", 495 | "libc", 496 | "spin", 497 | "untrusted", 498 | "windows-sys 0.52.0", 499 | ] 500 | 501 | [[package]] 502 | name = "rustc-hash" 503 | version = "1.1.0" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 506 | 507 | [[package]] 508 | name = "rustix" 509 | version = "0.38.44" 510 | source = "registry+https://github.com/rust-lang/crates.io-index" 511 | checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" 512 | dependencies = [ 513 | "bitflags", 514 | "errno", 515 | "libc", 516 | "linux-raw-sys", 517 | "windows-sys 0.59.0", 518 | ] 519 | 520 | [[package]] 521 | name = "rustls" 522 | version = "0.23.23" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" 525 | dependencies = [ 526 | "aws-lc-rs", 527 | "log", 528 | "once_cell", 529 | "rustls-pki-types", 530 | "rustls-webpki", 531 | "subtle", 532 | "zeroize", 533 | ] 534 | 535 | [[package]] 536 | name = "rustls-cng" 537 | version = "0.6.0" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "ae77a1973becea1dce2a2187b69a66252a7c1a2345c191bc3501e9497f03d7ea" 540 | dependencies = [ 541 | "rustls", 542 | "windows-sys 0.59.0", 543 | ] 544 | 545 | [[package]] 546 | name = "rustls-native-certs" 547 | version = "0.7.3" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" 550 | dependencies = [ 551 | "openssl-probe", 552 | "rustls-pemfile", 553 | "rustls-pki-types", 554 | "schannel", 555 | "security-framework", 556 | ] 557 | 558 | [[package]] 559 | name = "rustls-pemfile" 560 | version = "2.2.0" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" 563 | dependencies = [ 564 | "rustls-pki-types", 565 | ] 566 | 567 | [[package]] 568 | name = "rustls-pki-types" 569 | version = "1.11.0" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" 572 | 573 | [[package]] 574 | name = "rustls-platform-verifier" 575 | version = "0.4.0" 576 | source = "registry+https://github.com/rust-lang/crates.io-index" 577 | checksum = "a4c7dc240fec5517e6c4eab3310438636cfe6391dfc345ba013109909a90d136" 578 | dependencies = [ 579 | "core-foundation", 580 | "core-foundation-sys", 581 | "jni", 582 | "log", 583 | "once_cell", 584 | "rustls", 585 | "rustls-native-certs", 586 | "rustls-platform-verifier-android", 587 | "rustls-webpki", 588 | "security-framework", 589 | "security-framework-sys", 590 | "webpki-root-certs", 591 | "windows-sys 0.52.0", 592 | ] 593 | 594 | [[package]] 595 | name = "rustls-platform-verifier-android" 596 | version = "0.1.1" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" 599 | 600 | [[package]] 601 | name = "rustls-symcrypt" 602 | version = "0.2.1" 603 | dependencies = [ 604 | "der", 605 | "pkcs1", 606 | "pkcs8", 607 | "rustls", 608 | "rustls-pki-types", 609 | "sec1", 610 | "symcrypt", 611 | ] 612 | 613 | [[package]] 614 | name = "rustls-symcrypt-examples" 615 | version = "0.0.1" 616 | dependencies = [ 617 | "anyhow", 618 | "cmake", 619 | "hex", 620 | "once_cell", 621 | "rustls", 622 | "rustls-cng", 623 | "rustls-pemfile", 624 | "rustls-pki-types", 625 | "rustls-platform-verifier", 626 | "rustls-symcrypt", 627 | "rustls-webpki", 628 | "webpki-roots", 629 | ] 630 | 631 | [[package]] 632 | name = "rustls-webpki" 633 | version = "0.102.8" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" 636 | dependencies = [ 637 | "aws-lc-rs", 638 | "ring", 639 | "rustls-pki-types", 640 | "untrusted", 641 | ] 642 | 643 | [[package]] 644 | name = "same-file" 645 | version = "1.0.6" 646 | source = "registry+https://github.com/rust-lang/crates.io-index" 647 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 648 | dependencies = [ 649 | "winapi-util", 650 | ] 651 | 652 | [[package]] 653 | name = "schannel" 654 | version = "0.1.27" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" 657 | dependencies = [ 658 | "windows-sys 0.59.0", 659 | ] 660 | 661 | [[package]] 662 | name = "sec1" 663 | version = "0.7.3" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" 666 | dependencies = [ 667 | "base16ct", 668 | "der", 669 | "generic-array", 670 | "zeroize", 671 | ] 672 | 673 | [[package]] 674 | name = "security-framework" 675 | version = "2.11.1" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 678 | dependencies = [ 679 | "bitflags", 680 | "core-foundation", 681 | "core-foundation-sys", 682 | "libc", 683 | "num-bigint", 684 | "security-framework-sys", 685 | ] 686 | 687 | [[package]] 688 | name = "security-framework-sys" 689 | version = "2.14.0" 690 | source = "registry+https://github.com/rust-lang/crates.io-index" 691 | checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" 692 | dependencies = [ 693 | "core-foundation-sys", 694 | "libc", 695 | ] 696 | 697 | [[package]] 698 | name = "shlex" 699 | version = "1.3.0" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 702 | 703 | [[package]] 704 | name = "spin" 705 | version = "0.9.8" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 708 | 709 | [[package]] 710 | name = "spki" 711 | version = "0.7.3" 712 | source = "registry+https://github.com/rust-lang/crates.io-index" 713 | checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" 714 | dependencies = [ 715 | "base64ct", 716 | "der", 717 | ] 718 | 719 | [[package]] 720 | name = "subtle" 721 | version = "2.6.1" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 724 | 725 | [[package]] 726 | name = "symcrypt" 727 | version = "0.5.1" 728 | dependencies = [ 729 | "lazy_static", 730 | "libc", 731 | "symcrypt-sys", 732 | ] 733 | 734 | [[package]] 735 | name = "symcrypt-sys" 736 | version = "0.4.0" 737 | dependencies = [ 738 | "cc", 739 | "libc", 740 | ] 741 | 742 | [[package]] 743 | name = "syn" 744 | version = "2.0.98" 745 | source = "registry+https://github.com/rust-lang/crates.io-index" 746 | checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" 747 | dependencies = [ 748 | "proc-macro2", 749 | "quote", 750 | "unicode-ident", 751 | ] 752 | 753 | [[package]] 754 | name = "thiserror" 755 | version = "1.0.69" 756 | source = "registry+https://github.com/rust-lang/crates.io-index" 757 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 758 | dependencies = [ 759 | "thiserror-impl", 760 | ] 761 | 762 | [[package]] 763 | name = "thiserror-impl" 764 | version = "1.0.69" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 767 | dependencies = [ 768 | "proc-macro2", 769 | "quote", 770 | "syn", 771 | ] 772 | 773 | [[package]] 774 | name = "typenum" 775 | version = "1.17.0" 776 | source = "registry+https://github.com/rust-lang/crates.io-index" 777 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 778 | 779 | [[package]] 780 | name = "unicode-ident" 781 | version = "1.0.16" 782 | source = "registry+https://github.com/rust-lang/crates.io-index" 783 | checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" 784 | 785 | [[package]] 786 | name = "untrusted" 787 | version = "0.9.0" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 790 | 791 | [[package]] 792 | name = "version_check" 793 | version = "0.9.5" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 796 | 797 | [[package]] 798 | name = "walkdir" 799 | version = "2.5.0" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 802 | dependencies = [ 803 | "same-file", 804 | "winapi-util", 805 | ] 806 | 807 | [[package]] 808 | name = "wasi" 809 | version = "0.11.0+wasi-snapshot-preview1" 810 | source = "registry+https://github.com/rust-lang/crates.io-index" 811 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 812 | 813 | [[package]] 814 | name = "webpki-root-certs" 815 | version = "0.26.8" 816 | source = "registry+https://github.com/rust-lang/crates.io-index" 817 | checksum = "09aed61f5e8d2c18344b3faa33a4c837855fe56642757754775548fee21386c4" 818 | dependencies = [ 819 | "rustls-pki-types", 820 | ] 821 | 822 | [[package]] 823 | name = "webpki-roots" 824 | version = "0.26.8" 825 | source = "registry+https://github.com/rust-lang/crates.io-index" 826 | checksum = "2210b291f7ea53617fbafcc4939f10914214ec15aace5ba62293a668f322c5c9" 827 | dependencies = [ 828 | "rustls-pki-types", 829 | ] 830 | 831 | [[package]] 832 | name = "which" 833 | version = "4.4.2" 834 | source = "registry+https://github.com/rust-lang/crates.io-index" 835 | checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" 836 | dependencies = [ 837 | "either", 838 | "home", 839 | "once_cell", 840 | "rustix", 841 | ] 842 | 843 | [[package]] 844 | name = "winapi-util" 845 | version = "0.1.9" 846 | source = "registry+https://github.com/rust-lang/crates.io-index" 847 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 848 | dependencies = [ 849 | "windows-sys 0.59.0", 850 | ] 851 | 852 | [[package]] 853 | name = "windows-sys" 854 | version = "0.52.0" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 857 | dependencies = [ 858 | "windows-targets", 859 | ] 860 | 861 | [[package]] 862 | name = "windows-sys" 863 | version = "0.59.0" 864 | source = "registry+https://github.com/rust-lang/crates.io-index" 865 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 866 | dependencies = [ 867 | "windows-targets", 868 | ] 869 | 870 | [[package]] 871 | name = "windows-targets" 872 | version = "0.52.6" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 875 | dependencies = [ 876 | "windows_aarch64_gnullvm", 877 | "windows_aarch64_msvc", 878 | "windows_i686_gnu", 879 | "windows_i686_gnullvm", 880 | "windows_i686_msvc", 881 | "windows_x86_64_gnu", 882 | "windows_x86_64_gnullvm", 883 | "windows_x86_64_msvc", 884 | ] 885 | 886 | [[package]] 887 | name = "windows_aarch64_gnullvm" 888 | version = "0.52.6" 889 | source = "registry+https://github.com/rust-lang/crates.io-index" 890 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 891 | 892 | [[package]] 893 | name = "windows_aarch64_msvc" 894 | version = "0.52.6" 895 | source = "registry+https://github.com/rust-lang/crates.io-index" 896 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 897 | 898 | [[package]] 899 | name = "windows_i686_gnu" 900 | version = "0.52.6" 901 | source = "registry+https://github.com/rust-lang/crates.io-index" 902 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 903 | 904 | [[package]] 905 | name = "windows_i686_gnullvm" 906 | version = "0.52.6" 907 | source = "registry+https://github.com/rust-lang/crates.io-index" 908 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 909 | 910 | [[package]] 911 | name = "windows_i686_msvc" 912 | version = "0.52.6" 913 | source = "registry+https://github.com/rust-lang/crates.io-index" 914 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 915 | 916 | [[package]] 917 | name = "windows_x86_64_gnu" 918 | version = "0.52.6" 919 | source = "registry+https://github.com/rust-lang/crates.io-index" 920 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 921 | 922 | [[package]] 923 | name = "windows_x86_64_gnullvm" 924 | version = "0.52.6" 925 | source = "registry+https://github.com/rust-lang/crates.io-index" 926 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 927 | 928 | [[package]] 929 | name = "windows_x86_64_msvc" 930 | version = "0.52.6" 931 | source = "registry+https://github.com/rust-lang/crates.io-index" 932 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 933 | 934 | [[package]] 935 | name = "zeroize" 936 | version = "1.8.1" 937 | source = "registry+https://github.com/rust-lang/crates.io-index" 938 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 939 | -------------------------------------------------------------------------------- /examples/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustls-symcrypt-examples" 3 | version = "0.0.1" 4 | authors = ["Microsoft"] 5 | license = "Apache-2.0 OR ISC OR MIT" 6 | description = "rustls-symcrypt example code" 7 | edition.workspace = true 8 | rust-version.workspace = true 9 | publish = false 10 | 11 | [dependencies] 12 | rustls-symcrypt = {path = "../rustls-symcrypt"} 13 | rustls = { version = "0.23.0", default-features = false, features = ["tls12", "std", "custom-provider"]} 14 | webpki-roots = "0.26" 15 | webpki = {package = "rustls-webpki", default-features = false, version = "0.102", features = ["alloc"]} 16 | rustls-pemfile = "2.1.0" 17 | rustls-pki-types = "1.10" 18 | cmake = "0.1" 19 | once_cell = "1.8.0" 20 | hex = "0.4" 21 | 22 | rustls-platform-verifier = "0.5.0" 23 | 24 | rustls-cng = "0.6.0" 25 | anyhow = "1.0" 26 | 27 | 28 | [[bin]] 29 | name = "sample_internet_client" 30 | path = "bin/sample_internet_client.rs" 31 | 32 | [[bin]] 33 | name = "sample_server" 34 | path = "bin/sample_server.rs" 35 | 36 | [[bin]] 37 | name = "sample_local_client" 38 | path = "bin/sample_local_client.rs" 39 | 40 | [[bin]] 41 | name = "sample_internet_client_platform" 42 | path = "bin/sample_internet_client_platform.rs" 43 | 44 | [[bin]] 45 | name = "sample_windows_client_server" 46 | path = "bin/sample_windows_client_server.rs" 47 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Rust TLS Examples 2 | 3 | This README provides an overview and usage instructions for three Rust programs that demonstrate the use of `rustls` and `rustls_symcrypt`. These examples include an internet client, an internet client with platform certificate verification, a local client, and a local server. 4 | 5 | 6 | `sample_local_client` and `sample_server` both require certs and/or keys for proper functionality. These sample certs have been provided in the `bin/certs` path. 7 | 8 | ## 1. Sample Internet Client (`sample_internet_client`) 9 | 10 | This example establishes a TLS connection to `rust-lang.org` using `default_symcrypt_provider()`. This program does not take in any certs or keys and instead relies on `webpki_roots::TLS_SERVER_ROOTS`. 11 | 12 | ### Usage 13 | `cargo run --bin sample_internet_client` 14 | 15 | ## 2. Sample Internet Client with Platform Verifier (`sample_internet_client_platform`) 16 | 17 | This example establishes a TLS connection to `rust-lang.org` using `default_symcrypt_provider()`. and [rustls-platform-verifier](https://github.com/rustls/rustls-platform-verifier). This program does not take in any certs or keys and instead relies the roots that are managed by your platform. 18 | 19 | ### Usage 20 | `cargo run --bin sample_internet_client_platform` 21 | 22 | ## 3. Sample Local Client (`sample_local_client`) 23 | 24 | This example shows how to connect to a local server on `localhost:4444` using `custom_symcrypt_provider()`. This program requires the usage of a sample Root CA which has been provided and is named `RootCA.pem`. To get the client to connect, you can either start the `sample_server` in a separate terminal window, or you can start a simple openssl server in a separate terminal window. 25 | 26 | To spin up a simple openssl server please use the following from the `bin/certs` folder: 27 | 28 | `openssl s_server -accept 4444 -cert localhost.crt -key localhost.key -debug` 29 | 30 | ### Usage 31 | `cargo run --bin sample_local_client`. 32 | 33 | ## 4. Sample Server (`sample_server`) 34 | 35 | This example shows how to set up a server application that listens on `localhost:4444` for incoming TLS connections. This program requires the usage of an end cert and it's key which have been provided and is named `localhost.crt` and `localhost.pem` respectively. To get a client to connect to your server, you can either start `sample_local_client` in a separate terminal window, or you can start a simple openssl client in a separate terminal window. 36 | 37 | To spin up a simple openssl client please use the following from the `bin/certs` folder: 38 | 39 | `openssl s_client -connect localhost:4444 -CAfile RootCA.pem` 40 | 41 | ### Usage 42 | `cargo run --bin sample_server`. 43 | 44 | -------------------------------------------------------------------------------- /examples/bin/certs/RootCA.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDLzCCAhegAwIBAgIUTANmSWnfVeu3082UBM4SZV8e9jAwDQYJKoZIhvcNAQEL 3 | BQAwJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD0V4YW1wbGUtUm9vdC1DQTAeFw0y 4 | NDAyMjExOTM4MjNaFw0yNjEyMTExOTM4MjNaMCcxCzAJBgNVBAYTAlVTMRgwFgYD 5 | VQQDDA9FeGFtcGxlLVJvb3QtQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK 6 | AoIBAQCnH5dA166tnxD0tJWiHphWsFK/oc73hHmYwOOxRa+HaR/0QBy7753jFw/o 7 | JueMVfTrNtHoaLUaVwX9W3ATnbpv27JvTNSmoJx2h+XRn7Z7p+yokRj9YjnEu+Qo 8 | PvusGkYj8fh3y5SE6JfEhQa/tOD9Ex2fl+6KS2Va6L5uY8IfbU9/h/SX7AIMKCwr 9 | FlGvPmiavlj/WFSgao6qY4kB4dNjUHmgKMk/hFtPfKu5oEZVBt383cUxWI/CkwnK 10 | WnaFC89vV3UBPTjrixIwNcUpMsksl93ky3SBSeIQSUL19hvs1yJnB8koOpdSmT4O 11 | wqIai3d3YQyRzuRo6rvsbQ/Kag9LAgMBAAGjUzBRMB0GA1UdDgQWBBS1AhH/ACQN 12 | g49LL4LnWAYXsyE4zDAfBgNVHSMEGDAWgBS1AhH/ACQNg49LL4LnWAYXsyE4zDAP 13 | BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCboiZMR8uQuDBMNGw+ 14 | br2N06tHJWXdc80dNoFyHFbgvxYD/thCIG+eOotkFZbM7wds/dGhwSCLmPX6PIVh 15 | //ZVZdD/Dk8B0J0jfbeuRM/kAQAvX+6aZCP3EQ2A6zYmquWSWHLazyWD3aY3sGPX 16 | 4vfggNVTCnROS1Rk0i0IzU08z4fDZDfOjDW06x6pUek+7sUR1oVMFMd2Q6CxWCp9 17 | 15J63r0JKmZDG8SOG1iIGTPj4GopDuNnQKA8qJGWc6WwPvJTinCPWMBJ2s73KOwY 18 | hKJQGZ22zULoReHXFY+Dt05vN9iig95OpHHNw+f3jPgwKlPBHhInpjEPNvKFJZ6K 19 | F2kb 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /examples/bin/certs/RootCA.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCnH5dA166tnxD0 3 | tJWiHphWsFK/oc73hHmYwOOxRa+HaR/0QBy7753jFw/oJueMVfTrNtHoaLUaVwX9 4 | W3ATnbpv27JvTNSmoJx2h+XRn7Z7p+yokRj9YjnEu+QoPvusGkYj8fh3y5SE6JfE 5 | hQa/tOD9Ex2fl+6KS2Va6L5uY8IfbU9/h/SX7AIMKCwrFlGvPmiavlj/WFSgao6q 6 | Y4kB4dNjUHmgKMk/hFtPfKu5oEZVBt383cUxWI/CkwnKWnaFC89vV3UBPTjrixIw 7 | NcUpMsksl93ky3SBSeIQSUL19hvs1yJnB8koOpdSmT4OwqIai3d3YQyRzuRo6rvs 8 | bQ/Kag9LAgMBAAECggEBAJ0FyY9bFvx6X+wLYCwaoveQY6850MQu7DDhyw1cdDe+ 9 | Rg+vzU+nK6mamY9+PkBU4vG9aCv9dWtyKGaL6xoDMJC400ZP4d1NOrUDqqLydPpq 10 | JKmc6uXnzG9UOmK2CrEBXrWXO+USmlDmWPKEKnsk79/YfhTdI3s8q9Zmp8YAZPww 11 | qwev2QjHc09EfUXr4F3GxwPA/SRDgtjo0kjdGb66B3CmtS1YrABsTyefFFHEsO5w 12 | B39mW/q8q45N+6zAz5gqOpZFESTtr3IAf+eHecyGfSOtuIh2ssRiNF4Cj8Y40eM0 13 | kFcWVX7YNbveGNouJU8/lktBQmzOKhN4mswnROC8oEECgYEA25KLBivO6jK7NzDh 14 | 5LkI6CiENJzMs5Hdyv+cMTwBDJ9VftvTgHz5jc/Fu+0/UfxkdZRebFuE36DgfoDu 15 | 4g0RBVeDkfRuAHkrw9/VwksUHUSSG4WU6xzUZZqR/x3oGqG8VWmD+iCt0AaNjfqx 16 | BWBNqEY3MT+9ejys+W9D8URfkY0CgYEAwtl8YSpsd5N7NNv3iMKPaoR8l2AMWRB/ 17 | Nt8qUBt5DAjNbDtx8s/xz0/T9MAijEv94XFR93rCVWMDW2Gl3bxaoenMNNPOowui 18 | HeNApEstXYmWFsJljW/yNPvbU+e3shXUPf3aXVwKA/qkqbD+Bjz3CvsvUipjAWXe 19 | gtficCSVcjcCgYAKNj6RAuiUq9dZMcTPxmtLoNbFO6WplFckYc752ziRRbfMNp0X 20 | lLhmiAtCOj5/qaVicowRrg/39pt6RrTVfpYUEYXk++FB1GDcs0RVzPgahF3nOcc7 21 | SBP4xb+UheeNlYgU0Nt6fpqW2jcrK0WgYmI6OUnH2JcPYFMLJsmaJvvq4QKBgHy5 22 | uOt9u4bjigdxEsehOyqE+jfvzJeqfrRCMBStMVPpwo0YlD1IrNH2mIfgAX1rG22X 23 | G0/ebc04nyp8nC8O5bklLolWV7x4suKM2JESakyoyMFy2Iyr7w/JdEEGX8kIPh8c 24 | gw4l32dipsrUuBaIKd8GoOjopw17Bu8cgB8m298LAoGBAKMZrL7GZMM4uInZeCT9 25 | YYiF3g2WhsGnjB5kbIhy20CPCUtOEMviaziMnvlgQhwUz6ez4dmx05p6zYEMv5EX 26 | 57vCTTs31uokmGy+YX61J9DVt2eYjd2qcVKLPBEkgTLBJ8LZK3+yK1HBsVWRs2Cd 27 | 2bkIRFDri2sd0pFkAGNyGGRu 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /examples/bin/certs/RootCA.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDLzCCAhegAwIBAgIUTANmSWnfVeu3082UBM4SZV8e9jAwDQYJKoZIhvcNAQEL 3 | BQAwJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD0V4YW1wbGUtUm9vdC1DQTAeFw0y 4 | NDAyMjExOTM4MjNaFw0yNjEyMTExOTM4MjNaMCcxCzAJBgNVBAYTAlVTMRgwFgYD 5 | VQQDDA9FeGFtcGxlLVJvb3QtQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK 6 | AoIBAQCnH5dA166tnxD0tJWiHphWsFK/oc73hHmYwOOxRa+HaR/0QBy7753jFw/o 7 | JueMVfTrNtHoaLUaVwX9W3ATnbpv27JvTNSmoJx2h+XRn7Z7p+yokRj9YjnEu+Qo 8 | PvusGkYj8fh3y5SE6JfEhQa/tOD9Ex2fl+6KS2Va6L5uY8IfbU9/h/SX7AIMKCwr 9 | FlGvPmiavlj/WFSgao6qY4kB4dNjUHmgKMk/hFtPfKu5oEZVBt383cUxWI/CkwnK 10 | WnaFC89vV3UBPTjrixIwNcUpMsksl93ky3SBSeIQSUL19hvs1yJnB8koOpdSmT4O 11 | wqIai3d3YQyRzuRo6rvsbQ/Kag9LAgMBAAGjUzBRMB0GA1UdDgQWBBS1AhH/ACQN 12 | g49LL4LnWAYXsyE4zDAfBgNVHSMEGDAWgBS1AhH/ACQNg49LL4LnWAYXsyE4zDAP 13 | BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCboiZMR8uQuDBMNGw+ 14 | br2N06tHJWXdc80dNoFyHFbgvxYD/thCIG+eOotkFZbM7wds/dGhwSCLmPX6PIVh 15 | //ZVZdD/Dk8B0J0jfbeuRM/kAQAvX+6aZCP3EQ2A6zYmquWSWHLazyWD3aY3sGPX 16 | 4vfggNVTCnROS1Rk0i0IzU08z4fDZDfOjDW06x6pUek+7sUR1oVMFMd2Q6CxWCp9 17 | 15J63r0JKmZDG8SOG1iIGTPj4GopDuNnQKA8qJGWc6WwPvJTinCPWMBJ2s73KOwY 18 | hKJQGZ22zULoReHXFY+Dt05vN9iig95OpHHNw+f3jPgwKlPBHhInpjEPNvKFJZ6K 19 | F2kb 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /examples/bin/certs/localhost.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDczCCAlugAwIBAgIUVuQWWEJldt7G1ixpk1UwDM2xMw4wDQYJKoZIhvcNAQEL 3 | BQAwJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD0V4YW1wbGUtUm9vdC1DQTAeFw0y 4 | NDAyMjExOTM5NDNaFw0yNjEyMTExOTM5NDNaMG0xCzAJBgNVBAYTAlVTMRIwEAYD 5 | VQQIDAlZb3VyU3RhdGUxETAPBgNVBAcMCFlvdXJDaXR5MR0wGwYDVQQKDBRFeGFt 6 | cGxlLUNlcnRpZmljYXRlczEYMBYGA1UEAwwPbG9jYWxob3N0LmxvY2FsMIIBIjAN 7 | BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArwnqNmj1kU7mF2CHdk2J+aB+IXu6 8 | RettCfthQvER1AnehHUnLaMk5ufzuPoc7/7ZW03TdptKBgB4isT8RXwM9usxlvoI 9 | qFYldGD0rlQFfaVfdzFmGlwgloyS8tnQGzr5w6oUDKDClNPAxGIa5RzWiz5nT/zR 10 | 7F8+eiELdvrlLB8fTA5N7A3xCI9yywdYDgLlFsREUd6DpLrfdoCGah8xsFl8aU7E 11 | uL/QuROywz/aShGm9WG4cO7biac59/yMB9+ccchtI+PgScdzz/WsfiqaMZNL+XQ5 12 | iFg9KDJdvB5ZyXtEfQB0f6aX9bbDZ4qlMElCH1tYOyGy7tuy9Cl0AGEfRwIDAQAB 13 | o1EwTzAfBgNVHSMEGDAWgBS1AhH/ACQNg49LL4LnWAYXsyE4zDAJBgNVHRMEAjAA 14 | MAsGA1UdDwQEAwIE8DAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQEL 15 | BQADggEBAFWcr8VYNr+SUQUcAdgrZ5uBjdS9v0oE3co6zuClWhv9P7pKT+QYEe4/ 16 | 0DzNWz4BisT4VHUqyV4QAXqIY6qT0cmlHMwFBTcMNTagZ6sAGr0ULcLNAkwTjUkm 17 | xTQecGPE06ynZGxThY8Bd6jQGfrn8Bkpzv5DdM47SQxG5e2k6IeQ1GW7wE6BpjUv 18 | eRGUDAI7UD1R3T3zDDtWFOCz8jFodqC5E6Cy/Np7p2J+5Rhbxl+0ytMtegQY/Mfv 19 | Uvltab20jnZiozF4y27l18iG4SP7h61SJoF02Jc4VVZMAnoqm25ddR2VaoSXjW6i 20 | Tq/gk2MYM/5Ym1iSykAevHovwMMwzB0= 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /examples/bin/certs/localhost.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCvCeo2aPWRTuYX 3 | YId2TYn5oH4he7pF620J+2FC8RHUCd6EdSctoyTm5/O4+hzv/tlbTdN2m0oGAHiK 4 | xPxFfAz26zGW+gioViV0YPSuVAV9pV93MWYaXCCWjJLy2dAbOvnDqhQMoMKU08DE 5 | YhrlHNaLPmdP/NHsXz56IQt2+uUsHx9MDk3sDfEIj3LLB1gOAuUWxERR3oOkut92 6 | gIZqHzGwWXxpTsS4v9C5E7LDP9pKEab1Ybhw7tuJpzn3/IwH35xxyG0j4+BJx3PP 7 | 9ax+Kpoxk0v5dDmIWD0oMl28HlnJe0R9AHR/ppf1tsNniqUwSUIfW1g7IbLu27L0 8 | KXQAYR9HAgMBAAECggEADbm2TuvuDaXlLwIXvTQZVKS8Hz4HfkIWu8ileM1Ue48G 9 | jtZs6ww28ZBQsTmTmVIfkyRIJ63HoS7aRO9rZLt0fMw2iEM0+JZAu556sUzPXWnh 10 | UYRjIEAHIicFwttHkUsPmMM2bUMR3v+3xu52c27Od/69tSz6/RD+4i7DKmJEJDBv 11 | O0SJVVxdoam15TTTW1CqnQeomg+C5dNhFAg1uYu+REiddFRjLRJR4xG4FPsqdA1z 12 | 1rzGYZlU7I5bW3B8gNpwkRBx4f03NxyqDu3aPK8CBi/NMJumTpq0E1kSW7vODesZ 13 | /5fb8mWUVKQJwFmPWUNQs/Go5D0YOVYlu0YgVU5VqQKBgQDnuekUU10iadZKurkU 14 | 4NumGaHULVc0v9Iy6nhIQ8Y5uF0OMOWYIJ9kG1pAgzJ10bHJvhUvqi4fwxdRvo48 15 | qM8AT4RiXTa0RtZv3sMHcAn1ZHVzYGhwArN+UmMBuPvbj1HJHHYjUETBeHweHRVa 16 | /B/l1IjwoU4lYvTgNSEtmmuMfQKBgQDBX9f7SGdUhYyxjsAcnMmyYDQKiKa+XRlZ 17 | Is7q673ahs/b6oIBuvj5Bo4ZfpuX0ZkbbPgXemaKw1wnTOqaBv6bEAhqf8jb5HXp 18 | 6LvTzg2UQL8HdBdZOd/K1ozFbkdqqeO+sUw4dsp2lYU/zE+LhS1/MyRCWB7zMp1K 19 | 7d2aXXUaEwKBgQDBhZWOEADb2J/KUR54vUEy+n0YAbWuq/QT6ZUCZPeLBNlSHKvh 20 | 3HzA0ccR0X+2vaVI4qI26F0U0Y0MC6QmLKSTkdTxgP9Kl05GpzchYwQuF/Ouo3kU 21 | 8myMtqlQqvhLaOnYlxhibYq+OK0PSSKolZ7eBh1HOK9Wscnn5PcMasYe0QKBgB0j 22 | JvUrFL7MnMWIX/Qvv8iL7GuF6bIXbyFaOFl3ihTqaVmWvV4rYSaM0U6QIDvBDlPu 23 | mHdZLyhLhZA6a8MnuKd+w/XgKVDQ3N+Q/PROQQeMtfwWhwofyVPT/kQleMder/1k 24 | 07pSU/GIWBqj23yHZbKb7yO8CXXVs5O9wb1nxaRXAoGANjF/qdvNyEZ6RVu1Y1B9 25 | hT1zV9qUDfb+hdSEaUplijucrIISWNeOXyhLEPcQE/i+2gHYQShHBxN720n0HGlc 26 | L7sT9H4ewsJcLrH00VGZb1LZx497Aiwa86KEiLolNpPEOQ38gSLXxcq4wmLsS2bB 27 | XY84nhn6D6sPQDYhnKR7s0k= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /examples/bin/certs/localhost.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDczCCAlugAwIBAgIUVuQWWEJldt7G1ixpk1UwDM2xMw4wDQYJKoZIhvcNAQEL 3 | BQAwJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD0V4YW1wbGUtUm9vdC1DQTAeFw0y 4 | NDAyMjExOTM5NDNaFw0yNjEyMTExOTM5NDNaMG0xCzAJBgNVBAYTAlVTMRIwEAYD 5 | VQQIDAlZb3VyU3RhdGUxETAPBgNVBAcMCFlvdXJDaXR5MR0wGwYDVQQKDBRFeGFt 6 | cGxlLUNlcnRpZmljYXRlczEYMBYGA1UEAwwPbG9jYWxob3N0LmxvY2FsMIIBIjAN 7 | BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArwnqNmj1kU7mF2CHdk2J+aB+IXu6 8 | RettCfthQvER1AnehHUnLaMk5ufzuPoc7/7ZW03TdptKBgB4isT8RXwM9usxlvoI 9 | qFYldGD0rlQFfaVfdzFmGlwgloyS8tnQGzr5w6oUDKDClNPAxGIa5RzWiz5nT/zR 10 | 7F8+eiELdvrlLB8fTA5N7A3xCI9yywdYDgLlFsREUd6DpLrfdoCGah8xsFl8aU7E 11 | uL/QuROywz/aShGm9WG4cO7biac59/yMB9+ccchtI+PgScdzz/WsfiqaMZNL+XQ5 12 | iFg9KDJdvB5ZyXtEfQB0f6aX9bbDZ4qlMElCH1tYOyGy7tuy9Cl0AGEfRwIDAQAB 13 | o1EwTzAfBgNVHSMEGDAWgBS1AhH/ACQNg49LL4LnWAYXsyE4zDAJBgNVHRMEAjAA 14 | MAsGA1UdDwQEAwIE8DAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQEL 15 | BQADggEBAFWcr8VYNr+SUQUcAdgrZ5uBjdS9v0oE3co6zuClWhv9P7pKT+QYEe4/ 16 | 0DzNWz4BisT4VHUqyV4QAXqIY6qT0cmlHMwFBTcMNTagZ6sAGr0ULcLNAkwTjUkm 17 | xTQecGPE06ynZGxThY8Bd6jQGfrn8Bkpzv5DdM47SQxG5e2k6IeQ1GW7wE6BpjUv 18 | eRGUDAI7UD1R3T3zDDtWFOCz8jFodqC5E6Cy/Np7p2J+5Rhbxl+0ytMtegQY/Mfv 19 | Uvltab20jnZiozF4y27l18iG4SP7h61SJoF02Jc4VVZMAnoqm25ddR2VaoSXjW6i 20 | Tq/gk2MYM/5Ym1iSykAevHovwMMwzB0= 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /examples/bin/certs/rustls-client.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/rustls-symcrypt/1dcd61673fabfaa35b462997da2f07acd8f6a729/examples/bin/certs/rustls-client.pfx -------------------------------------------------------------------------------- /examples/bin/certs/rustls-server.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/rustls-symcrypt/1dcd61673fabfaa35b462997da2f07acd8f6a729/examples/bin/certs/rustls-server.pfx -------------------------------------------------------------------------------- /examples/bin/sample_internet_client.rs: -------------------------------------------------------------------------------- 1 | use rustls_symcrypt::default_symcrypt_provider; 2 | 3 | use std::io::{stdout, Read, Write}; 4 | use std::net::TcpStream; 5 | use std::sync::Arc; 6 | 7 | /// Usage 8 | /// This program provides a simple internet client using rustls-symcrypt to connect to rust-lang.org 9 | /// It uses the provided RootCA.pem to set up the client config. 10 | /// To run this program you can use the following command in your terminal: 11 | /// cargo run --bin sample_internet_client 12 | fn main() { 13 | let root_store = rustls::RootCertStore { 14 | roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(), 15 | }; 16 | 17 | let config = rustls::ClientConfig::builder_with_provider(Arc::new(default_symcrypt_provider())) 18 | .with_safe_default_protocol_versions() 19 | .unwrap() 20 | .with_root_certificates(root_store) 21 | .with_no_client_auth(); 22 | 23 | let server_name = "www.rust-lang.org".try_into().unwrap(); 24 | let mut sock = TcpStream::connect("www.rust-lang.org:443").unwrap(); 25 | let mut conn = rustls::ClientConnection::new(Arc::new(config), server_name).unwrap(); 26 | let mut tls = rustls::Stream::new(&mut conn, &mut sock); 27 | 28 | println!("Connecting to rust-lang.org, using the default_symcrypt_provider()"); 29 | 30 | tls.write_all( 31 | concat!( 32 | "GET / HTTP/1.1\r\n", 33 | "Host: www.rust-lang.org\r\n", 34 | "Connection: close\r\n", 35 | "Accept-Encoding: identity\r\n", 36 | "\r\n" 37 | ) 38 | .as_bytes(), 39 | ) 40 | .unwrap(); 41 | 42 | let ciphersuite = tls.conn.negotiated_cipher_suite().unwrap(); 43 | writeln!( 44 | &mut std::io::stderr(), 45 | "Current ciphersuite: {:?}", 46 | ciphersuite.suite() 47 | ) 48 | .unwrap(); 49 | 50 | let mut plaintext = Vec::new(); 51 | tls.read_to_end(&mut plaintext).unwrap(); 52 | stdout().write_all(&plaintext).unwrap(); 53 | 54 | println!("Connection established"); 55 | } 56 | -------------------------------------------------------------------------------- /examples/bin/sample_internet_client_platform.rs: -------------------------------------------------------------------------------- 1 | use rustls_platform_verifier::Verifier; 2 | use rustls_symcrypt::default_symcrypt_provider; 3 | 4 | use std::io::{stdout, Read, Write}; 5 | use std::net::TcpStream; 6 | use std::sync::Arc; 7 | 8 | /// Usage 9 | /// This program provides a simple internet client using rustls-symcrypt to connect to rust-lang.org 10 | /// It uses rustls-platform-verifier to use your machines cert validation. 11 | /// To run this program you can use the following command in your terminal: 12 | /// cargo run --bin sample_internet_client_platform 13 | fn main() { 14 | let config = rustls::ClientConfig::builder_with_provider(Arc::new(default_symcrypt_provider())) 15 | .with_safe_default_protocol_versions() 16 | .unwrap() 17 | .dangerous() 18 | .with_custom_certificate_verifier(Arc::new( 19 | Verifier::new().with_provider(Arc::new(default_symcrypt_provider())), 20 | )) 21 | .with_no_client_auth(); 22 | 23 | let server_name = "www.rust-lang.org".try_into().unwrap(); 24 | let mut sock = TcpStream::connect("www.rust-lang.org:443").unwrap(); 25 | let mut conn = rustls::ClientConnection::new(Arc::new(config), server_name).unwrap(); 26 | let mut tls = rustls::Stream::new(&mut conn, &mut sock); 27 | 28 | println!("Connecting to rust-lang.org, using the default_symcrypt_provider()"); 29 | 30 | tls.write_all( 31 | concat!( 32 | "GET / HTTP/1.1\r\n", 33 | "Host: www.rust-lang.org\r\n", 34 | "Connection: close\r\n", 35 | "Accept-Encoding: identity\r\n", 36 | "\r\n" 37 | ) 38 | .as_bytes(), 39 | ) 40 | .unwrap(); 41 | 42 | let ciphersuite = tls.conn.negotiated_cipher_suite().unwrap(); 43 | writeln!( 44 | &mut std::io::stderr(), 45 | "Current ciphersuite: {:?}", 46 | ciphersuite.suite() 47 | ) 48 | .unwrap(); 49 | 50 | let mut plaintext = Vec::new(); 51 | tls.read_to_end(&mut plaintext).unwrap(); 52 | stdout().write_all(&plaintext).unwrap(); 53 | 54 | println!("Connection established"); 55 | } 56 | -------------------------------------------------------------------------------- /examples/bin/sample_local_client.rs: -------------------------------------------------------------------------------- 1 | use rustls_symcrypt::{ 2 | custom_symcrypt_provider, SECP256R1, TLS13_AES_128_GCM_SHA256, TLS13_AES_256_GCM_SHA384, 3 | }; 4 | 5 | use std::fs::File; 6 | use std::io::BufReader; 7 | use std::io::{stdout, Read, Write}; 8 | use std::net::TcpStream; 9 | use std::path::PathBuf; 10 | use std::sync::Arc; 11 | 12 | static TEST_CERT_PATH: once_cell::sync::Lazy = once_cell::sync::Lazy::new(|| { 13 | let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 14 | path.push("bin"); 15 | path.push("certs"); 16 | path 17 | }); 18 | 19 | // Note this assumes that you are connecting to a localhost server that has the included localhost.crt and localhost.key 20 | // set up for the server configuration, to run this in openssl, you can use the following command: 21 | 22 | // openssl s_server -accept 4444 -cert localhost.crt -key localhost.key -debug 23 | 24 | /// Usage 25 | /// This program provides a simple localhost client using rustls-symcrypt. 26 | /// It uses the provided RootCA.pem to set up the client config. 27 | /// To run this program you can use the following command in your terminal: 28 | /// cargo run --bin sample_local_client 29 | fn main() { 30 | let cert_path = TEST_CERT_PATH 31 | .join("RootCA.pem") 32 | .into_os_string() 33 | .into_string() 34 | .unwrap(); 35 | let certs = rustls_pemfile::certs(&mut BufReader::new(&mut File::open(cert_path).unwrap())) 36 | .collect::, _>>() 37 | .unwrap(); 38 | 39 | let mut root_store = rustls::RootCertStore::empty(); 40 | root_store.add_parsable_certificates(certs); 41 | 42 | let cipher_suites = vec![TLS13_AES_128_GCM_SHA256, TLS13_AES_256_GCM_SHA384]; 43 | let kx_group = vec![SECP256R1]; 44 | 45 | let config = rustls::ClientConfig::builder_with_provider(Arc::new(custom_symcrypt_provider( 46 | Some(cipher_suites), 47 | Some(kx_group), 48 | ))) 49 | .with_safe_default_protocol_versions() 50 | .unwrap() 51 | .with_root_certificates(root_store) 52 | .with_no_client_auth(); 53 | 54 | let server_name = "localhost".try_into().unwrap(); 55 | let mut sock = TcpStream::connect("localhost:4444").unwrap(); 56 | 57 | let mut conn = rustls::ClientConnection::new(Arc::new(config), server_name).unwrap(); 58 | let mut tls = rustls::Stream::new(&mut conn, &mut sock); 59 | 60 | println!("Connecting to localhost using a the custom_symcrypt_provider."); 61 | println!("To get your own localhost server you can use the following openssl command:"); 62 | println!("openssl s_server -accept 4444 -cert localhost.crt -key localhost.key -debug"); 63 | 64 | tls.write_all( 65 | concat!( 66 | "GET / HTTP/1.1\r\n", 67 | "Host: localhost\r\n", 68 | "Connection: close\r\n", 69 | "Accept-Encoding: identity\r\n", 70 | "\r\n" 71 | ) 72 | .as_bytes(), 73 | ) 74 | .unwrap(); 75 | 76 | let ciphersuite = tls.conn.negotiated_cipher_suite().unwrap(); 77 | writeln!( 78 | &mut std::io::stderr(), 79 | "Current ciphersuite: {:?}", 80 | ciphersuite.suite() 81 | ) 82 | .unwrap(); 83 | 84 | let mut plaintext = Vec::new(); 85 | tls.read_to_end(&mut plaintext).unwrap(); 86 | stdout().write_all(&plaintext).unwrap(); 87 | 88 | println!("Connection established"); 89 | } 90 | -------------------------------------------------------------------------------- /examples/bin/sample_server.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | use std::sync::Arc; 3 | 4 | use rustls::server::Acceptor; 5 | use rustls::ServerConfig; 6 | 7 | use rustls_symcrypt::{ 8 | custom_symcrypt_provider, SECP256R1, TLS13_AES_128_GCM_SHA256, TLS13_AES_256_GCM_SHA384, 9 | }; 10 | 11 | use std::fs::File; 12 | use std::io::BufReader; 13 | use std::path::PathBuf; 14 | 15 | static TEST_CERT_PATH: once_cell::sync::Lazy = once_cell::sync::Lazy::new(|| { 16 | let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 17 | path.push("bin"); 18 | path.push("certs"); 19 | path 20 | }); 21 | 22 | /// Usage 23 | /// This program provides a simple localhost server using rustls-symcrypt. 24 | /// It uses the provided localhost.pem and localhost.key to set up the server config. 25 | /// To run this program you can use the following command in your terminal: 26 | /// cargo run --bin sample_server 27 | fn main() { 28 | // This code is similar to running the following command in openssl. 29 | // openssl s_server -accept 4444 -cert localhost.crt -key localhost.key -debug 30 | 31 | // Get file path and parse cert and key 32 | let cert_path = TEST_CERT_PATH 33 | .join("localhost.pem") 34 | .into_os_string() 35 | .into_string() 36 | .unwrap(); 37 | let key_path = TEST_CERT_PATH 38 | .join("localhost.key") 39 | .into_os_string() 40 | .into_string() 41 | .unwrap(); 42 | 43 | let certs = rustls_pemfile::certs(&mut BufReader::new(&mut File::open(cert_path).unwrap())) 44 | .collect::, _>>() 45 | .unwrap(); 46 | 47 | let private_key = 48 | rustls_pemfile::private_key(&mut BufReader::new(&mut File::open(key_path).unwrap())) 49 | .unwrap() 50 | .unwrap(); 51 | 52 | // Set what cipher suites are going to be accepted by the server. 53 | let cipher_suites = vec![TLS13_AES_128_GCM_SHA256, TLS13_AES_256_GCM_SHA384]; 54 | let kx_group = vec![SECP256R1]; 55 | 56 | // Set up custom server config. 57 | let server_config = ServerConfig::builder_with_provider(Arc::new(custom_symcrypt_provider( 58 | Some(cipher_suites), 59 | Some(kx_group), 60 | ))) 61 | .with_safe_default_protocol_versions() 62 | .unwrap() 63 | .with_no_client_auth() 64 | .with_single_cert(certs, private_key) 65 | .unwrap(); 66 | 67 | let server_config = Arc::new(server_config); 68 | let listener = std::net::TcpListener::bind(format!("[::]:{}", 4444)).unwrap(); 69 | 70 | println!("Staring server with custom_symcrypt_provider()"); 71 | 72 | // Handle incomming connections 73 | for stream in listener.incoming() { 74 | let mut stream = stream.unwrap(); 75 | let mut acceptor = Acceptor::default(); 76 | 77 | let accepted = loop { 78 | acceptor.read_tls(&mut stream).unwrap(); 79 | if let Some(accepted) = acceptor.accept().unwrap() { 80 | break accepted; 81 | } 82 | }; 83 | 84 | match accepted.into_connection(server_config.clone()) { 85 | Ok(mut conn) => { 86 | println!("Connection established"); 87 | 88 | let msg = concat!( 89 | "HTTP/1.1 200 OK\r\n", 90 | "Connection: Closed\r\n", 91 | "Content-Type: text/html\r\n", 92 | "\r\n", 93 | "

Hello World! From your own RUSTLS server

\r\n" 94 | ) 95 | .as_bytes(); 96 | 97 | // Note: do not use `unwrap()` on IO in real programs! 98 | conn.writer().write_all(msg).unwrap(); 99 | conn.write_tls(&mut stream).unwrap(); 100 | conn.complete_io(&mut stream).unwrap(); 101 | 102 | conn.send_close_notify(); 103 | conn.write_tls(&mut stream).unwrap(); 104 | conn.complete_io(&mut stream).unwrap(); 105 | } 106 | Err(_) => { 107 | // error here 108 | } 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /examples/bin/sample_windows_client_server.rs: -------------------------------------------------------------------------------- 1 | // This program provides a simple client-server application using rustls-symcrypt and rustls-cng. 2 | // It uses rustls-platform-verifier to utilize your machine's certificate validation for TLS communication. 3 | // Both the client and server retrieve certificates from the "CurrentUser" "my" store. 4 | // Please install rustls-client.pfx and rustls-server.pfx into "CurrentUser" "my" store if you want to test it. The password is "changeit" 5 | // Usage: cargo run --bin sample_client_server 6 | // The reference for this program is https://github.com/rustls/rustls-cng/blob/dev/tests/test_client_server.rs 7 | 8 | // if not windows, disable dead code and unused imports 9 | #![cfg_attr(not(windows), allow(dead_code, unused_imports))] 10 | 11 | mod client { 12 | 13 | use std::{ 14 | io::{Read, Write}, 15 | net::{Shutdown, TcpStream}, 16 | sync::Arc, 17 | }; 18 | 19 | use anyhow::Result; 20 | use rustls::{ 21 | client::ResolvesClientCert, sign::CertifiedKey, ClientConfig, ClientConnection, 22 | SignatureScheme, Stream, 23 | }; 24 | use rustls_cng::{ 25 | signer::CngSigningKey, 26 | store::{CertStore, CertStoreType}, 27 | }; 28 | use rustls_pki_types::CertificateDer; 29 | use rustls_platform_verifier::BuilderVerifierExt; 30 | use rustls_symcrypt::default_symcrypt_provider; 31 | 32 | #[derive(Debug)] 33 | pub struct ClientCertResolver(String); 34 | 35 | // This is the SHA256 thumbprint of the certificate in the CurrentUser My store 36 | fn get_chain(hex_thumbprint: &str) -> Result<(Vec>, CngSigningKey)> { 37 | let store = CertStore::open(CertStoreType::CurrentUser, "My")?; 38 | let thumbprint = hex::decode(hex_thumbprint)?; 39 | 40 | let contexts = store.find_by_sha256(thumbprint)?; 41 | let context = contexts 42 | .first() 43 | .ok_or_else(|| anyhow::Error::msg("Client: No client cert"))?; 44 | let key = context.acquire_key(true)?; 45 | let signing_key = CngSigningKey::new(key)?; 46 | let chain = context 47 | .as_chain_der()? 48 | .into_iter() 49 | .map(Into::into) 50 | .collect(); 51 | Ok((chain, signing_key)) 52 | } 53 | 54 | impl ResolvesClientCert for ClientCertResolver { 55 | fn resolve( 56 | &self, 57 | _acceptable_issuers: &[&[u8]], 58 | sigschemes: &[SignatureScheme], 59 | ) -> Option> { 60 | println!("Client sig schemes: {:#?}", sigschemes); 61 | let (chain, signing_key) = get_chain(&self.0).ok()?; 62 | for scheme in signing_key.supported_schemes() { 63 | if sigschemes.contains(scheme) { 64 | return Some(Arc::new(CertifiedKey { 65 | cert: chain, 66 | key: Arc::new(signing_key), 67 | ocsp: None, 68 | })); 69 | } 70 | } 71 | None 72 | } 73 | 74 | fn has_certs(&self) -> bool { 75 | true 76 | } 77 | } 78 | 79 | pub fn run_client(port: u16) -> Result<()> { 80 | let hex_thumbprint = "9e04c0715bb5f9d246c82a5dd841778f00137337f15c7e5f3444ddad975c789c"; 81 | 82 | let client_config = 83 | ClientConfig::builder_with_provider(Arc::new(default_symcrypt_provider())) 84 | .with_safe_default_protocol_versions()? 85 | .with_platform_verifier() 86 | .with_client_cert_resolver(Arc::new(ClientCertResolver( 87 | hex_thumbprint.to_string(), 88 | ))); 89 | 90 | let mut connection = 91 | ClientConnection::new(Arc::new(client_config), "rustls-server".try_into()?)?; 92 | println!("start a new connection"); 93 | let mut client = TcpStream::connect(format!("localhost:{}", port))?; 94 | let mut tls_stream = Stream::new(&mut connection, &mut client); 95 | println!("write to tls stream"); 96 | tls_stream.write_all(b"ping")?; 97 | println!("shut down socket"); 98 | tls_stream.sock.shutdown(Shutdown::Write)?; 99 | 100 | let mut buf = [0u8; 4]; 101 | tls_stream.read_exact(&mut buf)?; 102 | assert_eq!(&buf, b"pong"); 103 | 104 | tls_stream.sock.shutdown(Shutdown::Read)?; 105 | 106 | Ok(()) 107 | } 108 | } 109 | 110 | mod server { 111 | use std::{ 112 | io::{Read, Write}, 113 | net::{Shutdown, TcpListener, TcpStream}, 114 | sync::{mpsc::Sender, Arc}, 115 | }; 116 | 117 | use anyhow::Result; 118 | use rustls::{ 119 | server::{ClientHello, ResolvesServerCert, ServerConfig, ServerConnection}, 120 | sign::CertifiedKey, 121 | Stream, 122 | }; 123 | use rustls_cng::{ 124 | signer::CngSigningKey, 125 | store::{CertStore, CertStoreType}, 126 | }; 127 | use rustls_pki_types::CertificateDer; 128 | 129 | #[derive(Debug)] 130 | pub struct CachedServerCertResolver(Arc); 131 | 132 | impl CachedServerCertResolver { 133 | pub fn new(chain: Vec>, signing_key: CngSigningKey) -> Self { 134 | let certified_key = CertifiedKey { 135 | cert: chain, 136 | key: Arc::new(signing_key), 137 | ocsp: None, 138 | }; 139 | CachedServerCertResolver(Arc::new(certified_key)) 140 | } 141 | } 142 | 143 | impl ResolvesServerCert for CachedServerCertResolver { 144 | fn resolve(&self, _client_hello: ClientHello) -> Option> { 145 | Some(Arc::clone(&self.0)) 146 | } 147 | } 148 | 149 | // This is the SHA256 thumbprint of the certificate in the CurrentUser My store 150 | fn get_chain(hex_thumbprint: &str) -> Result<(Vec>, CngSigningKey)> { 151 | let store = CertStore::open(CertStoreType::CurrentUser, "My")?; 152 | let thumbprint = hex::decode(hex_thumbprint)?; 153 | 154 | let context = store 155 | .find_by_sha256(thumbprint) 156 | .unwrap() 157 | .into_iter() 158 | .next() 159 | .ok_or_else(|| anyhow::Error::msg("Server: No client certificate found"))?; 160 | let key = context.acquire_key(true)?; 161 | let signing_key = CngSigningKey::new(key)?; 162 | 163 | let chain = context 164 | .as_chain_der()? 165 | .into_iter() 166 | .map(Into::into) 167 | .collect(); 168 | Ok((chain, signing_key)) 169 | } 170 | 171 | fn handle_connection(mut stream: TcpStream, config: Arc) -> Result<()> { 172 | let mut connection = ServerConnection::new(config)?; 173 | let mut tls_stream = Stream::new(&mut connection, &mut stream); 174 | 175 | let mut buf = [0u8; 4]; 176 | tls_stream.read_exact(&mut buf)?; 177 | assert_eq!(&buf, b"ping"); 178 | tls_stream.sock.shutdown(Shutdown::Read)?; 179 | tls_stream.write_all(b"pong")?; 180 | tls_stream.sock.shutdown(Shutdown::Write)?; 181 | 182 | Ok(()) 183 | } 184 | 185 | pub fn run_server(sender: Sender) -> Result<()> { 186 | let hex_thumbprint = "04d6b562162923555c39d64cc6e5220fa0a7cf5a5a7720e6f3a8a34f976dc9e8"; 187 | let (chain, signing_key) = get_chain(hex_thumbprint)?; 188 | // Build the server configuration 189 | let server_config = ServerConfig::builder() 190 | .with_no_client_auth() 191 | .with_cert_resolver(Arc::new(CachedServerCertResolver::new(chain, signing_key))); 192 | 193 | let server = TcpListener::bind("127.0.0.1:0")?; 194 | let _ = sender.send(server.local_addr()?.port()); 195 | let stream = server.incoming().next().unwrap()?; 196 | let config = Arc::new(server_config); 197 | handle_connection(stream, config)?; 198 | 199 | Ok(()) 200 | } 201 | } 202 | 203 | // This program relies on rustls-cng which is only applicable for Windows Devices 204 | #[cfg(windows)] 205 | fn main() -> anyhow::Result<()> { 206 | let (tx, rx) = std::sync::mpsc::channel(); 207 | std::thread::spawn(move || { 208 | if let Err(e) = server::run_server(tx) { 209 | eprintln!("Server error: {:?}", e); 210 | } 211 | }); 212 | println!("Server is running"); 213 | if let Ok(port) = rx.recv() { 214 | if let Err(e) = client::run_client(port) { 215 | eprintln!("Client error: {:?}", e); 216 | } 217 | } 218 | 219 | Ok(()) 220 | } 221 | 222 | // Dummy main to pass build and test pipelines on non Windows devices. 223 | #[cfg(not(windows))] 224 | fn main() { 225 | println!("This program is only applicable for Windows Devices"); 226 | } 227 | -------------------------------------------------------------------------------- /rustls-symcrypt/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anstream" 16 | version = "0.6.18" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 19 | dependencies = [ 20 | "anstyle", 21 | "anstyle-parse", 22 | "anstyle-query", 23 | "anstyle-wincon", 24 | "colorchoice", 25 | "is_terminal_polyfill", 26 | "utf8parse", 27 | ] 28 | 29 | [[package]] 30 | name = "anstyle" 31 | version = "1.0.10" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 34 | 35 | [[package]] 36 | name = "anstyle-parse" 37 | version = "0.2.6" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 40 | dependencies = [ 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-query" 46 | version = "1.1.2" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 49 | dependencies = [ 50 | "windows-sys 0.59.0", 51 | ] 52 | 53 | [[package]] 54 | name = "anstyle-wincon" 55 | version = "3.0.6" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 58 | dependencies = [ 59 | "anstyle", 60 | "windows-sys 0.59.0", 61 | ] 62 | 63 | [[package]] 64 | name = "base16ct" 65 | version = "0.2.0" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" 68 | 69 | [[package]] 70 | name = "base64ct" 71 | version = "1.6.0" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" 74 | 75 | [[package]] 76 | name = "cc" 77 | version = "1.2.12" 78 | source = "registry+https://github.com/rust-lang/crates.io-index" 79 | checksum = "755717a7de9ec452bf7f3f1a3099085deabd7f2962b861dae91ecd7a365903d2" 80 | dependencies = [ 81 | "shlex", 82 | ] 83 | 84 | [[package]] 85 | name = "cfg-if" 86 | version = "1.0.0" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 89 | 90 | [[package]] 91 | name = "colorchoice" 92 | version = "1.0.3" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 95 | 96 | [[package]] 97 | name = "const-oid" 98 | version = "0.9.6" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" 101 | 102 | [[package]] 103 | name = "der" 104 | version = "0.7.9" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" 107 | dependencies = [ 108 | "const-oid", 109 | "zeroize", 110 | ] 111 | 112 | [[package]] 113 | name = "env_filter" 114 | version = "0.1.2" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" 117 | dependencies = [ 118 | "log", 119 | "regex", 120 | ] 121 | 122 | [[package]] 123 | name = "env_logger" 124 | version = "0.11.5" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" 127 | dependencies = [ 128 | "anstream", 129 | "anstyle", 130 | "env_filter", 131 | "humantime", 132 | "log", 133 | ] 134 | 135 | [[package]] 136 | name = "generic-array" 137 | version = "0.14.7" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 140 | dependencies = [ 141 | "typenum", 142 | "version_check", 143 | ] 144 | 145 | [[package]] 146 | name = "getrandom" 147 | version = "0.2.15" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 150 | dependencies = [ 151 | "cfg-if", 152 | "libc", 153 | "wasi", 154 | ] 155 | 156 | [[package]] 157 | name = "humantime" 158 | version = "2.1.0" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 161 | 162 | [[package]] 163 | name = "is_terminal_polyfill" 164 | version = "1.70.1" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 167 | 168 | [[package]] 169 | name = "lazy_static" 170 | version = "1.5.0" 171 | source = "registry+https://github.com/rust-lang/crates.io-index" 172 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 173 | 174 | [[package]] 175 | name = "libc" 176 | version = "0.2.167" 177 | source = "registry+https://github.com/rust-lang/crates.io-index" 178 | checksum = "09d6582e104315a817dff97f75133544b2e094ee22447d2acf4a74e189ba06fc" 179 | 180 | [[package]] 181 | name = "log" 182 | version = "0.4.22" 183 | source = "registry+https://github.com/rust-lang/crates.io-index" 184 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 185 | 186 | [[package]] 187 | name = "memchr" 188 | version = "2.7.4" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 191 | 192 | [[package]] 193 | name = "once_cell" 194 | version = "1.20.2" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 197 | 198 | [[package]] 199 | name = "pkcs1" 200 | version = "0.7.5" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" 203 | dependencies = [ 204 | "der", 205 | "pkcs8", 206 | "spki", 207 | ] 208 | 209 | [[package]] 210 | name = "pkcs8" 211 | version = "0.10.2" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" 214 | dependencies = [ 215 | "der", 216 | "spki", 217 | ] 218 | 219 | [[package]] 220 | name = "regex" 221 | version = "1.11.1" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 224 | dependencies = [ 225 | "aho-corasick", 226 | "memchr", 227 | "regex-automata", 228 | "regex-syntax", 229 | ] 230 | 231 | [[package]] 232 | name = "regex-automata" 233 | version = "0.4.9" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 236 | dependencies = [ 237 | "aho-corasick", 238 | "memchr", 239 | "regex-syntax", 240 | ] 241 | 242 | [[package]] 243 | name = "regex-syntax" 244 | version = "0.8.5" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 247 | 248 | [[package]] 249 | name = "ring" 250 | version = "0.17.8" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" 253 | dependencies = [ 254 | "cc", 255 | "cfg-if", 256 | "getrandom", 257 | "libc", 258 | "spin", 259 | "untrusted", 260 | "windows-sys 0.52.0", 261 | ] 262 | 263 | [[package]] 264 | name = "rustls" 265 | version = "0.23.19" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "934b404430bb06b3fae2cba809eb45a1ab1aecd64491213d7c3301b88393f8d1" 268 | dependencies = [ 269 | "once_cell", 270 | "rustls-pki-types", 271 | "rustls-webpki", 272 | "subtle", 273 | "zeroize", 274 | ] 275 | 276 | [[package]] 277 | name = "rustls-pemfile" 278 | version = "2.2.0" 279 | source = "registry+https://github.com/rust-lang/crates.io-index" 280 | checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" 281 | dependencies = [ 282 | "rustls-pki-types", 283 | ] 284 | 285 | [[package]] 286 | name = "rustls-pki-types" 287 | version = "1.11.0" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" 290 | 291 | [[package]] 292 | name = "rustls-symcrypt" 293 | version = "0.2.1" 294 | dependencies = [ 295 | "der", 296 | "env_logger", 297 | "lazy_static", 298 | "log", 299 | "once_cell", 300 | "pkcs1", 301 | "pkcs8", 302 | "rustls", 303 | "rustls-pemfile", 304 | "rustls-pki-types", 305 | "sec1", 306 | "symcrypt", 307 | "webpki-roots", 308 | ] 309 | 310 | [[package]] 311 | name = "rustls-webpki" 312 | version = "0.102.8" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" 315 | dependencies = [ 316 | "ring", 317 | "rustls-pki-types", 318 | "untrusted", 319 | ] 320 | 321 | [[package]] 322 | name = "sec1" 323 | version = "0.7.3" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" 326 | dependencies = [ 327 | "base16ct", 328 | "der", 329 | "generic-array", 330 | "zeroize", 331 | ] 332 | 333 | [[package]] 334 | name = "shlex" 335 | version = "1.3.0" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 338 | 339 | [[package]] 340 | name = "spin" 341 | version = "0.9.8" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 344 | 345 | [[package]] 346 | name = "spki" 347 | version = "0.7.3" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" 350 | dependencies = [ 351 | "base64ct", 352 | "der", 353 | ] 354 | 355 | [[package]] 356 | name = "subtle" 357 | version = "2.6.1" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 360 | 361 | [[package]] 362 | name = "symcrypt" 363 | version = "0.5.1" 364 | dependencies = [ 365 | "lazy_static", 366 | "libc", 367 | "symcrypt-sys", 368 | ] 369 | 370 | [[package]] 371 | name = "symcrypt-sys" 372 | version = "0.4.0" 373 | dependencies = [ 374 | "cc", 375 | "libc", 376 | ] 377 | 378 | [[package]] 379 | name = "typenum" 380 | version = "1.17.0" 381 | source = "registry+https://github.com/rust-lang/crates.io-index" 382 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 383 | 384 | [[package]] 385 | name = "untrusted" 386 | version = "0.9.0" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 389 | 390 | [[package]] 391 | name = "utf8parse" 392 | version = "0.2.2" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 395 | 396 | [[package]] 397 | name = "version_check" 398 | version = "0.9.5" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 401 | 402 | [[package]] 403 | name = "wasi" 404 | version = "0.11.0+wasi-snapshot-preview1" 405 | source = "registry+https://github.com/rust-lang/crates.io-index" 406 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 407 | 408 | [[package]] 409 | name = "webpki-roots" 410 | version = "0.26.7" 411 | source = "registry+https://github.com/rust-lang/crates.io-index" 412 | checksum = "5d642ff16b7e79272ae451b7322067cdc17cadf68c23264be9d94a32319efe7e" 413 | dependencies = [ 414 | "rustls-pki-types", 415 | ] 416 | 417 | [[package]] 418 | name = "windows-sys" 419 | version = "0.52.0" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 422 | dependencies = [ 423 | "windows-targets", 424 | ] 425 | 426 | [[package]] 427 | name = "windows-sys" 428 | version = "0.59.0" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 431 | dependencies = [ 432 | "windows-targets", 433 | ] 434 | 435 | [[package]] 436 | name = "windows-targets" 437 | version = "0.52.6" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 440 | dependencies = [ 441 | "windows_aarch64_gnullvm", 442 | "windows_aarch64_msvc", 443 | "windows_i686_gnu", 444 | "windows_i686_gnullvm", 445 | "windows_i686_msvc", 446 | "windows_x86_64_gnu", 447 | "windows_x86_64_gnullvm", 448 | "windows_x86_64_msvc", 449 | ] 450 | 451 | [[package]] 452 | name = "windows_aarch64_gnullvm" 453 | version = "0.52.6" 454 | source = "registry+https://github.com/rust-lang/crates.io-index" 455 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 456 | 457 | [[package]] 458 | name = "windows_aarch64_msvc" 459 | version = "0.52.6" 460 | source = "registry+https://github.com/rust-lang/crates.io-index" 461 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 462 | 463 | [[package]] 464 | name = "windows_i686_gnu" 465 | version = "0.52.6" 466 | source = "registry+https://github.com/rust-lang/crates.io-index" 467 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 468 | 469 | [[package]] 470 | name = "windows_i686_gnullvm" 471 | version = "0.52.6" 472 | source = "registry+https://github.com/rust-lang/crates.io-index" 473 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 474 | 475 | [[package]] 476 | name = "windows_i686_msvc" 477 | version = "0.52.6" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 480 | 481 | [[package]] 482 | name = "windows_x86_64_gnu" 483 | version = "0.52.6" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 486 | 487 | [[package]] 488 | name = "windows_x86_64_gnullvm" 489 | version = "0.52.6" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 492 | 493 | [[package]] 494 | name = "windows_x86_64_msvc" 495 | version = "0.52.6" 496 | source = "registry+https://github.com/rust-lang/crates.io-index" 497 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 498 | 499 | [[package]] 500 | name = "zeroize" 501 | version = "1.8.1" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 504 | -------------------------------------------------------------------------------- /rustls-symcrypt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rustls-symcrypt" 3 | authors = ["Microsoft"] 4 | version = "0.2.1" 5 | license = "Apache-2.0 OR ISC OR MIT" 6 | description = "Implementation of rustls crypto provider model for SymCrypt" 7 | edition.workspace = true 8 | rust-version.workspace = true 9 | homepage = "https://github.com/microsoft/SymCrypt" 10 | repository = "https://github.com/microsoft/rustls-symcrypt" 11 | readme = "README.md" 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | rustls = { version = "0.23.0", default-features = false, features = ["tls12", "std", "custom-provider"]} 17 | symcrypt = {version = "0.5.1" } 18 | 19 | rustls-pki-types = "1.11.0" 20 | pkcs1 = { version = "0.7.5", features = ["alloc"], default-features = false } 21 | pkcs8 = "0.10" 22 | sec1 = "0.7.0" 23 | der = "0.7.9" 24 | 25 | [features] 26 | default = [] 27 | x25519 = [] 28 | chacha = [] 29 | 30 | [dev-dependencies] 31 | rustls-pemfile = "2" 32 | webpki-roots = "0.26" 33 | log = "0.4.20" 34 | env_logger = "0.11" 35 | once_cell = "1.8.0" 36 | lazy_static = "1.4.0" 37 | -------------------------------------------------------------------------------- /rustls-symcrypt/README.md: -------------------------------------------------------------------------------- 1 | # SymCrypt Provider for Rustls 2 | 3 | This crate provides integration for using `SymCrypt` cryptographic functionalities with the `rustls` crate, by implementing the required traits specified by `rustls`. 4 | 5 | ### Supported Configurations 6 | 7 | | Operating Environment | Architecture | Dynamic Linking | 8 | | --------------------- | ----------------- | ----------- | 9 | | Windows user mode | AMD64, ARM64 | ✅ | 10 | | Ubuntu (Tested via WSL) | AMD64, ARM64 | ✅ | 11 | | Azure Linux 3 | AMD64, ARM64 | ✅ | 12 | | Azure Linux 2 | AMD64, ARM64 | ❌ | 13 | 14 | 15 | ## Limitations 16 | 17 | - QUIC Protocol: Not supported. 18 | - Integration Efforts: Ongoing integration with rustls-cng and rustls-platform-verifier. 19 | 20 | ## Dependencies 21 | 22 | This crate depends on the [symcrypt](https://github.com/microsoft/rust-symcrypt) crate and requires you have the necessary `symcrypt` binaries for your architecture. 23 | Refer to the [rust-symcrypt Quick Start Guide](https://github.com/microsoft/rust-symcrypt/tree/main/rust-symcrypt#quick-start-guide) to download the required binaries. 24 | 25 | 26 | ## Supported Ciphers 27 | 28 | Supported cipher suites are listed below, ordered by preference. IE: The default configuration prioritizes `TLS13_AES_256_GCM_SHA384` over `TLS13_AES_128_GCM_SHA256`. 29 | 30 | ### TLS 1.3 31 | 32 | ```ignore 33 | TLS13_AES_256_GCM_SHA384 34 | TLS13_AES_128_GCM_SHA256 35 | TLS13_CHACHA20_POLY1305_SHA256 // Enabled with the `chacha` feature 36 | ``` 37 | 38 | **Note:** `TLS13_CHACHA20_POLY1305_SHA256` is disabled by default. Enable the `chacha` feature in your `Cargo.toml` to use this cipher suite. 39 | 40 | ### TLS 1.2 41 | 42 | ```ignore 43 | TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 44 | TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 45 | TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 // Enabled with the `chacha` feature 46 | TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 47 | TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 48 | TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 // Enabled with the `chacha` feature 49 | ``` 50 | 51 | **Note:** `TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256` and `TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256` are disabled by default. Enable the `chacha` feature in your `Cargo.toml` to use this cipher suite. 52 | 53 | 54 | ## Supported Key Exchanges 55 | 56 | Key exchanges are listed below, ordered by preference. IE: `SECP384R1` is preferred over `SECP256R1`. 57 | 58 | ```ignore 59 | SECP384R1 60 | SECP256R1 61 | SECP521R1 62 | X25519 // Enabled with the `x25519` feature 63 | ``` 64 | 65 | **Note:** `X25519` is disabled by default. To enable, add `x25519` feature in your `Cargo.toml`. 66 | 67 | ## Dependencies 68 | 69 | This crate depends on the [symcrypt](https://github.com/microsoft/rust-symcrypt) crate and requires you have the necessary `symcrypt` binaries for your architecture. 70 | Refer to the [rust-symcrypt Quick Start Guide](https://github.com/microsoft/rust-symcrypt/tree/main/rust-symcrypt#quick-start-guide) to download the required binaries. 71 | 72 | ## Usage 73 | 74 | Add `rustls-symcrypt` to your `Cargo.toml`: 75 | **Note:** If you wish to enable `x25519` or `chacha` you may add it as a feature at this time. 76 | 77 | ```toml 78 | [dependencies] 79 | # Disabling aws-lc as it slows down build times and is not needed. 80 | rustls = { version = "0.23.0", features = ["tls12", "std"], default-features = false } 81 | rustls_symcrypt = "0.2.1" 82 | # To enable the chacha feature: 83 | # rustls_symcrypt = {version = "0.2.1", features = ["chacha"]} 84 | ``` 85 | 86 | ### Default Configuration 87 | 88 | Use `default_symcrypt_provider()` for a `ClientConfig` that utilizes the default cipher suites and key exchange groups listed above: 89 | 90 | ```rust 91 | use rustls::{ClientConfig, RootCertStore}; 92 | use rustls_symcrypt::default_symcrypt_provider; 93 | use std::sync::Arc; 94 | use webpki_roots; 95 | 96 | let mut root_store = RootCertStore { 97 | roots: webpki_roots::TLS_SERVER_ROOTS.iter().cloned().collect(), 98 | }; 99 | let mut config = 100 | ClientConfig::builder_with_provider(Arc::new(default_symcrypt_provider())) 101 | .with_safe_default_protocol_versions() 102 | .unwrap() 103 | .with_root_certificates(root_store) 104 | .with_no_client_auth(); 105 | 106 | // Rest of the connection setup 107 | 108 | ``` 109 | 110 | ### Custom Configuration 111 | 112 | To modify or change the order of negotiated cipher suites for `ClientConfig`, use `custom_symcrypt_provider()`. 113 | 114 | ```rust 115 | use rustls::{ClientConfig, RootCertStore}; 116 | use rustls_symcrypt::{custom_symcrypt_provider, TLS13_AES_128_GCM_SHA256, SECP256R1}; 117 | use std::sync::Arc; 118 | use webpki_roots; 119 | 120 | let mut root_store = RootCertStore { 121 | roots: webpki_roots::TLS_SERVER_ROOTS.iter().cloned().collect(), 122 | }; 123 | 124 | // Set custom config of cipher suites that have been imported from rustls_symcrypt. 125 | let cipher_suites = vec![TLS13_AES_128_GCM_SHA256]; 126 | let kx_group = vec![SECP256R1]; 127 | 128 | let mut config = 129 | ClientConfig::builder_with_provider(Arc::new(custom_symcrypt_provider( 130 | Some(cipher_suites), Some(kx_group)))) 131 | .with_safe_default_protocol_versions() 132 | .unwrap() 133 | .with_root_certificates(root_store) 134 | .with_no_client_auth(); 135 | 136 | // Rest of the connection setup 137 | 138 | ``` 139 | -------------------------------------------------------------------------------- /rustls-symcrypt/src/cipher_suites.rs: -------------------------------------------------------------------------------- 1 | //! Cipher Suites supported for TLS 1.3 and TLS 1.2 2 | use crate::hash::{Sha256, Sha384}; 3 | use crate::hmac::{HmacSha256, HmacSha384}; 4 | use rustls::crypto::{CipherSuiteCommon, KeyExchangeAlgorithm}; 5 | use rustls::{ 6 | CipherSuite, SignatureScheme, SupportedCipherSuite, Tls12CipherSuite, Tls13CipherSuite, 7 | }; 8 | 9 | use crate::tls12::Tls12Gcm; 10 | use crate::tls13::Tls13Gcm; 11 | 12 | #[cfg(feature = "chacha")] 13 | use crate::tls12::Tls12ChaCha; 14 | #[cfg(feature = "chacha")] 15 | use crate::tls13::Tls13ChaCha; 16 | 17 | use rustls::crypto::tls12::PrfUsingHmac; 18 | use rustls::crypto::tls13::HkdfUsingHmac; 19 | 20 | /// Algo types for GCM TLS 1.3 and TLS 1.2 21 | pub enum AesGcm { 22 | Aes128Gcm, 23 | Aes256Gcm, 24 | } 25 | 26 | impl AesGcm { 27 | pub fn key_size(&self) -> usize { 28 | match self { 29 | AesGcm::Aes128Gcm => 16, 30 | AesGcm::Aes256Gcm => 32, 31 | } 32 | } 33 | } 34 | 35 | /// The TLS1.3 ciphersuite TLS_CHACHA20_POLY1305_SHA256 36 | #[cfg(feature = "chacha")] 37 | pub static TLS13_CHACHA20_POLY1305_SHA256: SupportedCipherSuite = 38 | SupportedCipherSuite::Tls13(&Tls13CipherSuite { 39 | common: CipherSuiteCommon { 40 | suite: CipherSuite::TLS13_CHACHA20_POLY1305_SHA256, 41 | hash_provider: &Sha256, 42 | confidentiality_limit: u64::MAX, 43 | }, 44 | hkdf_provider: &HkdfUsingHmac(&HmacSha256), 45 | aead_alg: &Tls13ChaCha, 46 | quic: None, 47 | }); 48 | 49 | /// The TLS1.3 ciphersuite TLS_AES_256_GCM_SHA384 50 | pub static TLS13_AES_256_GCM_SHA384: SupportedCipherSuite = 51 | SupportedCipherSuite::Tls13(&Tls13CipherSuite { 52 | common: CipherSuiteCommon { 53 | suite: CipherSuite::TLS13_AES_256_GCM_SHA384, 54 | hash_provider: &Sha384, 55 | confidentiality_limit: 1 << 23, 56 | }, 57 | hkdf_provider: &HkdfUsingHmac(&HmacSha384), 58 | aead_alg: &Tls13Gcm { 59 | algo_type: AesGcm::Aes256Gcm, 60 | }, 61 | quic: None, 62 | }); 63 | 64 | /// The TLS1.3 ciphersuite TLS_AES_128_GCM_SHA256 65 | pub static TLS13_AES_128_GCM_SHA256: SupportedCipherSuite = 66 | SupportedCipherSuite::Tls13(&Tls13CipherSuite { 67 | common: CipherSuiteCommon { 68 | suite: CipherSuite::TLS13_AES_128_GCM_SHA256, 69 | hash_provider: &Sha256, 70 | confidentiality_limit: 1 << 23, 71 | }, 72 | hkdf_provider: &HkdfUsingHmac(&HmacSha256), 73 | aead_alg: &Tls13Gcm { 74 | algo_type: AesGcm::Aes128Gcm, 75 | }, 76 | quic: None, 77 | }); 78 | 79 | /// TLS 1.2 80 | /// 81 | /// The TLS1.2 ciphersuite TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256. 82 | #[cfg(feature = "chacha")] 83 | pub static TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256: SupportedCipherSuite = 84 | SupportedCipherSuite::Tls12(&Tls12CipherSuite { 85 | common: CipherSuiteCommon { 86 | suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 87 | hash_provider: &Sha256, 88 | confidentiality_limit: u64::MAX, 89 | }, 90 | kx: KeyExchangeAlgorithm::ECDHE, 91 | sign: TLS12_ECDSA_SCHEMES, 92 | aead_alg: &Tls12ChaCha, 93 | prf_provider: &PrfUsingHmac(&HmacSha256), 94 | }); 95 | 96 | /// The TLS1.2 ciphersuite TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 97 | #[cfg(feature = "chacha")] 98 | pub static TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256: SupportedCipherSuite = 99 | SupportedCipherSuite::Tls12(&Tls12CipherSuite { 100 | common: CipherSuiteCommon { 101 | suite: CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 102 | hash_provider: &Sha256, 103 | confidentiality_limit: u64::MAX, 104 | }, 105 | kx: KeyExchangeAlgorithm::ECDHE, 106 | sign: TLS12_RSA_SCHEMES, 107 | aead_alg: &Tls12ChaCha, 108 | prf_provider: &PrfUsingHmac(&HmacSha256), 109 | }); 110 | 111 | /// The TLS1.2 ciphersuite TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 112 | pub static TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: SupportedCipherSuite = 113 | SupportedCipherSuite::Tls12(&Tls12CipherSuite { 114 | common: CipherSuiteCommon { 115 | suite: CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 116 | hash_provider: &Sha256, 117 | confidentiality_limit: 1 << 23, 118 | }, 119 | kx: KeyExchangeAlgorithm::ECDHE, 120 | sign: TLS12_RSA_SCHEMES, 121 | aead_alg: &Tls12Gcm { 122 | algo_type: AesGcm::Aes128Gcm, 123 | }, 124 | prf_provider: &PrfUsingHmac(&HmacSha256), 125 | }); 126 | 127 | /// The TLS1.2 ciphersuite TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 128 | pub static TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: SupportedCipherSuite = 129 | SupportedCipherSuite::Tls12(&Tls12CipherSuite { 130 | common: CipherSuiteCommon { 131 | suite: CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 132 | hash_provider: &Sha384, 133 | confidentiality_limit: 1 << 23, 134 | }, 135 | kx: KeyExchangeAlgorithm::ECDHE, 136 | sign: TLS12_RSA_SCHEMES, 137 | aead_alg: &Tls12Gcm { 138 | algo_type: AesGcm::Aes256Gcm, 139 | }, 140 | prf_provider: &PrfUsingHmac(&HmacSha384), 141 | }); 142 | 143 | /// The TLS1.2 ciphersuite TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 144 | pub static TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: SupportedCipherSuite = 145 | SupportedCipherSuite::Tls12(&Tls12CipherSuite { 146 | common: CipherSuiteCommon { 147 | suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 148 | hash_provider: &Sha256, 149 | confidentiality_limit: 1 << 23, 150 | }, 151 | kx: KeyExchangeAlgorithm::ECDHE, 152 | sign: TLS12_ECDSA_SCHEMES, 153 | aead_alg: &Tls12Gcm { 154 | algo_type: AesGcm::Aes128Gcm, 155 | }, 156 | prf_provider: &PrfUsingHmac(&HmacSha256), 157 | }); 158 | 159 | /// The TLS1.2 ciphersuite TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 160 | pub static TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: SupportedCipherSuite = 161 | SupportedCipherSuite::Tls12(&Tls12CipherSuite { 162 | common: CipherSuiteCommon { 163 | suite: CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, 164 | hash_provider: &Sha384, 165 | confidentiality_limit: 1 << 23, 166 | }, 167 | kx: KeyExchangeAlgorithm::ECDHE, 168 | sign: TLS12_ECDSA_SCHEMES, 169 | aead_alg: &Tls12Gcm { 170 | algo_type: AesGcm::Aes256Gcm, 171 | }, 172 | prf_provider: &PrfUsingHmac(&HmacSha384), 173 | }); 174 | 175 | static TLS12_ECDSA_SCHEMES: &[SignatureScheme] = &[ 176 | SignatureScheme::ECDSA_NISTP521_SHA512, 177 | SignatureScheme::ECDSA_NISTP384_SHA384, 178 | SignatureScheme::ECDSA_NISTP256_SHA256, 179 | ]; 180 | 181 | static TLS12_RSA_SCHEMES: &[SignatureScheme] = &[ 182 | SignatureScheme::RSA_PSS_SHA512, 183 | SignatureScheme::RSA_PSS_SHA384, 184 | SignatureScheme::RSA_PSS_SHA256, 185 | SignatureScheme::RSA_PKCS1_SHA512, 186 | SignatureScheme::RSA_PKCS1_SHA384, 187 | SignatureScheme::RSA_PKCS1_SHA256, 188 | ]; 189 | -------------------------------------------------------------------------------- /rustls-symcrypt/src/ecdh.rs: -------------------------------------------------------------------------------- 1 | //! EcDh functions. For further documentation please refer to rust_symcrypt::ecdh 2 | use rustls::crypto::{ActiveKeyExchange, SharedSecret, SupportedKxGroup}; 3 | use rustls::{Error, NamedGroup}; 4 | 5 | use symcrypt::ecc::{CurveType, EcKey, EcKeyUsage}; 6 | 7 | /// KxGroup is a struct that easily ties `rustls::NamedGroup` to the `symcrypt_sys::ecurve::CurveType`. 8 | /// 9 | /// This outlines the supported key exchange groups that are exposed by Rustls and implemented by SymCrypt. 10 | /// Currently the only supported key exchange groups are `X25519`, `NistP38`, and `Nist256`. 11 | #[derive(Debug)] 12 | pub struct KxGroup { 13 | name: NamedGroup, 14 | curve_type: CurveType, 15 | } 16 | 17 | /// KeyExchange is a struct that defines the state for EcDh operations 18 | /// 19 | /// the `state` field is tied to the [`EcDh`] struct from symcrypt_sys. 20 | /// 21 | /// the `name` and `curve_type` provide access to the `rustls::NamedGroup` 22 | /// and `symcrypt_sys::ecurve::CurveType` respectively 23 | /// 24 | /// `pub_key` is a `Vec` that represents the public key that is tied to the [`EcDh`] state. 25 | /// The `private_key` is not exposed. 26 | pub struct KeyExchange { 27 | state: EcKey, 28 | name: NamedGroup, 29 | curve_type: CurveType, 30 | pub_key: Vec, 31 | } 32 | 33 | /// All supported KeyExchange groups. 34 | /// ```ignore 35 | /// SECP384R1 36 | /// SECP256R1 37 | /// X25519 // Enabled with the `x25519` feature 38 | /// ``` 39 | pub const ALL_KX_GROUPS: &[&dyn SupportedKxGroup] = &[ 40 | SECP384R1, 41 | SECP256R1, 42 | #[cfg(feature = "x25519")] 43 | X25519, 44 | ]; 45 | 46 | // Since the type trait size cannot be determined at compile time, we must use trait objects, hence the `&dyn SupportedKxGroup` 47 | // annotation. Similarly, `KxGroup` must then also be taken as a reference. 48 | #[cfg(feature = "x25519")] 49 | pub const X25519: &dyn SupportedKxGroup = &KxGroup { 50 | name: NamedGroup::X25519, 51 | curve_type: CurveType::Curve25519, 52 | }; 53 | 54 | pub const SECP256R1: &dyn SupportedKxGroup = &KxGroup { 55 | name: NamedGroup::secp256r1, 56 | curve_type: CurveType::NistP256, 57 | }; 58 | 59 | pub const SECP384R1: &dyn SupportedKxGroup = &KxGroup { 60 | name: NamedGroup::secp384r1, 61 | curve_type: CurveType::NistP384, 62 | }; 63 | 64 | /// Impl for the trait SupportedKxGroup 65 | /// 66 | /// `start()` creates a new `symcrypt::ecc::EcKey` struct and subsequently, a new [`KeyExchange`] struct. 67 | /// 68 | /// `name()` returns the `NamedGroup` of the current [`KeyExchange`] group. 69 | impl SupportedKxGroup for KxGroup { 70 | fn start(&self) -> Result, Error> { 71 | let ec_key = EcKey::generate_key_pair(self.curve_type, EcKeyUsage::EcDh) 72 | .map_err(|e| Error::General(format!("SymCrypt key generation failed: {:?}", e)))?; 73 | let mut pub_key = ec_key 74 | .export_public_key() 75 | .map_err(|e| Error::General(format!("SymCrypt public key export failed: {:?}", e)))?; 76 | 77 | // Based on RFC 8446 https://www.rfc-editor.org/rfc/rfc8446#section-4.2.8.2. 78 | // struct { 79 | // uint8 legacy_form = 4; 80 | // opaque X[coordinate_length]; 81 | // opaque Y[coordinate_length]; 82 | // } UncompressedPointRepresentation; 83 | 84 | // Have to pre-append 0x04 to the first element of the vec since SymCrypt expects the caller to do so, 85 | // and Rustls expects the crypto library to append the 0x04. 86 | // X25519 does not have the legacy form requirement. 87 | match ec_key.get_curve_type() { 88 | CurveType::NistP256 | CurveType::NistP384 => { 89 | pub_key.insert(0, 0x04); // Prepend legacy byte to public key 90 | } 91 | 92 | CurveType::Curve25519 => { 93 | // Curve25519 curve does not require public key prepending 94 | } 95 | 96 | // Not possible to reach this branch since NistP521 struct is not implemented for key exchange 97 | CurveType::NistP521 => { 98 | return Err(Error::General( 99 | "NistP521 is not supported for key exchange".to_string(), 100 | )); 101 | } 102 | } 103 | 104 | Ok(Box::new(KeyExchange { 105 | state: ec_key, 106 | name: self.name, 107 | curve_type: self.curve_type, 108 | pub_key, 109 | })) 110 | } 111 | 112 | fn name(&self) -> NamedGroup { 113 | self.name 114 | } 115 | } 116 | 117 | /// Impl for the trait for `ActiveKeyExchange` in order to do stateful operations on the [`EcDh`] state. 118 | /// 119 | /// `complete()` takes in a `peer_pub_key` and creates a secondary [`EcDh`] struct in order to generate the secret agreement. 120 | /// 121 | /// Errors from SymCrypt will be propagated back to the user as a `rustls::Error::GeneralError`. 122 | /// 123 | /// `pub_key()` will return a ref to the `Vec` that holds the public key. 124 | /// 125 | /// `group()` will return the [`NamedGroup`] of the [`KeyExchange`] 126 | impl ActiveKeyExchange for KeyExchange { 127 | fn complete(self: Box, peer_pub_key: &[u8]) -> Result { 128 | let new_peer_pub_key = match self.curve_type { 129 | CurveType::NistP256 | CurveType::NistP384 => { 130 | // If curve type is NistP256 or NistP384 or NistP521 remove the first byte 131 | // Based on RFC 8446 https://www.rfc-editor.org/rfc/rfc8446#section-4.2.8.2. 132 | // struct { 133 | // uint8 legacy_form = 4; 134 | // opaque X[coordinate_length]; 135 | // opaque Y[coordinate_length]; 136 | // } UncompressedPointRepresentation; 137 | 138 | // Have to remove the legacy_form 0x04. Rustls does not do this for us, and SymCrypt 139 | // only expects the X and Y coordinates. 140 | 141 | if peer_pub_key.starts_with(&[0x04]) { 142 | &peer_pub_key[1..] // Return a slice starting from the second byte 143 | } else { 144 | return Err(Error::General("Invalid public key".to_string())); 145 | } 146 | } 147 | 148 | CurveType::Curve25519 => { 149 | // Do not remove first byte for Curve22519, since Curve25519 only has the x and y coordinates. 150 | peer_pub_key 151 | } 152 | 153 | CurveType::NistP521 => { 154 | return Err(Error::General( 155 | "NistP521 is not supported for key exchange".to_string(), 156 | )); 157 | } 158 | }; 159 | 160 | let peer_ecdh = 161 | match EcKey::set_public_key(self.curve_type, new_peer_pub_key, EcKeyUsage::EcDh) { 162 | Ok(peer_ecdh) => peer_ecdh, 163 | Err(symcrypt_error) => { 164 | let custom_error_message = format!( 165 | "SymCryptError: {}", 166 | symcrypt_error // Using general error to propagate the SymCrypt error back to the caller 167 | ); 168 | return Err(Error::General(custom_error_message)); 169 | } 170 | }; 171 | 172 | let secret_agreement = match EcKey::ecdh_secret_agreement(&self.state, peer_ecdh) { 173 | Ok(secret_agreement) => secret_agreement, 174 | Err(symcrypt_error) => { 175 | let custom_error_message = format!( 176 | "SymCryptError: {}", 177 | symcrypt_error // Using general error to propagate the SymCrypt error back to the caller 178 | ); 179 | return Err(Error::General(custom_error_message)); 180 | } 181 | }; 182 | Ok(SharedSecret::from(secret_agreement.as_slice())) 183 | } 184 | 185 | fn pub_key(&self) -> &[u8] { 186 | self.pub_key.as_ref() 187 | } 188 | 189 | fn group(&self) -> NamedGroup { 190 | self.name 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /rustls-symcrypt/src/hash.rs: -------------------------------------------------------------------------------- 1 | //! Hash functions. For further documentation please refer to rust_symcrypt::hash 2 | use rustls::crypto::hash::{Context, Hash, HashAlgorithm, Output}; 3 | use symcrypt::hash::{sha256, sha384, HashState, Sha256State, Sha384State}; 4 | 5 | // ShaXXX is a struct that represents either a Sha256 or Sha384 hash Algorithm 6 | // 7 | // ShaXXXContext is a wrapper over the ShaXXXState from SymCrypt. This is what needs to 8 | // be initialized in order to run stateful operations on the SymCrypt ShaXXXState. 9 | // 10 | // 11 | // 12 | // Impl's for the Hash trait for both Sha256 and Sha384 implement the Ruslts traits for hashing 13 | // 14 | // `algorithm()` returns Rustls' friendly [`HashAlgorithm`] name 15 | // 16 | // `output_len()` returns the output length for ShaXXX 17 | // 18 | // `start()` creates a `Box<>'d` ShaXXXContext that is needed in order to run stateful operations 19 | // 20 | // `hash()` computes a stateless hash operation based on the current ShaXXX hash algorithm. 21 | // 22 | // 23 | // 24 | // Impl's for the Context trait for both [`Sha256Context`] and [`Sha384Context`] implement the Rustls trait for 25 | // hashing context which is called state on the SymCrypt side. 26 | // 27 | // `fork_finish()` creates clones of the current hash state and then returns the clone'd hash result 28 | // There is no intermediate fork operation like this for this native to SymCrypt so a clone must be created. 29 | // 30 | // `fork()` creates a new ShaXXXContext that is a clone of the current Hash state 31 | // 32 | // `finish()` returns the hash output for the current ShaXXXContext. This results in the end of lifetime for the ShaXXXContext and therefore is 33 | // the end of life for ShaXXXState. `SymCryptWipe()` will be called under the covers. 34 | // 35 | // `update()` appends data to the ShaXXXState in order to be hashed. This operation can be done multiple times. 36 | 37 | /// Structs related to Sha256 38 | pub struct Sha256; 39 | pub struct Sha256Context(Sha256State); 40 | 41 | /// Structs related to Sha384 42 | pub struct Sha384; 43 | struct Sha384Context(Sha384State); 44 | 45 | /// Impl's for Sha256 related traits 46 | impl Hash for Sha256 { 47 | fn algorithm(&self) -> HashAlgorithm { 48 | HashAlgorithm::SHA256 49 | } 50 | 51 | fn output_len(&self) -> usize { 52 | 32 53 | } 54 | 55 | fn start(&self) -> Box { 56 | Box::new(Sha256Context(Sha256State::new())) 57 | } 58 | 59 | fn hash(&self, data: &[u8]) -> Output { 60 | Output::new(&sha256(data)[..]) 61 | } 62 | } 63 | 64 | impl Context for Sha256Context { 65 | fn fork_finish(&self) -> Output { 66 | let mut new_context = self.0.clone(); 67 | 68 | Output::new(&new_context.result()[..]) 69 | } 70 | 71 | fn fork(&self) -> Box { 72 | Box::new(Sha256Context(self.0.clone())) 73 | } 74 | 75 | fn finish(mut self: Box) -> Output { 76 | Output::new(&self.0.result()[..]) 77 | } 78 | 79 | fn update(&mut self, data: &[u8]) { 80 | self.0.append(data); 81 | } 82 | } 83 | 84 | /// Impl's for Sha384 related traits 85 | impl Hash for Sha384 { 86 | fn algorithm(&self) -> HashAlgorithm { 87 | HashAlgorithm::SHA384 88 | } 89 | 90 | fn output_len(&self) -> usize { 91 | 48 92 | } 93 | 94 | fn start(&self) -> Box { 95 | Box::new(Sha384Context(Sha384State::new())) 96 | } 97 | 98 | fn hash(&self, data: &[u8]) -> Output { 99 | Output::new(&sha384(data)[..]) 100 | } 101 | } 102 | 103 | impl Context for Sha384Context { 104 | fn fork_finish(&self) -> Output { 105 | let mut new_context = self.0.clone(); 106 | Output::new(&new_context.result()[..]) 107 | } 108 | 109 | fn fork(&self) -> Box { 110 | Box::new(Sha384Context(self.0.clone())) 111 | } 112 | 113 | fn finish(mut self: Box) -> Output { 114 | Output::new(&self.0.result()[..]) 115 | } 116 | 117 | fn update(&mut self, data: &[u8]) { 118 | self.0.append(data); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /rustls-symcrypt/src/hmac.rs: -------------------------------------------------------------------------------- 1 | //! Hmac functions. For further documentation please refer to rust_symcrypt::hmac 2 | use rustls::crypto::hmac::{Hmac, Key, Tag}; 3 | use symcrypt::hmac::{HmacSha256State, HmacSha384State, HmacState}; 4 | 5 | // HmacShaXXX is a struct that represents either HmacSha256 or HmacSha384 6 | // 7 | // HmacShaXXXKey is a wrapper around HmacShaXXXState This is what needs to 8 | // be initialized in order to run stateful operations on the SymCrypt HmacShaXXXState 9 | // 10 | // 11 | // 12 | // Impl's for the Hash trait for both HmacSha256 and HmacSha384 implement the Ruslts traits for hashing 13 | // 14 | // `with_key()` creates a new `Box<>'d` HmacShaXXXState via its wrapper HmacShaXXXKey. This state is needed in order to 15 | // run stateful operations. 16 | // 17 | // `hash_output_len()` returns the hash output length based on the hash algorithm. 18 | // 19 | // 20 | // 21 | // Impl's for the Key trait for both HmacSha256Key and HmacSha384Key implement the Rustls trait for 22 | // Key which is called state on the SymCrypt side. 23 | // 24 | // `sign()` returns a tag based on the data that is passed in. 25 | // 26 | // `sign_concat()` returns a tag based on the set of first, middle and last data that is passed in. 27 | // The passed data will be appended sequentially to the HmacShaXXXState. 28 | // 29 | // `tag_len()` returns the tag length associated wit Hmac algorithm. 30 | 31 | /// Structs related to HmacSha256 32 | pub struct HmacSha256; 33 | pub struct HmacSha256Key(HmacSha256State); 34 | 35 | /// Structs related to HmacSha384 36 | pub struct HmacSha384; 37 | pub struct HmacSha384Key(HmacSha384State); 38 | 39 | /// Impl's related to HmacSha256 40 | impl Hmac for HmacSha256 { 41 | fn with_key(&self, key: &[u8]) -> Box { 42 | // unwrap here since rustls::hmac does not accept errors. 43 | Box::new(HmacSha256Key(HmacSha256State::new(key).unwrap())) 44 | } 45 | 46 | fn hash_output_len(&self) -> usize { 47 | 32 48 | } 49 | } 50 | 51 | impl Key for HmacSha256Key { 52 | fn sign(&self, data: &[&[u8]]) -> Tag { 53 | self.sign_concat(&[], data, &[]) 54 | } 55 | 56 | fn sign_concat(&self, first: &[u8], middle: &[&[u8]], last: &[u8]) -> Tag { 57 | let mut new_state = self.0.clone(); 58 | new_state.append(first); 59 | for d in middle { 60 | new_state.append(d); 61 | } 62 | new_state.append(last); 63 | 64 | let result = new_state.result(); 65 | Tag::new(&result) 66 | } 67 | 68 | fn tag_len(&self) -> usize { 69 | 32 70 | } 71 | } 72 | 73 | /// Impl's related to HmacSha384 74 | impl Hmac for HmacSha384 { 75 | fn with_key(&self, key: &[u8]) -> Box { 76 | // unwrap here since rustls::Hmac does not accept errors. 77 | Box::new(HmacSha384Key(HmacSha384State::new(key).unwrap())) 78 | } 79 | 80 | fn hash_output_len(&self) -> usize { 81 | 48 82 | } 83 | } 84 | 85 | impl Key for HmacSha384Key { 86 | fn sign(&self, data: &[&[u8]]) -> Tag { 87 | self.sign_concat(&[], data, &[]) 88 | } 89 | 90 | fn sign_concat(&self, first: &[u8], middle: &[&[u8]], last: &[u8]) -> Tag { 91 | let mut new_state = self.0.clone(); 92 | new_state.append(first); 93 | for d in middle { 94 | new_state.append(d); 95 | } 96 | new_state.append(last); 97 | 98 | let result = new_state.result(); 99 | Tag::new(&result) 100 | } 101 | 102 | fn tag_len(&self) -> usize { 103 | 48 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /rustls-symcrypt/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | use rustls::crypto::{CryptoProvider, GetRandomFailed, SecureRandom, SupportedKxGroup}; 3 | 4 | use rustls::SupportedCipherSuite; 5 | use symcrypt::symcrypt_random; 6 | 7 | mod cipher_suites; 8 | mod ecdh; 9 | mod hash; 10 | mod hmac; 11 | mod signer; 12 | mod tls12; 13 | mod tls13; 14 | mod verify; 15 | use crate::verify::SUPPORTED_SIG_ALGS; 16 | 17 | /// Exporting default cipher suites for TLS 1.3 18 | pub use cipher_suites::{TLS13_AES_128_GCM_SHA256, TLS13_AES_256_GCM_SHA384}; 19 | 20 | /// Exporting default cipher suites for TLS 1.2 21 | pub use cipher_suites::{ 22 | TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, 23 | TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 24 | }; 25 | 26 | /// Exporting ChaCha suites for TLS 1.2 and TLS 1.3 27 | #[cfg(feature = "chacha")] 28 | pub use cipher_suites::{ 29 | TLS13_CHACHA20_POLY1305_SHA256, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 30 | TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 31 | }; 32 | 33 | /// Exporting default key exchange groups 34 | pub use ecdh::{SECP256R1, SECP384R1}; 35 | 36 | /// Exporting X25519 key exchange group 37 | #[cfg(feature = "x25519")] 38 | pub use ecdh::X25519; 39 | 40 | /// `default_symcrypt_provider` returns a `CryptoProvider` using the default `SymCrypt` configuration and cipher suites. 41 | /// To see the default cipher suites, please take a look at [`DEFAULT_CIPHER_SUITES`]. 42 | /// 43 | /// Sample usage: 44 | /// ```rust 45 | /// use rustls::{ClientConfig, RootCertStore}; 46 | /// use rustls_symcrypt::default_symcrypt_provider; 47 | /// use std::sync::Arc; 48 | /// use webpki_roots; 49 | /// 50 | /// let mut root_store = RootCertStore { 51 | /// roots: webpki_roots::TLS_SERVER_ROOTS.iter().cloned().collect(), 52 | /// }; 53 | /// 54 | /// let mut config = 55 | /// ClientConfig::builder_with_provider(Arc::new(default_symcrypt_provider())) 56 | /// .with_safe_default_protocol_versions() 57 | /// .unwrap() 58 | /// .with_root_certificates(root_store) 59 | /// .with_no_client_auth(); 60 | /// // Rest of the connection setup 61 | /// 62 | /// ``` 63 | pub fn default_symcrypt_provider() -> CryptoProvider { 64 | CryptoProvider { 65 | cipher_suites: DEFAULT_CIPHER_SUITES.to_vec(), 66 | kx_groups: ecdh::ALL_KX_GROUPS.to_vec(), 67 | signature_verification_algorithms: SUPPORTED_SIG_ALGS, 68 | secure_random: &SymCrypt, 69 | key_provider: &signer::SymCryptProvider, 70 | } 71 | } 72 | 73 | /// `custom_symcrypt_provider` provides a way to set up an custom config using a `symcrypt` crypto backend. 74 | /// 75 | /// `provided_cipher_suites` takes in an optional `Vec<>` of `SupportedCipherSuites`. 76 | /// The supplied arguments for `provided_cipher_suite` will be used when when negotiating the TLS cipher suite; 77 | /// and should be placed in preference order, where the first element has highest priority. 78 | /// If `None` or an empty `Vec<>` is provided the [`DEFAULT_CIPHER_SUITES`] will be used instead. 79 | /// 80 | /// `provided_kx_group` takes in an optional `Vec<>` of `SupportedKxGroup` 81 | /// The supplied arguments for `provided_kx_group` will be used when when negotiating the TLS key exchange; 82 | /// and should be placed in preference order, where the first element has highest priority. 83 | /// If `None` or an empty `Vec<>` is provided the default will be used instead. 84 | /// 85 | /// This call cannot fail. 86 | /// 87 | /// Sample usage: 88 | /// ```rust 89 | /// use rustls::{ClientConfig, RootCertStore}; 90 | /// use rustls_symcrypt::{custom_symcrypt_provider, TLS13_AES_128_GCM_SHA256, SECP256R1}; 91 | /// use std::sync::Arc; 92 | /// use webpki_roots; 93 | /// 94 | /// let mut root_store = RootCertStore { 95 | /// roots: webpki_roots::TLS_SERVER_ROOTS.iter().cloned().collect(), 96 | /// }; 97 | /// 98 | /// // Set custom config of cipher suites that have been imported from rustls_symcrypt. 99 | /// let cipher_suites = vec![TLS13_AES_128_GCM_SHA256]; 100 | /// let kx_group = vec![SECP256R1]; 101 | /// 102 | /// let mut config = 103 | /// ClientConfig::builder_with_provider(Arc::new(custom_symcrypt_provider( 104 | /// Some(cipher_suites), Some(kx_group)))) 105 | /// .with_safe_default_protocol_versions() 106 | /// .unwrap() 107 | /// .with_root_certificates(root_store) 108 | /// .with_no_client_auth(); 109 | /// // Rest of the connection setup 110 | /// ``` 111 | pub fn custom_symcrypt_provider( 112 | provided_cipher_suites: Option>, 113 | provided_kx_group: Option>, 114 | ) -> CryptoProvider { 115 | let cipher_suites = match provided_cipher_suites { 116 | Some(suites) if !suites.is_empty() => suites, // Use provided non-empty suites 117 | _ => DEFAULT_CIPHER_SUITES.to_vec(), // Use default suites if None or empty 118 | }; 119 | 120 | let kx_group = match provided_kx_group { 121 | Some(groups) if !groups.is_empty() => groups, // Use provided non-empty groups 122 | _ => ecdh::ALL_KX_GROUPS.to_vec(), // Use default groups if None or empty 123 | }; 124 | 125 | CryptoProvider { 126 | cipher_suites, 127 | kx_groups: kx_group, 128 | signature_verification_algorithms: SUPPORTED_SIG_ALGS, 129 | secure_random: &SymCrypt, 130 | key_provider: &signer::SymCryptProvider, 131 | } 132 | } 133 | 134 | /// List of SymCrypt supported cipher suites in a preference order. 135 | /// The first element has highest priority when negotiating cipher suites. 136 | /// ```ignore 137 | /// // TLS 1.3 suites 138 | /// TLS13_AES_256_GCM_SHA384 139 | /// TLS13_AES_128_GCM_SHA256 140 | /// TLS13_CHACHA20_POLY1305_SHA256 // Enabled with the `chacha` feature 141 | /// // TLS 1.2 suites 142 | /// TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 143 | /// TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 144 | /// TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 // Enabled with the `chacha` feature 145 | /// TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 146 | /// TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 147 | /// TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 // Enabled with the `chacha` feature 148 | /// ``` 149 | pub static DEFAULT_CIPHER_SUITES: &[SupportedCipherSuite] = ALL_CIPHER_SUITES; 150 | 151 | static ALL_CIPHER_SUITES: &[SupportedCipherSuite] = &[ 152 | // TLS 1.3 suites 153 | TLS13_AES_256_GCM_SHA384, 154 | TLS13_AES_128_GCM_SHA256, 155 | #[cfg(feature = "chacha")] 156 | TLS13_CHACHA20_POLY1305_SHA256, 157 | // TLS 1.2 suites 158 | TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, 159 | TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 160 | #[cfg(feature = "chacha")] 161 | TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 162 | TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 163 | TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 164 | #[cfg(feature = "chacha")] 165 | TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 166 | ]; 167 | 168 | #[derive(Debug)] 169 | struct SymCrypt; 170 | 171 | impl SecureRandom for SymCrypt { 172 | fn fill(&self, buf: &mut [u8]) -> Result<(), GetRandomFailed> { 173 | symcrypt_random(buf); 174 | Ok(()) 175 | } 176 | } 177 | 178 | #[cfg(test)] 179 | mod test { 180 | use super::*; 181 | 182 | #[test] 183 | fn test_secure_random() { 184 | let random = SymCrypt; 185 | let mut buff_1 = [0u8; 10]; 186 | let mut buff_2 = [0u8; 10]; 187 | 188 | let _ = random.fill(&mut buff_1); 189 | let _ = random.fill(&mut buff_2); 190 | 191 | assert_ne!(buff_1, buff_2); 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /rustls-symcrypt/src/signer.rs: -------------------------------------------------------------------------------- 1 | use rustls::crypto::KeyProvider; 2 | use rustls::pki_types::PrivateKeyDer; 3 | use rustls::sign::Signer; 4 | use rustls::sign::SigningKey; 5 | use rustls::Error; 6 | use rustls::{SignatureAlgorithm, SignatureScheme}; 7 | use symcrypt::{ 8 | ecc::{CurveType, EcKey, EcKeyUsage}, 9 | hash::{sha256, sha384, sha512, HashAlgorithm}, 10 | rsa::{RsaKey, RsaKeyUsage}, 11 | }; 12 | 13 | use pkcs1::der::Decode; 14 | use pkcs1::RsaPrivateKey; 15 | use pkcs8::PrivateKeyInfo; 16 | use sec1::EcPrivateKey; 17 | use std::fmt::Debug; 18 | use std::sync::Arc; 19 | 20 | use der::Encode; 21 | use pkcs1::RsaPublicKey as ECSignatureData; 22 | use pkcs1::UintRef; 23 | use symcrypt::hash::{SHA256_RESULT_SIZE, SHA384_RESULT_SIZE, SHA512_RESULT_SIZE}; 24 | 25 | pub fn any_supported_type(der: &PrivateKeyDer<'_>) -> Result, Error> { 26 | if let Ok(rsa) = RsaSigningKey::new(der) { 27 | return Ok(Arc::new(rsa)); 28 | } 29 | 30 | if let Ok(ecdsa) = any_ecdsa_type(der) { 31 | return Ok(ecdsa); 32 | } 33 | 34 | Err(Error::General( 35 | "failed to parse private key as RSA or ECDSA".into(), 36 | )) 37 | } 38 | 39 | pub fn any_ecdsa_type(der: &PrivateKeyDer<'_>) -> Result, Error> { 40 | if let Ok(ecdsa_p256) = EcdsaSigningKey::new(der, SignatureScheme::ECDSA_NISTP256_SHA256) { 41 | Ok(Arc::new(ecdsa_p256)) 42 | } else if let Ok(ecdsa_p384) = EcdsaSigningKey::new(der, SignatureScheme::ECDSA_NISTP384_SHA384) 43 | { 44 | Ok(Arc::new(ecdsa_p384)) 45 | } else if let Ok(ecdsa_p521) = EcdsaSigningKey::new(der, SignatureScheme::ECDSA_NISTP521_SHA512) 46 | { 47 | Ok(Arc::new(ecdsa_p521)) 48 | } else { 49 | Err(Error::General( 50 | "failed to parse ECDSA private key as PKCS#8 or SEC1".into(), 51 | )) 52 | } 53 | } 54 | 55 | #[derive(Debug)] 56 | pub struct RsaSigningKey { 57 | key: Arc, 58 | } 59 | 60 | static ALL_RSA_SCHEMES: &[SignatureScheme] = &[ 61 | SignatureScheme::RSA_PSS_SHA512, 62 | SignatureScheme::RSA_PSS_SHA384, 63 | SignatureScheme::RSA_PSS_SHA256, 64 | SignatureScheme::RSA_PKCS1_SHA512, 65 | SignatureScheme::RSA_PKCS1_SHA384, 66 | SignatureScheme::RSA_PKCS1_SHA256, 67 | ]; 68 | 69 | impl RsaSigningKey { 70 | pub fn new(der: &PrivateKeyDer<'_>) -> Result { 71 | let key = match der { 72 | PrivateKeyDer::Pkcs1(pkcs1) => { 73 | // Get the DER-encoded private key blob 74 | let private_key_blob = pkcs1.secret_pkcs1_der(); // returns &[u8] that is DER encoded 75 | 76 | // Parse the DER-encoded RSA private key 77 | let private_key = RsaPrivateKey::from_der(private_key_blob) 78 | .map_err(|_| Error::General("Failed to parse PKCS#1 DER".into()))?; 79 | 80 | // Extract the components and set the key pair 81 | RsaKey::set_key_pair( 82 | private_key.modulus.as_bytes(), 83 | private_key.public_exponent.as_bytes(), 84 | private_key.prime1.as_bytes(), 85 | private_key.prime2.as_bytes(), 86 | RsaKeyUsage::Sign, 87 | ) 88 | .map_err(|_| Error::General("Failed to set RsaKey from PKCS#1".into()))? 89 | } 90 | 91 | PrivateKeyDer::Pkcs8(pkcs8) => { 92 | let private_key_blob = pkcs8.secret_pkcs8_der(); // DER-encoded &[u8] 93 | 94 | // Parse the DER-encoded private key 95 | let private_key_info = PrivateKeyInfo::from_der(private_key_blob) 96 | .map_err(|_| Error::General("Failed to parse PKCS#8 DER".into()))?; 97 | let private_key = RsaPrivateKey::from_der(private_key_info.private_key) 98 | .map_err(|_| Error::General("Failed to parse PKCS#8 DER".into()))?; 99 | 100 | // Extract the components and set the key pair 101 | RsaKey::set_key_pair( 102 | private_key.modulus.as_bytes(), 103 | private_key.public_exponent.as_bytes(), 104 | private_key.prime1.as_bytes(), 105 | private_key.prime2.as_bytes(), 106 | RsaKeyUsage::Sign, 107 | ) 108 | .map_err(|_| Error::General("Failed to set RsaKey from PKCS#8".into()))? 109 | } 110 | _ => { 111 | return Err(Error::General( 112 | "Failed to parse RSA private key as either PKCS#1 or PKCS#8".into(), 113 | )); 114 | } 115 | }; 116 | 117 | Ok(Self { key: Arc::new(key) }) 118 | } 119 | } 120 | 121 | impl SigningKey for RsaSigningKey { 122 | fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option> { 123 | ALL_RSA_SCHEMES 124 | .iter() 125 | .find(|scheme| offered.contains(scheme)) 126 | .map(|&scheme| { 127 | Box::new(RsaSigner { 128 | key: Arc::clone(&self.key), 129 | scheme, 130 | }) as Box 131 | }) 132 | } 133 | 134 | fn algorithm(&self) -> SignatureAlgorithm { 135 | SignatureAlgorithm::RSA 136 | } 137 | } 138 | 139 | #[derive(Debug)] 140 | struct RsaSigner { 141 | key: Arc, 142 | scheme: SignatureScheme, 143 | } 144 | 145 | impl Signer for RsaSigner { 146 | fn sign(&self, message: &[u8]) -> Result, Error> { 147 | match self.scheme { 148 | SignatureScheme::RSA_PKCS1_SHA256 => { 149 | let hashed_message = sha256(message); 150 | match self.key.pkcs1_sign(&hashed_message, HashAlgorithm::Sha256) { 151 | Ok(signature) => Ok(signature), 152 | Err(e) => Err(Error::General(format!("failed to sign message: {}", e))), 153 | } 154 | } 155 | SignatureScheme::RSA_PKCS1_SHA384 => { 156 | let hashed_message = sha384(message); 157 | match self.key.pkcs1_sign(&hashed_message, HashAlgorithm::Sha384) { 158 | Ok(signature) => Ok(signature), 159 | Err(e) => Err(Error::General(format!("failed to sign message: {}", e))), 160 | } 161 | } 162 | SignatureScheme::RSA_PKCS1_SHA512 => { 163 | let hashed_message = sha512(message); 164 | match self.key.pkcs1_sign(&hashed_message, HashAlgorithm::Sha512) { 165 | Ok(signature) => Ok(signature), 166 | Err(e) => Err(Error::General(format!("failed to sign message: {}", e))), 167 | } 168 | } 169 | SignatureScheme::RSA_PSS_SHA256 => { 170 | let hashed_message = sha256(message); 171 | match self 172 | .key 173 | .pss_sign(&hashed_message, HashAlgorithm::Sha256, SHA256_RESULT_SIZE) 174 | { 175 | Ok(signature) => Ok(signature), 176 | Err(e) => Err(Error::General(format!("failed to sign message: {}", e))), 177 | } 178 | } 179 | SignatureScheme::RSA_PSS_SHA384 => { 180 | let hashed_message = sha384(message); 181 | match self 182 | .key 183 | .pss_sign(&hashed_message, HashAlgorithm::Sha384, SHA384_RESULT_SIZE) 184 | { 185 | Ok(signature) => Ok(signature), 186 | Err(e) => Err(Error::General(format!("failed to sign message: {}", e))), 187 | } 188 | } 189 | SignatureScheme::RSA_PSS_SHA512 => { 190 | let hashed_message = sha512(message); 191 | match self 192 | .key 193 | .pss_sign(&hashed_message, HashAlgorithm::Sha512, SHA512_RESULT_SIZE) 194 | { 195 | Ok(signature) => Ok(signature), 196 | Err(e) => Err(Error::General(format!("failed to sign message: {}", e))), 197 | } 198 | } 199 | _ => Err(Error::General( 200 | "unsupported RSA-PKCS1 signature scheme".into(), 201 | )), 202 | } 203 | } 204 | 205 | fn scheme(&self) -> SignatureScheme { 206 | self.scheme 207 | } 208 | } 209 | 210 | #[derive(Debug)] 211 | pub struct EcdsaSigningKey { 212 | key: Arc, 213 | scheme: SignatureScheme, 214 | } 215 | 216 | impl EcdsaSigningKey { 217 | /// Creates a new `ECDSASigningKey` from DER encoding in either PKCS#8 or SEC1 218 | /// format, ensuring compatibility with the specified signature scheme. 219 | fn new(der: &PrivateKeyDer<'_>, scheme: SignatureScheme) -> Result { 220 | // Map the signature scheme to rust-symcrypt's CurveType 221 | let curve_type = match scheme { 222 | SignatureScheme::ECDSA_NISTP256_SHA256 => CurveType::NistP256, 223 | SignatureScheme::ECDSA_NISTP384_SHA384 => CurveType::NistP384, 224 | SignatureScheme::ECDSA_NISTP521_SHA512 => CurveType::NistP521, 225 | _ => return Err(Error::General("Unsupported signature scheme".into())), 226 | }; 227 | 228 | // Initialize the key based on the DER encoding format 229 | let key = match der { 230 | PrivateKeyDer::Sec1(sec1) => { 231 | // Extract DER-encoded private key blob for SEC1 232 | let private_key_blob = sec1.secret_sec1_der(); 233 | 234 | // Parse the SEC1 DER-encoded EC private key 235 | let private_key = EcPrivateKey::from_der(private_key_blob) 236 | .map_err(|_| Error::General("Failed to parse SEC1 DER".into()))?; 237 | 238 | // Use EcPrivateKey's private_key to set up the ECDSA key 239 | EcKey::set_key_pair(curve_type, private_key.private_key, None, EcKeyUsage::EcDsa) 240 | .map_err(|_| Error::General("Failed to set ECDSA key from SEC1".into()))? 241 | } 242 | PrivateKeyDer::Pkcs8(pkcs8) => { 243 | // Extract DER-encoded private key blob for PKCS#8 244 | let private_key_blob = pkcs8.secret_pkcs8_der(); 245 | 246 | // Parse the DER-encoded private key 247 | let private_key_info = match PrivateKeyInfo::from_der(private_key_blob) { 248 | Ok(info) => info, 249 | Err(_) => { 250 | return Err(Error::General( 251 | "Failed to parse private key info from DER".into(), 252 | )) 253 | } 254 | }; 255 | 256 | // Parse the PKCS#8 DER-encoded EC private key 257 | let private_key = EcPrivateKey::from_der(private_key_info.private_key) 258 | .map_err(|_| Error::General("Failed to parse PKCS#8 DER".into()))?; 259 | 260 | // Use EcPrivateKey's private_key to set up the ECDSA key 261 | EcKey::set_key_pair(curve_type, private_key.private_key, None, EcKeyUsage::EcDsa) 262 | .map_err(|_| Error::General("Failed to set ECDSA key from PKCS#8".into()))? 263 | } 264 | _ => { 265 | return Err(Error::General( 266 | "Invalid key format: must be PKCS#1 or PKCS#8".into(), 267 | )) 268 | } 269 | }; 270 | 271 | // Return the ECDSASigningKey with Arc-wrapped key_pair and scheme 272 | Ok(Self { 273 | key: Arc::new(key), 274 | scheme, 275 | }) 276 | } 277 | } 278 | 279 | impl SigningKey for EcdsaSigningKey { 280 | fn choose_scheme(&self, offered: &[SignatureScheme]) -> Option> { 281 | if offered.contains(&self.scheme) { 282 | Some(Box::new(EcdsaSigner { 283 | key: Arc::clone(&self.key), 284 | scheme: self.scheme, 285 | })) 286 | } else { 287 | None 288 | } 289 | } 290 | 291 | fn algorithm(&self) -> SignatureAlgorithm { 292 | SignatureAlgorithm::ECDSA 293 | } 294 | } 295 | 296 | #[derive(Debug)] 297 | struct EcdsaSigner { 298 | key: Arc, 299 | scheme: SignatureScheme, 300 | } 301 | 302 | impl Signer for EcdsaSigner { 303 | fn sign(&self, message: &[u8]) -> Result, Error> { 304 | // Step 1: Hash the message based on the scheme 305 | let hash_value = match self.scheme { 306 | SignatureScheme::ECDSA_NISTP256_SHA256 => sha256(message).to_vec(), 307 | SignatureScheme::ECDSA_NISTP384_SHA384 => sha384(message).to_vec(), 308 | SignatureScheme::ECDSA_NISTP521_SHA512 => sha512(message).to_vec(), 309 | _ => return Err(Error::General("unsupported ECDSA signature scheme".into())), 310 | }; 311 | 312 | // Step 2: Sign the hashed message 313 | let signature = self 314 | .key 315 | .ecdsa_sign(&hash_value) 316 | .map_err(|e| Error::General(format!("failed to sign message: {}", e)))?; 317 | 318 | // Step 3: Split the signature into r and s components 319 | let (r, s) = signature.split_at(signature.len() / 2); 320 | 321 | // Step 4: Create a ECSignature structure which is mapped to pkcs1::RsaPublicKey, which contains the signature r and s 322 | // ECSignatureData is encoded as sequence of two integers. RsaPublicKey is also encoded as sequence of two integers. 323 | // Will use RsaPublicKey to enode where modulus contains r and public_exponent contains s 324 | let modulus = match UintRef::new(r) { 325 | Ok(value) => value, 326 | Err(_) => { 327 | return Err(Error::General( 328 | "Failed to create UintRef for modulus".into(), 329 | )) 330 | } 331 | }; 332 | 333 | let public_exponent = match UintRef::new(s) { 334 | Ok(value) => value, 335 | Err(_) => { 336 | return Err(Error::General( 337 | "Failed to create UintRef for public exponent".into(), 338 | )) 339 | } 340 | }; 341 | 342 | let ec_sig_data = ECSignatureData { 343 | modulus, // r 344 | public_exponent, // s 345 | }; 346 | 347 | // Step 5: Encode the RsaPublicKey using the Encode trait 348 | let mut encoded_signature = Vec::new(); 349 | ec_sig_data 350 | .encode_to_vec(&mut encoded_signature) 351 | .map_err(|e| Error::General(format!("failed to encode signature: {}", e)))?; 352 | 353 | Ok(encoded_signature) 354 | } 355 | 356 | fn scheme(&self) -> SignatureScheme { 357 | self.scheme 358 | } 359 | } 360 | 361 | #[derive(Debug)] 362 | pub struct SymCryptProvider; 363 | 364 | impl KeyProvider for SymCryptProvider { 365 | fn load_private_key( 366 | &self, 367 | key_der: PrivateKeyDer<'static>, 368 | ) -> Result, Error> { 369 | any_supported_type(&key_der) 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /rustls-symcrypt/src/tls12.rs: -------------------------------------------------------------------------------- 1 | //! GCM and ChaCha functions for TLS 1.2. For further documentation please refer to rust_symcrypt::gcm and symcrypt::chacha 2 | use crate::cipher_suites::AesGcm; 3 | use rustls::crypto::cipher::{ 4 | make_tls12_aad, AeadKey, InboundOpaqueMessage, InboundPlainMessage, Iv, KeyBlockShape, 5 | MessageDecrypter, MessageEncrypter, Nonce, OutboundOpaqueMessage, OutboundPlainMessage, 6 | PrefixedPayload, Tls12AeadAlgorithm, UnsupportedOperationError, 7 | }; 8 | use rustls::{ConnectionTrafficSecrets, Error}; 9 | use symcrypt::cipher::BlockCipherType; 10 | 11 | use symcrypt::gcm::GcmExpandedKey; 12 | const GCM_FULL_NONCE_LENGTH: usize = 12; 13 | const GCM_EXPLICIT_NONCE_LENGTH: usize = 8; 14 | const GCM_IMPLICIT_NONCE_LENGTH: usize = 4; 15 | const GCM_TAG_LENGTH: usize = 16; 16 | 17 | #[cfg(feature = "chacha")] 18 | use symcrypt::chacha::{chacha20_poly1305_decrypt_in_place, chacha20_poly1305_encrypt_in_place}; 19 | #[cfg(feature = "chacha")] 20 | const CHACHA_TAG_LENGTH: usize = 16; 21 | #[cfg(feature = "chacha")] 22 | const CHAHCA_NONCE_LENGTH: usize = 12; 23 | #[cfg(feature = "chacha")] 24 | const CHACHA_KEY_LENGTH: usize = 32; 25 | 26 | /// ChaCha for TLS 1.2 27 | /// ChaCha functionality will be disabled by default, in order to enable ChaCha functionality, 28 | /// user must pass the "chacha" feature via `Cargo.toml` 29 | /// 30 | /// `Tls12ChaCha` impls `Tls12AeadAlgorithm`. 31 | #[cfg(feature = "chacha")] 32 | pub struct Tls12ChaCha; 33 | 34 | /// `TLs12ChaCha20Poly1305` impls `MessageEncrypter` and `MessageDecrypter` 35 | /// `key` is a ChaCha key and must be 32 bytes long. 36 | /// `iv` is an initialization vector that is needed to create the unique nonce. 37 | #[cfg(feature = "chacha")] 38 | pub struct Tls12ChaCha20Poly1305 { 39 | key: [u8; CHACHA_KEY_LENGTH], 40 | iv: Iv, 41 | } 42 | 43 | #[cfg(feature = "chacha")] 44 | impl Tls12AeadAlgorithm for Tls12ChaCha { 45 | fn encrypter(&self, key: AeadKey, iv: &[u8], _: &[u8]) -> Box { 46 | assert_eq!(key.as_ref().len(), CHACHA_KEY_LENGTH); // ChaCha key length must be 32 bytes. 47 | 48 | let mut chacha_key = [0u8; CHACHA_KEY_LENGTH]; 49 | chacha_key[..CHACHA_KEY_LENGTH].copy_from_slice(key.as_ref()); 50 | 51 | Box::new(Tls12ChaCha20Poly1305 { 52 | key: chacha_key, 53 | iv: Iv::copy(iv), 54 | }) 55 | } 56 | 57 | fn decrypter(&self, key: AeadKey, iv: &[u8]) -> Box { 58 | assert_eq!(key.as_ref().len(), CHACHA_KEY_LENGTH); // ChaCha key length must be 32 bytes. 59 | 60 | let mut chacha_key = [0u8; CHACHA_KEY_LENGTH]; 61 | chacha_key[..CHACHA_KEY_LENGTH].copy_from_slice(key.as_ref()); 62 | 63 | Box::new(Tls12ChaCha20Poly1305 { 64 | key: chacha_key, 65 | iv: Iv::copy(iv), 66 | }) 67 | } 68 | 69 | fn key_block_shape(&self) -> KeyBlockShape { 70 | KeyBlockShape { 71 | enc_key_len: CHACHA_KEY_LENGTH, // ChaCha key must be 32 bytes. 72 | fixed_iv_len: CHAHCA_NONCE_LENGTH, 73 | explicit_nonce_len: 0, 74 | } 75 | } 76 | 77 | fn extract_keys( 78 | &self, 79 | key: AeadKey, 80 | iv: &[u8], 81 | _explicit: &[u8], 82 | ) -> Result { 83 | assert_eq!(CHAHCA_NONCE_LENGTH, iv.len()); // Nonce length must be 12 for ChaCha 84 | Ok(ConnectionTrafficSecrets::Chacha20Poly1305 { 85 | key, 86 | iv: Iv::new(iv[..].try_into().unwrap()), 87 | }) 88 | } 89 | } 90 | 91 | /// `MessageEncrypter` for ChaCha 1.2 92 | /// the `payload` field that comes from the `OutboundPlainMessage` is structured to include the message which is an arbitrary length, 93 | /// and the tag which is 16 bytes. 94 | /// ex : [1, 2, 3, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ,13, 14, 15, 16] 95 | /// ^ ^ ^ ^ 96 | /// Message (N bytes) Tag (16 bytes) 97 | #[cfg(feature = "chacha")] 98 | impl MessageEncrypter for Tls12ChaCha20Poly1305 { 99 | fn encrypt( 100 | &mut self, 101 | msg: OutboundPlainMessage, 102 | seq: u64, 103 | ) -> Result { 104 | // Adding the size of message, the tag and encoding type to the capacity of payload vector. 105 | // Must create the payload this way. There is a header of 5 bytes at the front of the payload. 106 | // Using overridden with_capacity() will return a new payload with the header of 5 bytes set to 0 and accounted for. 107 | let total_len = self.encrypted_payload_len(msg.payload.len()); 108 | let mut payload = PrefixedPayload::with_capacity(total_len); 109 | 110 | // payload will be appended do via extend_from_chunks() starting after the 5 byte buffer. 111 | payload.extend_from_chunks(&msg.payload); 112 | 113 | // Set up needed parameters for ChaCha encrypt. 114 | let mut tag = [0u8; CHACHA_TAG_LENGTH]; 115 | let nonce = Nonce::new(&self.iv, seq); 116 | let auth_data = make_tls12_aad(seq, msg.typ, msg.version, msg.payload.len()); 117 | 118 | // ChaCha Encrypt in place, only the message from the payload will be encrypted. 119 | // payload.as_mut() returns the slice that is indexed by 5 bytes to avoid encrypting the header. 120 | match chacha20_poly1305_encrypt_in_place( 121 | &self.key, 122 | &nonce.0, 123 | &auth_data, 124 | &mut payload.as_mut()[..msg.payload.len()], 125 | &mut tag, 126 | ) { 127 | Ok(_) => { 128 | payload.extend_from_slice(&tag); // Add tag to the end of the payload. 129 | Ok(OutboundOpaqueMessage::new(msg.typ, msg.version, payload)) 130 | } 131 | Err(symcrypt_error) => { 132 | let custom_error_message = format!( 133 | "SymCryptError: {}", 134 | symcrypt_error // Using general error to propagate the SymCrypt error back to the caller. 135 | ); 136 | Err(Error::General(custom_error_message)) 137 | } 138 | } 139 | } 140 | 141 | fn encrypted_payload_len(&self, payload_len: usize) -> usize { 142 | payload_len + CHACHA_TAG_LENGTH 143 | } 144 | } 145 | 146 | /// `MessageDecrypter` for ChaCha 1.2 147 | /// the `payload` field that comes from the `InboundOpaqueMessage` is structured to include the message which is an arbitrary length, 148 | /// and the tag which is 16 bytes. 149 | /// ex : [1, 2, 3, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ,13, 14, 15, 16] 150 | /// ^ ^ ^ ^ 151 | /// Message (N bytes) Tag (16 bytes) 152 | #[cfg(feature = "chacha")] 153 | impl MessageDecrypter for Tls12ChaCha20Poly1305 { 154 | fn decrypt<'a>( 155 | &mut self, 156 | mut msg: InboundOpaqueMessage<'a>, 157 | seq: u64, 158 | ) -> Result, Error> { 159 | let payload = &mut msg.payload; // payload is already mutable since it is a reference to [`BorrowedPayload`] 160 | let payload_len = payload.len(); // This length includes the message and the tag. 161 | if payload_len < CHACHA_TAG_LENGTH { 162 | return Err(Error::DecryptError); 163 | } 164 | let message_len = payload_len - CHACHA_TAG_LENGTH; // This length is only the message and does not include tag. 165 | 166 | // Set up needed parameters for ChaCha decrypt 167 | let nonce = Nonce::new(&self.iv, seq); 168 | let auth_data = make_tls12_aad(seq, msg.typ, msg.version, message_len); 169 | let mut tag = [0u8; CHACHA_TAG_LENGTH]; 170 | tag.copy_from_slice(&payload[message_len..]); // get the tag 171 | 172 | // Decrypting the payload in place, only the message from the payload will be decrypted. 173 | match chacha20_poly1305_decrypt_in_place( 174 | &self.key, 175 | &nonce.0, 176 | &auth_data, 177 | &mut payload[..message_len], 178 | &tag, 179 | ) { 180 | Ok(_) => { 181 | payload.truncate(message_len); 182 | Ok(msg.into_plain_message()) 183 | } 184 | Err(symcrypt_error) => { 185 | let custom_error_message = format!( 186 | "SymCryptError: {}", 187 | symcrypt_error // Using general error to propagate the SymCrypt error back to the caller 188 | ); 189 | Err(Error::General(custom_error_message)) 190 | } 191 | } 192 | } 193 | } 194 | 195 | /// GCM 1.2 196 | /// Tls12Gcm impls [`Tls12AeadAlgorithm`]. 197 | /// 198 | /// `algo_type` represents either `Aes128Gcm` or `Aes256Gcm` which corresponds to a 16 and 32 byte key respectively. 199 | pub struct Tls12Gcm { 200 | pub(crate) algo_type: AesGcm, 201 | } 202 | 203 | /// Gcm12Decrypt impls [`MessageDecrypter`] 204 | /// `key` is a [`GcmExpandedKey`] which takes in a key, and block type to return a Pin>'d expanded key. 205 | /// The only supported block type is AES. 206 | /// `iv` is an implicit Iv that must be 4 bytes. 207 | pub struct Gcm12Decrypt { 208 | key: GcmExpandedKey, 209 | iv: [u8; GCM_IMPLICIT_NONCE_LENGTH], 210 | } 211 | 212 | /// Gcm12Encrypt impls [`MessageEncrypter`] 213 | /// `key` is a [`GcmExpandedKey`] which takes in a key, and block type to return a Pin>'d expanded key. 214 | /// The only supported block type is AES. 215 | /// `full_iv` includes both the implicit and the explicit iv. 216 | pub struct Gcm12Encrypt { 217 | key: GcmExpandedKey, 218 | full_iv: [u8; GCM_FULL_NONCE_LENGTH], 219 | } 220 | 221 | impl Tls12AeadAlgorithm for Tls12Gcm { 222 | fn encrypter(&self, key: AeadKey, iv: &[u8], extra: &[u8]) -> Box { 223 | assert_eq!(iv.len(), GCM_IMPLICIT_NONCE_LENGTH); 224 | assert_eq!(extra.len(), GCM_EXPLICIT_NONCE_LENGTH); 225 | let mut full_iv = [0u8; GCM_FULL_NONCE_LENGTH]; 226 | full_iv[..GCM_IMPLICIT_NONCE_LENGTH].copy_from_slice(iv); 227 | full_iv[GCM_IMPLICIT_NONCE_LENGTH..].copy_from_slice(extra); 228 | 229 | // Unwrapping here, since rustls does not expect this to fail. 230 | // In the scenarios that GcmExpandKey would fail should result in a panic, ie: Not enough memory. 231 | Box::new(Gcm12Encrypt { 232 | key: GcmExpandedKey::new(key.as_ref(), BlockCipherType::AesBlock).unwrap(), 233 | full_iv, 234 | }) 235 | } 236 | 237 | fn decrypter(&self, key: AeadKey, iv: &[u8]) -> Box { 238 | assert_eq!(iv.len(), GCM_IMPLICIT_NONCE_LENGTH); 239 | let mut implicit_iv = [0u8; GCM_IMPLICIT_NONCE_LENGTH]; 240 | implicit_iv.copy_from_slice(iv); 241 | 242 | // Unwrapping here, since rustls does not expect this to fail. 243 | // In the scenarios that GcmExpandKey would fail should result in a panic, ie: Not enough memory. 244 | Box::new(Gcm12Decrypt { 245 | key: GcmExpandedKey::new(key.as_ref(), BlockCipherType::AesBlock).unwrap(), 246 | iv: implicit_iv, 247 | }) 248 | } 249 | 250 | fn key_block_shape(&self) -> KeyBlockShape { 251 | KeyBlockShape { 252 | enc_key_len: self.algo_type.key_size(), // Can be either 16 or 32 253 | fixed_iv_len: GCM_IMPLICIT_NONCE_LENGTH, 254 | explicit_nonce_len: GCM_EXPLICIT_NONCE_LENGTH, 255 | } 256 | } 257 | 258 | fn extract_keys( 259 | &self, 260 | key: AeadKey, 261 | iv: &[u8], 262 | explicit: &[u8], 263 | ) -> Result { 264 | let mut gcm_iv = [0; GCM_FULL_NONCE_LENGTH]; 265 | gcm_iv[..GCM_IMPLICIT_NONCE_LENGTH].copy_from_slice(iv); 266 | gcm_iv[GCM_IMPLICIT_NONCE_LENGTH..].copy_from_slice(explicit); 267 | 268 | match self.algo_type.key_size() { 269 | 16 => Ok(ConnectionTrafficSecrets::Aes128Gcm { 270 | key, 271 | iv: Iv::new(gcm_iv), 272 | }), 273 | 32 => Ok(ConnectionTrafficSecrets::Aes256Gcm { 274 | key, 275 | iv: Iv::new(gcm_iv), 276 | }), 277 | _ => Err(UnsupportedOperationError), 278 | } 279 | } 280 | } 281 | 282 | /// [`MessageEncrypter`] for Gcm 1.2 283 | /// the `payload` field that comes from the [`OutboundPlainMessage`] is structured to include the explicit iv which is 8 bytes, 284 | /// the message which is an arbitrary length, and the tag which is 16 bytes. 285 | /// ex : [1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ,13, 14, 15, 16] 286 | /// ^ ^ ^ ^ ^ ^ 287 | /// Explicit Iv (8 bytes) Message (N bytes) Tag (16 bytes) 288 | impl MessageEncrypter for Gcm12Encrypt { 289 | fn encrypt( 290 | &mut self, 291 | msg: OutboundPlainMessage, 292 | seq: u64, 293 | ) -> Result { 294 | // Adding the size of message, the tag and encoding type to the capacity of payload vector. 295 | // Must create the payload this way. There is a header of 5 bytes at the front of the payload. 296 | // Using overridden with_capacity() will return a new payload with the header of 5 bytes set to 0 and accounted for. 297 | let total_len = self.encrypted_payload_len(msg.payload.len()); 298 | let mut payload = PrefixedPayload::with_capacity(total_len); 299 | 300 | // Construct the payload 301 | let nonce = Nonce::new(&Iv::copy(&self.full_iv), seq); 302 | payload.extend_from_slice(&nonce.0[GCM_IMPLICIT_NONCE_LENGTH..]); 303 | payload.extend_from_chunks(&msg.payload); 304 | 305 | let mut tag = [0u8; GCM_TAG_LENGTH]; 306 | let auth_data = make_tls12_aad(seq, msg.typ, msg.version, msg.payload.len()); 307 | 308 | // Encrypting the payload in place, only the message from the payload will be encrypted, explicit iv will not be encrypted. 309 | // This call cannot fail. 310 | self.key.encrypt_in_place( 311 | &nonce.0, 312 | &auth_data, 313 | &mut payload.as_mut() 314 | [GCM_EXPLICIT_NONCE_LENGTH..(msg.payload.len() + GCM_EXPLICIT_NONCE_LENGTH)], 315 | // adding the gcm_explicit_nonce_length to account for shifting 8 bytes 316 | &mut tag, 317 | ); 318 | payload.extend_from_slice(&tag); 319 | Ok(OutboundOpaqueMessage::new(msg.typ, msg.version, payload)) 320 | } 321 | 322 | fn encrypted_payload_len(&self, payload_len: usize) -> usize { 323 | payload_len + GCM_EXPLICIT_NONCE_LENGTH + GCM_TAG_LENGTH 324 | } 325 | } 326 | 327 | /// [`MessageDecrypter`] for Gcm 1.2 328 | /// the `payload` field that comes from the [`InboundOpaqueMessage`] is structured to include the explicit iv which is 8 bytes, 329 | /// the message which is an arbitrary length, and the tag which is 16 bytes. 330 | /// ex : [1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 5, 6, 7, 8, 9, 10, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ,13, 14, 15, 16] 331 | /// ^ ^ ^ ^ ^ ^ 332 | /// Explicit Iv (8 bytes) Message (N bytes) Tag (16 bytes) 333 | impl MessageDecrypter for Gcm12Decrypt { 334 | fn decrypt<'a>( 335 | &mut self, 336 | mut msg: InboundOpaqueMessage<'a>, 337 | seq: u64, 338 | ) -> Result, Error> { 339 | let payload = &mut msg.payload; // payload is already mutable since it is a reference to [`BorrowedPayload`] 340 | let payload_len = payload.len(); // This length includes the explicit iv, message and tag 341 | if payload_len < GCM_TAG_LENGTH + GCM_EXPLICIT_NONCE_LENGTH { 342 | return Err(Error::DecryptError); 343 | } 344 | 345 | // Construct nonce, the first 4 bytes of nonce will be the the implicit iv, the last 8 bytes will be the explicit iv. The explicit 346 | // iv is taken from the first 8 bytes of the payload. The explicit iv will not be encrypted. 347 | let mut nonce = [0u8; GCM_FULL_NONCE_LENGTH]; 348 | nonce[..GCM_IMPLICIT_NONCE_LENGTH].copy_from_slice(&self.iv); 349 | nonce[GCM_IMPLICIT_NONCE_LENGTH..].copy_from_slice(&payload[..GCM_EXPLICIT_NONCE_LENGTH]); 350 | 351 | // Set up needed parameters for Gcm decrypt 352 | let mut tag = [0u8; GCM_TAG_LENGTH]; 353 | tag.copy_from_slice(&payload[payload_len - GCM_TAG_LENGTH..]); 354 | let auth_data = make_tls12_aad( 355 | seq, 356 | msg.typ, 357 | msg.version, 358 | payload_len - GCM_TAG_LENGTH - GCM_EXPLICIT_NONCE_LENGTH, 359 | ); 360 | 361 | // Decrypting the payload in place, only the message from the payload will be decrypted, explicit iv will not be decrypted. 362 | match self.key.decrypt_in_place( 363 | &nonce, 364 | &auth_data, 365 | &mut payload[GCM_EXPLICIT_NONCE_LENGTH..payload_len - GCM_TAG_LENGTH], 366 | &tag, 367 | ) { 368 | Ok(()) => { 369 | // copy bytes from the [GCM_EXPLICIT_NONCE_LENTH..] to end of array, starting destination is 0 index. 370 | // This overwrites the the first 8 bytes that were previously the explicit nonce. 371 | payload.copy_within(GCM_EXPLICIT_NONCE_LENGTH..(payload_len - GCM_TAG_LENGTH), 0); 372 | 373 | // Remove the last 8 bytes since they are now garbage. 374 | // This work around is needed because rustls wraps the payload ( which is just an array ) behind 375 | // a BorrowedPayload type, which only exposes truncate as a field, and hides many methods like new() pop() etc. 376 | payload.truncate(payload_len - (GCM_EXPLICIT_NONCE_LENGTH + GCM_TAG_LENGTH)); 377 | 378 | Ok(msg.into_plain_message()) 379 | } 380 | Err(symcrypt_error) => { 381 | let custom_error_message = format!( 382 | "SymCryptError: {}", 383 | symcrypt_error // Using general error to propagate the SymCrypt error back to the caller 384 | ); 385 | Err(Error::General(custom_error_message)) 386 | } 387 | } 388 | } 389 | } 390 | -------------------------------------------------------------------------------- /rustls-symcrypt/src/tls13.rs: -------------------------------------------------------------------------------- 1 | //! GCM and ChaCha functions for TLS 1.3. For further documentation please refer to rust_symcrypt::gcm and symcrypt::chacha 2 | use crate::cipher_suites::AesGcm; 3 | use rustls::crypto::cipher::{ 4 | make_tls13_aad, AeadKey, InboundOpaqueMessage, InboundPlainMessage, Iv, MessageDecrypter, 5 | MessageEncrypter, Nonce, OutboundOpaqueMessage, OutboundPlainMessage, PrefixedPayload, 6 | Tls13AeadAlgorithm, UnsupportedOperationError, 7 | }; 8 | use rustls::ConnectionTrafficSecrets; 9 | use symcrypt::cipher::BlockCipherType; 10 | 11 | use symcrypt::gcm::GcmExpandedKey; 12 | const GCM_TAG_LENGTH: usize = 16; 13 | 14 | #[cfg(feature = "chacha")] 15 | use symcrypt::chacha::{chacha20_poly1305_decrypt_in_place, chacha20_poly1305_encrypt_in_place}; 16 | #[cfg(feature = "chacha")] 17 | const CHACHA_TAG_LENGTH: usize = 16; 18 | #[cfg(feature = "chacha")] 19 | const CHACHA_KEY_LENGTH: usize = 32; 20 | 21 | /// ChaCha for TLS 1.3. 22 | /// ChaCha functionality will be disabled by default, in order to enable ChaCha functionality, 23 | /// user must pass the "chacha" feature via `Cargo.toml` 24 | /// 25 | /// [`Tls13ChaCha`] impls [`Tls13AeadAlgorithm`] 26 | #[cfg(feature = "chacha")] 27 | pub struct Tls13ChaCha; 28 | 29 | /// [`Tls13ChaCha20Poly1305`] impls [`MessageEncrypter`] and [`MessageDecrypter`]. 30 | /// `key` is a ChaCha key and must be 32 bytes. 31 | /// `iv` is an initialization vector that is needed to create the unique nonce. 32 | #[cfg(feature = "chacha")] 33 | pub struct Tls13ChaCha20Poly1305 { 34 | key: [u8; CHACHA_KEY_LENGTH], 35 | iv: Iv, 36 | } 37 | 38 | #[cfg(feature = "chacha")] 39 | impl Tls13AeadAlgorithm for Tls13ChaCha { 40 | fn encrypter(&self, key: AeadKey, iv: Iv) -> Box { 41 | assert_eq!(key.as_ref().len(), CHACHA_KEY_LENGTH); // ChaCha key length must be 32 bytes. 42 | let mut chacha_key = [0u8; CHACHA_KEY_LENGTH]; 43 | chacha_key[..CHACHA_KEY_LENGTH].copy_from_slice(key.as_ref()); 44 | 45 | Box::new(Tls13ChaCha20Poly1305 { 46 | key: chacha_key, 47 | iv, 48 | }) 49 | } 50 | 51 | fn decrypter(&self, key: AeadKey, iv: Iv) -> Box { 52 | assert_eq!(key.as_ref().len(), CHACHA_KEY_LENGTH); // ChaCha key length must be 32 bytes. 53 | let mut chacha_key = [0u8; CHACHA_KEY_LENGTH]; 54 | chacha_key[..CHACHA_KEY_LENGTH].copy_from_slice(key.as_ref()); 55 | 56 | Box::new(Tls13ChaCha20Poly1305 { 57 | key: chacha_key, 58 | iv, 59 | }) 60 | } 61 | 62 | fn key_len(&self) -> usize { 63 | CHACHA_KEY_LENGTH // ChaCha key must be 32 bytes. 64 | } 65 | 66 | fn extract_keys( 67 | &self, 68 | key: AeadKey, 69 | iv: Iv, 70 | ) -> Result { 71 | Ok(ConnectionTrafficSecrets::Chacha20Poly1305 { key, iv }) 72 | } 73 | } 74 | 75 | /// [`MessageEncrypter`] for ChaCha 1.3 76 | /// the `payload` field that comes from the [`OutboundPlainMessage`] is structured to include the message which is an arbitrary length, 77 | /// an encoding type that is 1 byte and then finally the tag which is 16 bytes. 78 | /// ex : [1, 2, 3, 5, 6, 7, 8, 9, 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ,13, 14, 15, 16] 79 | /// ^ ^ ^ ^ ^ 80 | /// Message (N bytes) Encoding (1 byte) Tag (16 bytes) 81 | #[cfg(feature = "chacha")] 82 | impl MessageEncrypter for Tls13ChaCha20Poly1305 { 83 | fn encrypt( 84 | &mut self, 85 | msg: OutboundPlainMessage, 86 | seq: u64, 87 | ) -> Result { 88 | // Adding the size of message, the tag and encoding type to the capacity of payload vector. 89 | // Must create the payload this way. There is a header of 5 bytes at the front of the payload. 90 | // Using overridden with_capacity() will return a new payload with the header of 5 bytes set to 0 and accounted for. 91 | let total_len = self.encrypted_payload_len(msg.payload.len()); 92 | let mut payload = PrefixedPayload::with_capacity(total_len); 93 | 94 | // payload will be appended via extend_from_chunks() and extend_from_slice() starting after the 5 byte buffer. 95 | payload.extend_from_chunks(&msg.payload); 96 | payload.extend_from_slice(&msg.typ.to_array()); 97 | 98 | // Set up needed parameters for ChaCha encrypt. Must use total length of message, not including the length of 99 | // the 5 byte header, since that is not included in the message to be encrypted. 100 | let nonce = Nonce::new(&self.iv, seq); 101 | let auth_data = make_tls13_aad(total_len); 102 | let mut tag = [0u8; CHACHA_TAG_LENGTH]; 103 | 104 | // Encrypting the payload in place. +1 is added to account for encoding type that must also be encrypted. 105 | // payload.as_mut() returns the slice that is indexed by 5 bytes to avoid encrypting the header. 106 | match chacha20_poly1305_encrypt_in_place( 107 | &self.key, 108 | &nonce.0, 109 | &auth_data, 110 | &mut payload.as_mut()[..msg.payload.len() + 1], 111 | &mut tag, 112 | ) { 113 | Ok(_) => { 114 | payload.extend_from_slice(&tag); // Add tag to the end of the payload. 115 | Ok(OutboundOpaqueMessage::new( 116 | // Note: all TLS 1.3 application data records use TLSv1_2 (0x0303) as the legacy record 117 | // protocol version, see https://www.rfc-editor.org/rfc/rfc8446#section-5.1 118 | rustls::ContentType::ApplicationData, 119 | rustls::ProtocolVersion::TLSv1_2, 120 | payload, 121 | )) 122 | } 123 | Err(symcrypt_error) => { 124 | let custom_error_message = format!( 125 | "SymCryptError: {}", 126 | symcrypt_error // Using general error to propagate the SymCrypt error back to the caller 127 | ); 128 | Err(rustls::Error::General(custom_error_message)) 129 | } 130 | } 131 | } 132 | 133 | fn encrypted_payload_len(&self, payload_len: usize) -> usize { 134 | payload_len + 1 + CHACHA_TAG_LENGTH 135 | } 136 | } 137 | 138 | /// [`MessageDecrypter`] for ChaCha 1.3 139 | /// the `payload` field that comes from the [`InboundOpaqueMessage`] is structured to include the message which is an arbitrary length, 140 | /// an encoding type that is 1 byte and then finally the tag which is 16 bytes. 141 | /// ex : [1, 2, 3, 5, 6, 7, 8, 9, 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ,13, 14, 15, 16] 142 | /// ^ ^ ^ ^ ^ 143 | /// Message (N bytes) Encoding (1 byte) Tag (16 bytes) 144 | #[cfg(feature = "chacha")] 145 | impl MessageDecrypter for Tls13ChaCha20Poly1305 { 146 | fn decrypt<'a>( 147 | &mut self, 148 | mut msg: InboundOpaqueMessage<'a>, 149 | seq: u64, 150 | ) -> Result, rustls::Error> { 151 | let payload = &mut msg.payload; // This is mutable since it is a reference of BorrowedPayload 152 | let payload_len = payload.len(); // This length includes the message, encoding, and tag. 153 | 154 | // Ensure that the length is over 16 bytes or there is a decryption error. 155 | if payload_len < CHACHA_TAG_LENGTH { 156 | return Err(rustls::Error::DecryptError); 157 | } 158 | let message_length = payload_len - CHACHA_TAG_LENGTH; // getting message length, this includes the message length and the encoding type. 159 | 160 | // Set up needed parameters for ChaCha decrypt 161 | let nonce = Nonce::new(&self.iv, seq); 162 | let auth_data = make_tls13_aad(payload_len); // The total message including tag and encoding byte must be used for auth_data. 163 | let mut tag = [0u8; GCM_TAG_LENGTH]; 164 | tag.copy_from_slice(&payload[message_length..]); // get tag 165 | 166 | // Decrypting the payload in place, there is no +1 here since message_length accounts for the extra byte for encoding type. 167 | match chacha20_poly1305_decrypt_in_place( 168 | &self.key, 169 | &nonce.0, 170 | &auth_data, 171 | &mut payload[..message_length], 172 | &tag, 173 | ) { 174 | Ok(_) => { 175 | payload.truncate(message_length); 176 | msg.into_tls13_unpadded_message() // Removes the optional padding of zero bytes. 177 | } 178 | Err(symcrypt_error) => { 179 | let custom_error_message = format!( 180 | "SymCryptError: {}", 181 | symcrypt_error // Using general error to propagate the SymCrypt error back to the caller 182 | ); 183 | Err(rustls::Error::General(custom_error_message)) 184 | } 185 | } 186 | } 187 | } 188 | 189 | /// GCM for TLS 1.3 190 | /// 191 | /// [`Tls13Gcm`] impls [`Tls13AeadAlgorithm`]. 192 | /// 193 | /// `algo_type` represents either `Aes128Gcm` or `Aes256Gcm` which corresponds to a 16 and 32 byte key respectively. 194 | pub struct Tls13Gcm { 195 | pub(crate) algo_type: AesGcm, 196 | } 197 | 198 | /// [`Tls13GcmState`] impls [`MessageEncrypter`] and [`MessageDecrypter`] 199 | /// 200 | /// `key` is a rust-symcrypt::GcmExpandedKey that has expands the provided key 201 | /// `iv` is an initialization vector that is needed to create the unique nonce. 202 | pub struct Tls13GcmState { 203 | key: GcmExpandedKey, 204 | iv: Iv, 205 | } 206 | 207 | impl Tls13AeadAlgorithm for Tls13Gcm { 208 | fn encrypter(&self, key: AeadKey, iv: Iv) -> Box { 209 | // Unwrapping here, since rustls does not expect this to fail. 210 | // In the scenarios that GcmExpandKey would fail should result in a panic, ie: Not enough memory. 211 | Box::new(Tls13GcmState { 212 | key: GcmExpandedKey::new(key.as_ref(), BlockCipherType::AesBlock).unwrap(), 213 | iv, 214 | }) 215 | } 216 | 217 | fn decrypter(&self, key: AeadKey, iv: Iv) -> Box { 218 | // Unwrapping here, since rustls does not expect this to fail. 219 | // In the scenarios that GcmExpandKey would fail should result in a panic, ie: Not enough memory. 220 | Box::new(Tls13GcmState { 221 | key: GcmExpandedKey::new(key.as_ref(), BlockCipherType::AesBlock).unwrap(), 222 | iv, 223 | }) 224 | } 225 | 226 | fn key_len(&self) -> usize { 227 | self.algo_type.key_size() 228 | } 229 | 230 | fn extract_keys( 231 | &self, 232 | key: AeadKey, 233 | iv: Iv, 234 | ) -> Result { 235 | match self.key_len() { 236 | 16 => Ok(ConnectionTrafficSecrets::Aes128Gcm { key, iv }), 237 | 32 => Ok(ConnectionTrafficSecrets::Aes256Gcm { key, iv }), 238 | _ => Err(UnsupportedOperationError), 239 | } 240 | } 241 | } 242 | 243 | /// [`MessageEncrypter`] for GCM 1.3 244 | /// the `payload` field that comes from the [`OutboundPlainMessage`] is structured to include the message which is an arbitrary length, 245 | /// an encoding type that is 1 byte and then finally the tag which is 16 bytes. 246 | /// ex : [1, 2, 3, 5, 6, 7, 8, 9, 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ,13, 14, 15, 16] 247 | /// ^ ^ ^ ^ ^ 248 | /// Message (N bytes) Encoding (1 byte) Tag (16 bytes) 249 | impl MessageEncrypter for Tls13GcmState { 250 | fn encrypt( 251 | &mut self, 252 | msg: OutboundPlainMessage, 253 | seq: u64, 254 | ) -> Result { 255 | // Adding the size of message, the tag and encoding type to the capacity of payload vector. 256 | // Must create the payload this way. There is a header of 5 bytes at the front of the payload. 257 | // Using overridden with_capacity() will return a new payload with the header of 5 bytes set to 0 and accounted for. 258 | let total_len = self.encrypted_payload_len(msg.payload.len()); 259 | let mut payload = PrefixedPayload::with_capacity(total_len); 260 | 261 | // payload will be appended do via extend_from_chunks() and extend_from_slice() starting after the 5 byte buffer. 262 | payload.extend_from_chunks(&msg.payload); 263 | payload.extend_from_slice(&msg.typ.to_array()); 264 | 265 | // Set up needed parameters for ChaCha encrypt, Must use total length of message, not including the length of 266 | // the 5 byte header, since that is not included in the message to be encrypted. 267 | let nonce = Nonce::new(&self.iv, seq); 268 | let auth_data = make_tls13_aad(total_len); 269 | let mut tag = [0u8; GCM_TAG_LENGTH]; 270 | 271 | // Encrypting the payload in place. +1 is added to account for encoding type that must also be encrypted. 272 | // payload.as_mut() returns the slice that is indexed by 5 bytes to avoid encrypting the header. This call cannot fail. 273 | self.key.encrypt_in_place( 274 | &nonce.0, 275 | &auth_data, 276 | &mut payload.as_mut()[..msg.payload.len() + 1], 277 | &mut tag, 278 | ); 279 | 280 | payload.extend_from_slice(&tag); 281 | Ok(OutboundOpaqueMessage::new( 282 | // Note: all TLS 1.3 application data records use TLSv1_2 (0x0303) as the legacy record 283 | // protocol version, see https://www.rfc-editor.org/rfc/rfc8446#section-5.1 284 | rustls::ContentType::ApplicationData, 285 | rustls::ProtocolVersion::TLSv1_2, 286 | payload, 287 | )) 288 | } 289 | 290 | fn encrypted_payload_len(&self, payload_len: usize) -> usize { 291 | payload_len + 1 + GCM_TAG_LENGTH 292 | } 293 | } 294 | 295 | /// [`MessageDecrypter`] for GCM 1.3 296 | /// the `payload` field that comes from the [`InboundOpaqueMessage`] is structured to include the message which is an arbitrary length, 297 | /// an encoding type that is 1 byte. After the encoding byte there can be a padding of 0 or more zero bytes, and finally the tag which is 16 bytes. 298 | /// ex : [1, 2, 3, 5, 6, 7, 8, 9, 10, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ,13, 14, 15, 16] 299 | /// ^ ^ ^ ^ ^ 300 | /// Message (N bytes) Encoding (1 byte) Tag (16 bytes) 301 | impl MessageDecrypter for Tls13GcmState { 302 | fn decrypt<'a>( 303 | &mut self, 304 | mut msg: InboundOpaqueMessage<'a>, 305 | seq: u64, 306 | ) -> Result, rustls::Error> { 307 | let payload = &mut msg.payload; // payload is already mutable since it is a reference to BorrowedPayload 308 | let payload_len = payload.len(); // This length includes the message, encoding, and tag. 309 | if payload_len < GCM_TAG_LENGTH { 310 | return Err(rustls::Error::DecryptError); 311 | } 312 | let message_length = payload_len - GCM_TAG_LENGTH; // This includes the message length and the encoding type. 313 | 314 | // Set up needed parameters for GCM decrypt. 315 | let nonce = Nonce::new(&self.iv, seq); 316 | let auth_data = make_tls13_aad(payload_len); // The whole message, including encoding type and tag should be used. 317 | let mut tag = [0u8; GCM_TAG_LENGTH]; 318 | tag.copy_from_slice(&payload[message_length..]); // get tag 319 | 320 | // Decrypting the payload in place, there is no +1 here since message_length accounts for the extra byte for encoding type. 321 | match self 322 | .key 323 | .decrypt_in_place(&nonce.0, &auth_data, &mut payload[..message_length], &tag) 324 | { 325 | Ok(()) => { 326 | payload.truncate(message_length); 327 | msg.into_tls13_unpadded_message() // This removes the optional padding of zero bytes. 328 | } 329 | Err(symcrypt_error) => { 330 | let custom_error_message = format!( 331 | "SymCryptError: {}", 332 | symcrypt_error // Using general error to propagate the SymCrypt error back to the caller 333 | ); 334 | Err(rustls::Error::General(custom_error_message)) 335 | } 336 | } 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /rustls-symcrypt/src/verify.rs: -------------------------------------------------------------------------------- 1 | use pkcs1::RsaPublicKey as AsnRsaPublicKey; 2 | use rustls::crypto::WebPkiSupportedAlgorithms; 3 | use rustls::pki_types::{AlgorithmIdentifier, InvalidSignature, SignatureVerificationAlgorithm}; 4 | use rustls::SignatureScheme; 5 | use rustls_pki_types::alg_id; 6 | use symcrypt::ecc::{CurveType, EcKey, EcKeyUsage}; 7 | use symcrypt::hash::{sha256, sha384, sha512, HashAlgorithm}; 8 | use symcrypt::rsa::{RsaKey, RsaKeyUsage}; 9 | 10 | /// Rsa signatures from the wire will come in the following ASN1 format: 11 | /// RSAPublicKey ::= SEQUENCE { 12 | /// modulus INTEGER, -- n 13 | /// publicExponent INTEGER -- e 14 | /// } 15 | fn extract_rsa_public_key(pub_key: &[u8]) -> Result<(Vec, Vec), InvalidSignature> { 16 | let key = AsnRsaPublicKey::try_from(pub_key).map_err(|_| InvalidSignature)?; 17 | 18 | let modulus = key.modulus.as_bytes().to_vec(); 19 | let exponent = key.public_exponent.as_bytes().to_vec(); 20 | 21 | Ok((modulus, exponent)) 22 | } 23 | 24 | // NistP256, NistP384, and NistP521 have a prepended Legacy byte that needs to be removed. 25 | // This call will return InvalidSignature if the public key is P256, P384, or P21 and does not have the legacy byte. 26 | fn extract_ecc_public_key( 27 | pub_key: &[u8], 28 | curve_type: CurveType, 29 | ) -> Result, InvalidSignature> { 30 | match curve_type { 31 | CurveType::NistP256 | CurveType::NistP384 | CurveType::NistP521 => { 32 | if pub_key.starts_with(&[0x04]) { 33 | Ok(pub_key[1..].to_vec()) 34 | } else { 35 | Err(InvalidSignature) // Propagate InvalidSignature error back to caller 36 | } 37 | } 38 | CurveType::Curve25519 => { 39 | // Curve25519 is not supported for ECC signatures 40 | Err(InvalidSignature) 41 | } 42 | } 43 | } 44 | 45 | /// Ecc signatures from the wire will come in the following ASN1 format: 46 | /// ECDSASignature ::= SEQUENCE { 47 | /// r INTEGER, 48 | /// s INTEGER 49 | /// } 50 | /// SymCrypt expects a concatenated r+s with leading padding and leading 0's for both r and s to be removed 51 | fn extract_ecc_signature(signature: &[u8], curve: CurveType) -> Result, InvalidSignature> { 52 | // We use pkcs1::RsaPublicKey because the underlying ASN1 format between an RSA public key and 53 | // an ECC signature is the same. 54 | let signature = AsnRsaPublicKey::try_from(signature).map_err(|_| InvalidSignature)?; 55 | 56 | let component_length = curve.get_size() as usize; 57 | 58 | // Leading 0's are stripped when using as_bytes() 59 | // https://docs.rs/pkcs1/0.7.5/pkcs1/struct.UintRef.html 60 | let r = signature.modulus.as_bytes(); // cast name from `modulus` to `r` 61 | let s = signature.public_exponent.as_bytes(); // cast name from `public_exponent` to `s` 62 | 63 | // SymCrypt takes in a concatenated r+s. with the individual r and s components having a size of curve / 2. 64 | // If for example the curve is P256, the r and s components must individually 32 bytes long. 65 | // as_bytes() removes all leading 0s, which covers the case if r or s is 33 bytes long for example. 66 | // as_bytes() may remove ALL 0's even if one was randomly generated as part of the signature. 67 | // So after we remove the 0's we prepend 0's that were removed until the lengths are 32. 68 | // Majority of the time this will not happen but this is to cover corner cases where it does. 69 | 70 | // Ensure r and s are the correct length (pad with leading zeros if necessary). 71 | let mut r_padded = Vec::with_capacity(component_length); 72 | let mut s_padded = Vec::with_capacity(component_length); 73 | 74 | // In the scenario where there are too many bytes after the leading 0's are removed, return an error. 75 | if r.len() > component_length || s.len() > component_length { 76 | return Err(InvalidSignature); 77 | } 78 | 79 | // Prepend zeros if r is smaller than component_length. 80 | if r.len() < component_length { 81 | r_padded.extend(std::iter::repeat(0).take(component_length - r.len())); 82 | } 83 | r_padded.extend_from_slice(r); // Add the actual r bytes. 84 | 85 | // Prepend zeros if s is smaller than component_length. 86 | if s.len() < component_length { 87 | s_padded.extend(std::iter::repeat(0).take(component_length - s.len())); 88 | } 89 | s_padded.extend_from_slice(s); // Add the actual s bytes. 90 | 91 | // Concatenate the padded r and s components. 92 | Ok([r_padded, s_padded].concat()) 93 | } 94 | 95 | pub static SUPPORTED_SIG_ALGS: WebPkiSupportedAlgorithms = WebPkiSupportedAlgorithms { 96 | all: &[ 97 | ECDSA_P256_SHA256, 98 | ECDSA_P256_SHA384, 99 | ECDSA_P384_SHA256, 100 | ECDSA_P384_SHA384, 101 | ECDSA_P521_SHA256, 102 | ECDSA_P521_SHA384, 103 | ECDSA_P521_SHA512, 104 | RSA_PKCS1_SHA256, 105 | RSA_PKCS1_SHA384, 106 | RSA_PKCS1_SHA512, 107 | RSA_PSS_SHA256, 108 | RSA_PSS_SHA384, 109 | RSA_PSS_SHA512, 110 | ], 111 | mapping: &[ 112 | // Note: for TLS1.2 the curve is not fixed by SignatureScheme. For TLS1.3 it is. 113 | ( 114 | SignatureScheme::ECDSA_NISTP384_SHA384, 115 | &[ECDSA_P384_SHA384, ECDSA_P256_SHA384, ECDSA_P521_SHA384], 116 | ), 117 | ( 118 | SignatureScheme::ECDSA_NISTP256_SHA256, 119 | &[ECDSA_P256_SHA256, ECDSA_P384_SHA256, ECDSA_P521_SHA256], 120 | ), 121 | (SignatureScheme::ECDSA_NISTP521_SHA512, &[ECDSA_P521_SHA512]), 122 | (SignatureScheme::RSA_PSS_SHA512, &[RSA_PSS_SHA512]), 123 | (SignatureScheme::RSA_PSS_SHA384, &[RSA_PSS_SHA384]), 124 | (SignatureScheme::RSA_PSS_SHA256, &[RSA_PSS_SHA256]), 125 | (SignatureScheme::RSA_PKCS1_SHA512, &[RSA_PKCS1_SHA512]), 126 | (SignatureScheme::RSA_PKCS1_SHA384, &[RSA_PKCS1_SHA384]), 127 | (SignatureScheme::RSA_PKCS1_SHA256, &[RSA_PKCS1_SHA256]), 128 | ], 129 | }; 130 | 131 | fn hash_sha256(data: &[u8]) -> Vec { 132 | sha256(data).to_vec() 133 | } 134 | 135 | fn hash_sha384(data: &[u8]) -> Vec { 136 | sha384(data).to_vec() 137 | } 138 | 139 | fn hash_sha512(data: &[u8]) -> Vec { 140 | sha512(data).to_vec() 141 | } 142 | 143 | #[derive(Debug)] 144 | enum KeyType { 145 | RsaPkcs1(RsaPkcs1), 146 | RsaPss(RsaPss), 147 | Ecc(Ecc), 148 | } 149 | 150 | #[derive(Debug)] 151 | struct RsaPkcs1 { 152 | hash_algorithm: HashAlgorithm, 153 | } 154 | 155 | #[derive(Debug)] 156 | struct RsaPss { 157 | hash_algorithm: HashAlgorithm, 158 | salt_length: u32, 159 | } 160 | 161 | #[derive(Debug)] 162 | struct Ecc { 163 | curve: CurveType, 164 | } 165 | 166 | /// ECDSA signatures using the P-256 curve and SHA-256. 167 | pub static ECDSA_P256_SHA256: &dyn SignatureVerificationAlgorithm = &SymCryptAlgorithm { 168 | public_key_alg_id: alg_id::ECDSA_P256, 169 | signature_alg_id: alg_id::ECDSA_SHA256, 170 | hasher: hash_sha256, 171 | key_type: KeyType::Ecc(Ecc { 172 | curve: CurveType::NistP256, 173 | }), 174 | }; 175 | 176 | /// ECDSA signatures using the P-256 curve and SHA-384. 177 | pub static ECDSA_P256_SHA384: &dyn SignatureVerificationAlgorithm = &SymCryptAlgorithm { 178 | public_key_alg_id: alg_id::ECDSA_P256, 179 | signature_alg_id: alg_id::ECDSA_SHA384, 180 | hasher: hash_sha384, 181 | key_type: KeyType::Ecc(Ecc { 182 | curve: CurveType::NistP256, 183 | }), 184 | }; 185 | 186 | /// ECDSA signatures using the P-384 curve and SHA-256. 187 | pub static ECDSA_P384_SHA256: &dyn SignatureVerificationAlgorithm = &SymCryptAlgorithm { 188 | public_key_alg_id: alg_id::ECDSA_P384, 189 | signature_alg_id: alg_id::ECDSA_SHA256, 190 | hasher: hash_sha256, 191 | key_type: KeyType::Ecc(Ecc { 192 | curve: CurveType::NistP384, 193 | }), 194 | }; 195 | 196 | /// ECDSA signatures using the P-384 curve and SHA-384. 197 | pub static ECDSA_P384_SHA384: &dyn SignatureVerificationAlgorithm = &SymCryptAlgorithm { 198 | public_key_alg_id: alg_id::ECDSA_P384, 199 | signature_alg_id: alg_id::ECDSA_SHA384, 200 | hasher: hash_sha384, 201 | key_type: KeyType::Ecc(Ecc { 202 | curve: CurveType::NistP384, 203 | }), 204 | }; 205 | 206 | /// ECDSA signatures using the P-521 curve and SHA-256. 207 | pub static ECDSA_P521_SHA256: &dyn SignatureVerificationAlgorithm = &SymCryptAlgorithm { 208 | public_key_alg_id: alg_id::ECDSA_P521, 209 | signature_alg_id: alg_id::ECDSA_SHA256, 210 | hasher: hash_sha256, 211 | key_type: KeyType::Ecc(Ecc { 212 | curve: CurveType::NistP521, 213 | }), 214 | }; 215 | 216 | /// ECDSA signatures using the P-521 curve and SHA-384. 217 | pub static ECDSA_P521_SHA384: &dyn SignatureVerificationAlgorithm = &SymCryptAlgorithm { 218 | public_key_alg_id: alg_id::ECDSA_P521, 219 | signature_alg_id: alg_id::ECDSA_SHA384, 220 | hasher: hash_sha384, 221 | key_type: KeyType::Ecc(Ecc { 222 | curve: CurveType::NistP521, 223 | }), 224 | }; 225 | 226 | /// ECDSA signatures using the P-521 curve and SHA-512. 227 | pub static ECDSA_P521_SHA512: &dyn SignatureVerificationAlgorithm = &SymCryptAlgorithm { 228 | public_key_alg_id: alg_id::ECDSA_P521, 229 | signature_alg_id: alg_id::ECDSA_SHA512, 230 | hasher: hash_sha512, 231 | key_type: KeyType::Ecc(Ecc { 232 | curve: CurveType::NistP521, 233 | }), 234 | }; 235 | 236 | /// RSA PKCS1 signatures using SHA-256. 237 | pub static RSA_PKCS1_SHA256: &dyn SignatureVerificationAlgorithm = &SymCryptAlgorithm { 238 | public_key_alg_id: alg_id::RSA_ENCRYPTION, 239 | signature_alg_id: alg_id::RSA_PKCS1_SHA256, 240 | hasher: hash_sha256, 241 | key_type: KeyType::RsaPkcs1(RsaPkcs1 { 242 | hash_algorithm: HashAlgorithm::Sha256, 243 | }), 244 | }; 245 | 246 | /// RSA PKCS1 signatures using SHA-384. 247 | pub static RSA_PKCS1_SHA384: &dyn SignatureVerificationAlgorithm = &SymCryptAlgorithm { 248 | public_key_alg_id: alg_id::RSA_ENCRYPTION, 249 | signature_alg_id: alg_id::RSA_PKCS1_SHA384, 250 | hasher: hash_sha384, 251 | key_type: KeyType::RsaPkcs1(RsaPkcs1 { 252 | hash_algorithm: HashAlgorithm::Sha384, 253 | }), 254 | }; 255 | 256 | /// RSA PKCS1 signatures using SHA-512. 257 | pub static RSA_PKCS1_SHA512: &dyn SignatureVerificationAlgorithm = &SymCryptAlgorithm { 258 | public_key_alg_id: alg_id::RSA_ENCRYPTION, 259 | signature_alg_id: alg_id::RSA_PKCS1_SHA512, 260 | hasher: hash_sha512, 261 | key_type: KeyType::RsaPkcs1(RsaPkcs1 { 262 | hash_algorithm: HashAlgorithm::Sha512, 263 | }), 264 | }; 265 | 266 | /// RSA PSS signatures using SHA-256. 267 | pub static RSA_PSS_SHA256: &dyn SignatureVerificationAlgorithm = &SymCryptAlgorithm { 268 | public_key_alg_id: alg_id::RSA_ENCRYPTION, 269 | signature_alg_id: alg_id::RSA_PSS_SHA256, 270 | hasher: hash_sha256, 271 | key_type: KeyType::RsaPss(RsaPss { 272 | hash_algorithm: HashAlgorithm::Sha256, 273 | salt_length: 32, 274 | }), 275 | }; 276 | 277 | /// RSA PSS signatures using SHA-384. 278 | pub static RSA_PSS_SHA384: &dyn SignatureVerificationAlgorithm = &SymCryptAlgorithm { 279 | public_key_alg_id: alg_id::RSA_ENCRYPTION, 280 | signature_alg_id: alg_id::RSA_PSS_SHA384, 281 | hasher: hash_sha384, 282 | key_type: KeyType::RsaPss(RsaPss { 283 | hash_algorithm: HashAlgorithm::Sha384, 284 | salt_length: 48, 285 | }), 286 | }; 287 | 288 | /// RSA PSS signatures using SHA-256. 289 | pub static RSA_PSS_SHA512: &dyn SignatureVerificationAlgorithm = &SymCryptAlgorithm { 290 | public_key_alg_id: alg_id::RSA_ENCRYPTION, 291 | signature_alg_id: alg_id::RSA_PSS_SHA512, 292 | hasher: hash_sha512, 293 | key_type: KeyType::RsaPss(RsaPss { 294 | hash_algorithm: HashAlgorithm::Sha512, 295 | salt_length: 64, 296 | }), 297 | }; 298 | 299 | #[derive(Debug)] 300 | /// SymCryptAlgorithm holds the following fields 301 | /// `public_key_alg_id`: The public key algorithm identifier, this is tied to the 302 | /// rustls::pki_types::AlgorithmIdentifier and is used to match the public key 303 | /// 304 | /// `signature_alg_id`: The signature algorithm identifier, this is tied to the 305 | /// rustls::pki_types::AlgorithmIdentifier and is used to match the signature 306 | /// 307 | /// `hasher`: A function that takes a slice of bytes and returns a vector of bytes 308 | /// that have been hashed 309 | /// 310 | /// `key_type`: A enum that holds the type of key that is being used, this is used to store 311 | /// the info needed to create a public key on the SymCrypt side. 312 | /// 313 | /// Each SymCryptAlgorithm has already been matched for the necessary fields for creating 314 | /// public keys on the SymCrypt side through the fields in this struct. 315 | struct SymCryptAlgorithm { 316 | public_key_alg_id: AlgorithmIdentifier, 317 | signature_alg_id: AlgorithmIdentifier, 318 | hasher: fn(&[u8]) -> Vec, 319 | key_type: KeyType, 320 | } 321 | 322 | /// [`SignatureVerificationAlgorithm`] for SymCryptAlgorithm. 323 | /// Creates either a RSA or ECC key depending on the SymCryptAlgorithm then does a verify based on the 324 | /// provided `public_key` bytes, `message` bytes and the `signature` bytes. 325 | impl SignatureVerificationAlgorithm for SymCryptAlgorithm { 326 | fn verify_signature( 327 | &self, 328 | public_key: &[u8], 329 | message: &[u8], 330 | signature: &[u8], 331 | ) -> Result<(), InvalidSignature> { 332 | match &self.key_type { 333 | KeyType::Ecc(ecc) => { 334 | // the pub_key passed will have a 0x04 legacy byte prepended to the key for the case of 335 | // NistP256, NistP384, NistP521. 336 | let key = extract_ecc_public_key(public_key, ecc.curve)?; 337 | 338 | // the signature will be in ASN.1 DER format, with separated `r` and `s` components, need to remove padding 339 | // and concatenate the two components. 340 | let sig = extract_ecc_signature(signature, ecc.curve)?; 341 | 342 | let ec_key = EcKey::set_public_key(ecc.curve, &key, EcKeyUsage::EcDsa) 343 | .map_err(|_| InvalidSignature)?; 344 | let hashed_message = (self.hasher)(message); 345 | ec_key 346 | .ecdsa_verify(&sig, &hashed_message) 347 | .map_err(|_| InvalidSignature) 348 | } 349 | KeyType::RsaPkcs1(rsa_pkcs1) => { 350 | // extract the modulus and exponent from the public key 351 | let (modulus, exponent) = extract_rsa_public_key(public_key)?; 352 | let rsa_key = RsaKey::set_public_key(&modulus, &exponent, RsaKeyUsage::Sign) 353 | .map_err(|_| InvalidSignature)?; 354 | let hashed_message = (self.hasher)(message); 355 | rsa_key 356 | .pkcs1_verify(&hashed_message, signature, rsa_pkcs1.hash_algorithm) 357 | .map_err(|_| InvalidSignature) 358 | } 359 | KeyType::RsaPss(rsa_pss) => { 360 | // extract the modulus and exponent from the public key 361 | let (modulus, exponent) = extract_rsa_public_key(public_key)?; 362 | let rsa_key = RsaKey::set_public_key(&modulus, &exponent, RsaKeyUsage::Sign) 363 | .map_err(|_| InvalidSignature)?; 364 | let hashed_message = (self.hasher)(message); 365 | rsa_key 366 | .pss_verify( 367 | &hashed_message, 368 | signature, 369 | rsa_pss.hash_algorithm, 370 | rsa_pss.salt_length as usize, 371 | ) 372 | .map_err(|_| InvalidSignature) 373 | } 374 | } 375 | } 376 | 377 | fn public_key_alg_id(&self) -> AlgorithmIdentifier { 378 | self.public_key_alg_id 379 | } 380 | 381 | fn signature_alg_id(&self) -> AlgorithmIdentifier { 382 | self.signature_alg_id 383 | } 384 | 385 | fn fips(&self) -> bool { 386 | true 387 | } 388 | } 389 | -------------------------------------------------------------------------------- /rustls-symcrypt/tests/certs/RootCA.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDLzCCAhegAwIBAgIUTANmSWnfVeu3082UBM4SZV8e9jAwDQYJKoZIhvcNAQEL 3 | BQAwJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD0V4YW1wbGUtUm9vdC1DQTAeFw0y 4 | NDAyMjExOTM4MjNaFw0yNjEyMTExOTM4MjNaMCcxCzAJBgNVBAYTAlVTMRgwFgYD 5 | VQQDDA9FeGFtcGxlLVJvb3QtQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK 6 | AoIBAQCnH5dA166tnxD0tJWiHphWsFK/oc73hHmYwOOxRa+HaR/0QBy7753jFw/o 7 | JueMVfTrNtHoaLUaVwX9W3ATnbpv27JvTNSmoJx2h+XRn7Z7p+yokRj9YjnEu+Qo 8 | PvusGkYj8fh3y5SE6JfEhQa/tOD9Ex2fl+6KS2Va6L5uY8IfbU9/h/SX7AIMKCwr 9 | FlGvPmiavlj/WFSgao6qY4kB4dNjUHmgKMk/hFtPfKu5oEZVBt383cUxWI/CkwnK 10 | WnaFC89vV3UBPTjrixIwNcUpMsksl93ky3SBSeIQSUL19hvs1yJnB8koOpdSmT4O 11 | wqIai3d3YQyRzuRo6rvsbQ/Kag9LAgMBAAGjUzBRMB0GA1UdDgQWBBS1AhH/ACQN 12 | g49LL4LnWAYXsyE4zDAfBgNVHSMEGDAWgBS1AhH/ACQNg49LL4LnWAYXsyE4zDAP 13 | BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCboiZMR8uQuDBMNGw+ 14 | br2N06tHJWXdc80dNoFyHFbgvxYD/thCIG+eOotkFZbM7wds/dGhwSCLmPX6PIVh 15 | //ZVZdD/Dk8B0J0jfbeuRM/kAQAvX+6aZCP3EQ2A6zYmquWSWHLazyWD3aY3sGPX 16 | 4vfggNVTCnROS1Rk0i0IzU08z4fDZDfOjDW06x6pUek+7sUR1oVMFMd2Q6CxWCp9 17 | 15J63r0JKmZDG8SOG1iIGTPj4GopDuNnQKA8qJGWc6WwPvJTinCPWMBJ2s73KOwY 18 | hKJQGZ22zULoReHXFY+Dt05vN9iig95OpHHNw+f3jPgwKlPBHhInpjEPNvKFJZ6K 19 | F2kb 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /rustls-symcrypt/tests/certs/RootCA.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCnH5dA166tnxD0 3 | tJWiHphWsFK/oc73hHmYwOOxRa+HaR/0QBy7753jFw/oJueMVfTrNtHoaLUaVwX9 4 | W3ATnbpv27JvTNSmoJx2h+XRn7Z7p+yokRj9YjnEu+QoPvusGkYj8fh3y5SE6JfE 5 | hQa/tOD9Ex2fl+6KS2Va6L5uY8IfbU9/h/SX7AIMKCwrFlGvPmiavlj/WFSgao6q 6 | Y4kB4dNjUHmgKMk/hFtPfKu5oEZVBt383cUxWI/CkwnKWnaFC89vV3UBPTjrixIw 7 | NcUpMsksl93ky3SBSeIQSUL19hvs1yJnB8koOpdSmT4OwqIai3d3YQyRzuRo6rvs 8 | bQ/Kag9LAgMBAAECggEBAJ0FyY9bFvx6X+wLYCwaoveQY6850MQu7DDhyw1cdDe+ 9 | Rg+vzU+nK6mamY9+PkBU4vG9aCv9dWtyKGaL6xoDMJC400ZP4d1NOrUDqqLydPpq 10 | JKmc6uXnzG9UOmK2CrEBXrWXO+USmlDmWPKEKnsk79/YfhTdI3s8q9Zmp8YAZPww 11 | qwev2QjHc09EfUXr4F3GxwPA/SRDgtjo0kjdGb66B3CmtS1YrABsTyefFFHEsO5w 12 | B39mW/q8q45N+6zAz5gqOpZFESTtr3IAf+eHecyGfSOtuIh2ssRiNF4Cj8Y40eM0 13 | kFcWVX7YNbveGNouJU8/lktBQmzOKhN4mswnROC8oEECgYEA25KLBivO6jK7NzDh 14 | 5LkI6CiENJzMs5Hdyv+cMTwBDJ9VftvTgHz5jc/Fu+0/UfxkdZRebFuE36DgfoDu 15 | 4g0RBVeDkfRuAHkrw9/VwksUHUSSG4WU6xzUZZqR/x3oGqG8VWmD+iCt0AaNjfqx 16 | BWBNqEY3MT+9ejys+W9D8URfkY0CgYEAwtl8YSpsd5N7NNv3iMKPaoR8l2AMWRB/ 17 | Nt8qUBt5DAjNbDtx8s/xz0/T9MAijEv94XFR93rCVWMDW2Gl3bxaoenMNNPOowui 18 | HeNApEstXYmWFsJljW/yNPvbU+e3shXUPf3aXVwKA/qkqbD+Bjz3CvsvUipjAWXe 19 | gtficCSVcjcCgYAKNj6RAuiUq9dZMcTPxmtLoNbFO6WplFckYc752ziRRbfMNp0X 20 | lLhmiAtCOj5/qaVicowRrg/39pt6RrTVfpYUEYXk++FB1GDcs0RVzPgahF3nOcc7 21 | SBP4xb+UheeNlYgU0Nt6fpqW2jcrK0WgYmI6OUnH2JcPYFMLJsmaJvvq4QKBgHy5 22 | uOt9u4bjigdxEsehOyqE+jfvzJeqfrRCMBStMVPpwo0YlD1IrNH2mIfgAX1rG22X 23 | G0/ebc04nyp8nC8O5bklLolWV7x4suKM2JESakyoyMFy2Iyr7w/JdEEGX8kIPh8c 24 | gw4l32dipsrUuBaIKd8GoOjopw17Bu8cgB8m298LAoGBAKMZrL7GZMM4uInZeCT9 25 | YYiF3g2WhsGnjB5kbIhy20CPCUtOEMviaziMnvlgQhwUz6ez4dmx05p6zYEMv5EX 26 | 57vCTTs31uokmGy+YX61J9DVt2eYjd2qcVKLPBEkgTLBJ8LZK3+yK1HBsVWRs2Cd 27 | 2bkIRFDri2sd0pFkAGNyGGRu 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /rustls-symcrypt/tests/certs/RootCA.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDLzCCAhegAwIBAgIUTANmSWnfVeu3082UBM4SZV8e9jAwDQYJKoZIhvcNAQEL 3 | BQAwJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD0V4YW1wbGUtUm9vdC1DQTAeFw0y 4 | NDAyMjExOTM4MjNaFw0yNjEyMTExOTM4MjNaMCcxCzAJBgNVBAYTAlVTMRgwFgYD 5 | VQQDDA9FeGFtcGxlLVJvb3QtQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK 6 | AoIBAQCnH5dA166tnxD0tJWiHphWsFK/oc73hHmYwOOxRa+HaR/0QBy7753jFw/o 7 | JueMVfTrNtHoaLUaVwX9W3ATnbpv27JvTNSmoJx2h+XRn7Z7p+yokRj9YjnEu+Qo 8 | PvusGkYj8fh3y5SE6JfEhQa/tOD9Ex2fl+6KS2Va6L5uY8IfbU9/h/SX7AIMKCwr 9 | FlGvPmiavlj/WFSgao6qY4kB4dNjUHmgKMk/hFtPfKu5oEZVBt383cUxWI/CkwnK 10 | WnaFC89vV3UBPTjrixIwNcUpMsksl93ky3SBSeIQSUL19hvs1yJnB8koOpdSmT4O 11 | wqIai3d3YQyRzuRo6rvsbQ/Kag9LAgMBAAGjUzBRMB0GA1UdDgQWBBS1AhH/ACQN 12 | g49LL4LnWAYXsyE4zDAfBgNVHSMEGDAWgBS1AhH/ACQNg49LL4LnWAYXsyE4zDAP 13 | BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCboiZMR8uQuDBMNGw+ 14 | br2N06tHJWXdc80dNoFyHFbgvxYD/thCIG+eOotkFZbM7wds/dGhwSCLmPX6PIVh 15 | //ZVZdD/Dk8B0J0jfbeuRM/kAQAvX+6aZCP3EQ2A6zYmquWSWHLazyWD3aY3sGPX 16 | 4vfggNVTCnROS1Rk0i0IzU08z4fDZDfOjDW06x6pUek+7sUR1oVMFMd2Q6CxWCp9 17 | 15J63r0JKmZDG8SOG1iIGTPj4GopDuNnQKA8qJGWc6WwPvJTinCPWMBJ2s73KOwY 18 | hKJQGZ22zULoReHXFY+Dt05vN9iig95OpHHNw+f3jPgwKlPBHhInpjEPNvKFJZ6K 19 | F2kb 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /rustls-symcrypt/tests/certs/localhost.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDczCCAlugAwIBAgIUVuQWWEJldt7G1ixpk1UwDM2xMw4wDQYJKoZIhvcNAQEL 3 | BQAwJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD0V4YW1wbGUtUm9vdC1DQTAeFw0y 4 | NDAyMjExOTM5NDNaFw0yNjEyMTExOTM5NDNaMG0xCzAJBgNVBAYTAlVTMRIwEAYD 5 | VQQIDAlZb3VyU3RhdGUxETAPBgNVBAcMCFlvdXJDaXR5MR0wGwYDVQQKDBRFeGFt 6 | cGxlLUNlcnRpZmljYXRlczEYMBYGA1UEAwwPbG9jYWxob3N0LmxvY2FsMIIBIjAN 7 | BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArwnqNmj1kU7mF2CHdk2J+aB+IXu6 8 | RettCfthQvER1AnehHUnLaMk5ufzuPoc7/7ZW03TdptKBgB4isT8RXwM9usxlvoI 9 | qFYldGD0rlQFfaVfdzFmGlwgloyS8tnQGzr5w6oUDKDClNPAxGIa5RzWiz5nT/zR 10 | 7F8+eiELdvrlLB8fTA5N7A3xCI9yywdYDgLlFsREUd6DpLrfdoCGah8xsFl8aU7E 11 | uL/QuROywz/aShGm9WG4cO7biac59/yMB9+ccchtI+PgScdzz/WsfiqaMZNL+XQ5 12 | iFg9KDJdvB5ZyXtEfQB0f6aX9bbDZ4qlMElCH1tYOyGy7tuy9Cl0AGEfRwIDAQAB 13 | o1EwTzAfBgNVHSMEGDAWgBS1AhH/ACQNg49LL4LnWAYXsyE4zDAJBgNVHRMEAjAA 14 | MAsGA1UdDwQEAwIE8DAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQEL 15 | BQADggEBAFWcr8VYNr+SUQUcAdgrZ5uBjdS9v0oE3co6zuClWhv9P7pKT+QYEe4/ 16 | 0DzNWz4BisT4VHUqyV4QAXqIY6qT0cmlHMwFBTcMNTagZ6sAGr0ULcLNAkwTjUkm 17 | xTQecGPE06ynZGxThY8Bd6jQGfrn8Bkpzv5DdM47SQxG5e2k6IeQ1GW7wE6BpjUv 18 | eRGUDAI7UD1R3T3zDDtWFOCz8jFodqC5E6Cy/Np7p2J+5Rhbxl+0ytMtegQY/Mfv 19 | Uvltab20jnZiozF4y27l18iG4SP7h61SJoF02Jc4VVZMAnoqm25ddR2VaoSXjW6i 20 | Tq/gk2MYM/5Ym1iSykAevHovwMMwzB0= 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /rustls-symcrypt/tests/certs/localhost.key: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCvCeo2aPWRTuYX 3 | YId2TYn5oH4he7pF620J+2FC8RHUCd6EdSctoyTm5/O4+hzv/tlbTdN2m0oGAHiK 4 | xPxFfAz26zGW+gioViV0YPSuVAV9pV93MWYaXCCWjJLy2dAbOvnDqhQMoMKU08DE 5 | YhrlHNaLPmdP/NHsXz56IQt2+uUsHx9MDk3sDfEIj3LLB1gOAuUWxERR3oOkut92 6 | gIZqHzGwWXxpTsS4v9C5E7LDP9pKEab1Ybhw7tuJpzn3/IwH35xxyG0j4+BJx3PP 7 | 9ax+Kpoxk0v5dDmIWD0oMl28HlnJe0R9AHR/ppf1tsNniqUwSUIfW1g7IbLu27L0 8 | KXQAYR9HAgMBAAECggEADbm2TuvuDaXlLwIXvTQZVKS8Hz4HfkIWu8ileM1Ue48G 9 | jtZs6ww28ZBQsTmTmVIfkyRIJ63HoS7aRO9rZLt0fMw2iEM0+JZAu556sUzPXWnh 10 | UYRjIEAHIicFwttHkUsPmMM2bUMR3v+3xu52c27Od/69tSz6/RD+4i7DKmJEJDBv 11 | O0SJVVxdoam15TTTW1CqnQeomg+C5dNhFAg1uYu+REiddFRjLRJR4xG4FPsqdA1z 12 | 1rzGYZlU7I5bW3B8gNpwkRBx4f03NxyqDu3aPK8CBi/NMJumTpq0E1kSW7vODesZ 13 | /5fb8mWUVKQJwFmPWUNQs/Go5D0YOVYlu0YgVU5VqQKBgQDnuekUU10iadZKurkU 14 | 4NumGaHULVc0v9Iy6nhIQ8Y5uF0OMOWYIJ9kG1pAgzJ10bHJvhUvqi4fwxdRvo48 15 | qM8AT4RiXTa0RtZv3sMHcAn1ZHVzYGhwArN+UmMBuPvbj1HJHHYjUETBeHweHRVa 16 | /B/l1IjwoU4lYvTgNSEtmmuMfQKBgQDBX9f7SGdUhYyxjsAcnMmyYDQKiKa+XRlZ 17 | Is7q673ahs/b6oIBuvj5Bo4ZfpuX0ZkbbPgXemaKw1wnTOqaBv6bEAhqf8jb5HXp 18 | 6LvTzg2UQL8HdBdZOd/K1ozFbkdqqeO+sUw4dsp2lYU/zE+LhS1/MyRCWB7zMp1K 19 | 7d2aXXUaEwKBgQDBhZWOEADb2J/KUR54vUEy+n0YAbWuq/QT6ZUCZPeLBNlSHKvh 20 | 3HzA0ccR0X+2vaVI4qI26F0U0Y0MC6QmLKSTkdTxgP9Kl05GpzchYwQuF/Ouo3kU 21 | 8myMtqlQqvhLaOnYlxhibYq+OK0PSSKolZ7eBh1HOK9Wscnn5PcMasYe0QKBgB0j 22 | JvUrFL7MnMWIX/Qvv8iL7GuF6bIXbyFaOFl3ihTqaVmWvV4rYSaM0U6QIDvBDlPu 23 | mHdZLyhLhZA6a8MnuKd+w/XgKVDQ3N+Q/PROQQeMtfwWhwofyVPT/kQleMder/1k 24 | 07pSU/GIWBqj23yHZbKb7yO8CXXVs5O9wb1nxaRXAoGANjF/qdvNyEZ6RVu1Y1B9 25 | hT1zV9qUDfb+hdSEaUplijucrIISWNeOXyhLEPcQE/i+2gHYQShHBxN720n0HGlc 26 | L7sT9H4ewsJcLrH00VGZb1LZx497Aiwa86KEiLolNpPEOQ38gSLXxcq4wmLsS2bB 27 | XY84nhn6D6sPQDYhnKR7s0k= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /rustls-symcrypt/tests/certs/localhost.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDczCCAlugAwIBAgIUVuQWWEJldt7G1ixpk1UwDM2xMw4wDQYJKoZIhvcNAQEL 3 | BQAwJzELMAkGA1UEBhMCVVMxGDAWBgNVBAMMD0V4YW1wbGUtUm9vdC1DQTAeFw0y 4 | NDAyMjExOTM5NDNaFw0yNjEyMTExOTM5NDNaMG0xCzAJBgNVBAYTAlVTMRIwEAYD 5 | VQQIDAlZb3VyU3RhdGUxETAPBgNVBAcMCFlvdXJDaXR5MR0wGwYDVQQKDBRFeGFt 6 | cGxlLUNlcnRpZmljYXRlczEYMBYGA1UEAwwPbG9jYWxob3N0LmxvY2FsMIIBIjAN 7 | BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArwnqNmj1kU7mF2CHdk2J+aB+IXu6 8 | RettCfthQvER1AnehHUnLaMk5ufzuPoc7/7ZW03TdptKBgB4isT8RXwM9usxlvoI 9 | qFYldGD0rlQFfaVfdzFmGlwgloyS8tnQGzr5w6oUDKDClNPAxGIa5RzWiz5nT/zR 10 | 7F8+eiELdvrlLB8fTA5N7A3xCI9yywdYDgLlFsREUd6DpLrfdoCGah8xsFl8aU7E 11 | uL/QuROywz/aShGm9WG4cO7biac59/yMB9+ccchtI+PgScdzz/WsfiqaMZNL+XQ5 12 | iFg9KDJdvB5ZyXtEfQB0f6aX9bbDZ4qlMElCH1tYOyGy7tuy9Cl0AGEfRwIDAQAB 13 | o1EwTzAfBgNVHSMEGDAWgBS1AhH/ACQNg49LL4LnWAYXsyE4zDAJBgNVHRMEAjAA 14 | MAsGA1UdDwQEAwIE8DAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQEL 15 | BQADggEBAFWcr8VYNr+SUQUcAdgrZ5uBjdS9v0oE3co6zuClWhv9P7pKT+QYEe4/ 16 | 0DzNWz4BisT4VHUqyV4QAXqIY6qT0cmlHMwFBTcMNTagZ6sAGr0ULcLNAkwTjUkm 17 | xTQecGPE06ynZGxThY8Bd6jQGfrn8Bkpzv5DdM47SQxG5e2k6IeQ1GW7wE6BpjUv 18 | eRGUDAI7UD1R3T3zDDtWFOCz8jFodqC5E6Cy/Np7p2J+5Rhbxl+0ytMtegQY/Mfv 19 | Uvltab20jnZiozF4y27l18iG4SP7h61SJoF02Jc4VVZMAnoqm25ddR2VaoSXjW6i 20 | Tq/gk2MYM/5Ym1iSykAevHovwMMwzB0= 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /rustls-symcrypt/tests/full_test.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::fs::File; 3 | use std::io::{BufReader, Write}; 4 | use std::net::TcpStream; 5 | use std::path::PathBuf; 6 | use std::process::{Child, Command}; 7 | use std::sync::{Arc, Mutex}; 8 | use std::thread; 9 | 10 | use rustls::crypto::SupportedKxGroup; 11 | use rustls::{CipherSuite, SupportedCipherSuite}; 12 | 13 | use rustls_symcrypt::{ 14 | custom_symcrypt_provider, default_symcrypt_provider, SECP256R1, SECP384R1, 15 | TLS13_AES_128_GCM_SHA256, TLS13_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 16 | TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 17 | TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 18 | }; 19 | 20 | #[cfg(feature = "x25519")] 21 | use rustls_symcrypt::X25519; 22 | 23 | #[cfg(feature = "chacha")] 24 | use rustls_symcrypt::{ 25 | TLS13_CHACHA20_POLY1305_SHA256, TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 26 | TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 27 | }; 28 | 29 | static TEST_CERT_PATH: once_cell::sync::Lazy = once_cell::sync::Lazy::new(|| { 30 | let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR")); 31 | path.push("tests"); 32 | path.push("certs"); 33 | path 34 | }); 35 | 36 | struct OpenSSLServer(Child); 37 | 38 | impl Drop for OpenSSLServer { 39 | fn drop(&mut self) { 40 | let _ = self.0.kill(); 41 | } 42 | } 43 | 44 | fn start_openssl_server() -> OpenSSLServer { 45 | let cert_path = TEST_CERT_PATH 46 | .join("localhost.pem") 47 | .into_os_string() 48 | .into_string() 49 | .unwrap(); 50 | let key_path = TEST_CERT_PATH 51 | .join("localhost.key") 52 | .into_os_string() 53 | .into_string() 54 | .unwrap(); 55 | 56 | let child = Command::new("openssl") 57 | .arg("s_server") 58 | .arg("-accept") 59 | .arg("4443") 60 | .arg("-cert") 61 | .arg(cert_path) 62 | .arg("-key") 63 | .arg(key_path) 64 | .stdout(std::process::Stdio::null()) // Suppress standard output 65 | .stderr(std::process::Stdio::null()) // Suppress standard error 66 | .spawn() 67 | .expect("Failed to start OpenSSL server."); 68 | 69 | OpenSSLServer(child) 70 | } 71 | 72 | fn test_with_config( 73 | suite: SupportedCipherSuite, 74 | group: &'static dyn SupportedKxGroup, 75 | ) -> CipherSuite { 76 | let cipher_suites = vec![suite]; 77 | let kx_group = vec![group]; 78 | 79 | // Add default webpki roots to the root store 80 | let mut root_store = rustls::RootCertStore { 81 | roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(), 82 | }; 83 | 84 | let cert_path = TEST_CERT_PATH 85 | .join("RootCA.pem") 86 | .into_os_string() 87 | .into_string() 88 | .unwrap(); 89 | 90 | let certs = rustls_pemfile::certs(&mut BufReader::new(&mut File::open(cert_path).unwrap())) 91 | .collect::, _>>() 92 | .unwrap(); 93 | 94 | root_store.add_parsable_certificates(certs); 95 | 96 | let config = rustls::ClientConfig::builder_with_provider(Arc::new(custom_symcrypt_provider( 97 | Some(cipher_suites), 98 | Some(kx_group), 99 | ))) 100 | .with_safe_default_protocol_versions() 101 | .unwrap() 102 | .with_root_certificates(root_store) 103 | .with_no_client_auth(); 104 | 105 | let server_name = "localhost".try_into().unwrap(); 106 | 107 | let mut sock = TcpStream::connect("localhost:4443").unwrap(); 108 | 109 | let mut conn = rustls::ClientConnection::new(Arc::new(config), server_name).unwrap(); 110 | let mut tls = rustls::Stream::new(&mut conn, &mut sock); 111 | tls.write_all( 112 | concat!( 113 | "GET / HTTP/1.1\r\n", 114 | "Host: localhost\r\n", 115 | "Connection: close\r\n", 116 | "Accept-Encoding: identity\r\n", 117 | "\r\n" 118 | ) 119 | .as_bytes(), 120 | ) 121 | .unwrap(); 122 | 123 | let ciphersuite = tls.conn.negotiated_cipher_suite().unwrap(); 124 | 125 | let mut exit_buffer: [u8; 1] = [0]; // Size 1 because "Q" is a single byte command 126 | exit_buffer[0] = b'Q'; // Assign the ASCII value of "Q" to the buffer 127 | 128 | // Write the "Q" command to the TLS connection stream 129 | tls.write_all(&exit_buffer).unwrap(); 130 | ciphersuite.suite() 131 | } 132 | 133 | fn test_with_custom_config_to_internet( 134 | suite: SupportedCipherSuite, 135 | group: &'static dyn SupportedKxGroup, 136 | host_name: String, 137 | ) -> CipherSuite { 138 | let cipher_suites = vec![suite]; 139 | let kx_group = vec![group]; 140 | 141 | // Add default webpki roots to the root store 142 | let root_store = rustls::RootCertStore { 143 | roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(), 144 | }; 145 | 146 | let config = rustls::ClientConfig::builder_with_provider(Arc::new(custom_symcrypt_provider( 147 | Some(cipher_suites), 148 | Some(kx_group), 149 | ))) 150 | .with_safe_default_protocol_versions() 151 | .unwrap() 152 | .with_root_certificates(root_store) 153 | .with_no_client_auth(); 154 | 155 | let server_name = host_name.clone().try_into().unwrap(); 156 | let addr = format!("{}:443", host_name); 157 | let mut sock = TcpStream::connect(&addr).unwrap(); 158 | 159 | let mut conn = rustls::ClientConnection::new(Arc::new(config), server_name).unwrap(); 160 | let mut tls = rustls::Stream::new(&mut conn, &mut sock); 161 | tls.write_all( 162 | format!( 163 | "GET / HTTP/1.1\r\n\ 164 | Host: {}\r\n\ 165 | Connection: close\r\n\ 166 | Accept-Encoding: identity\r\n\ 167 | \r\n", 168 | host_name 169 | ) 170 | .as_bytes(), 171 | ) 172 | .unwrap(); 173 | 174 | let ciphersuite = tls.conn.negotiated_cipher_suite().unwrap(); 175 | 176 | let mut exit_buffer: [u8; 1] = [0]; // Size 1 because "Q" is a single byte command 177 | exit_buffer[0] = b'Q'; // Assign the ASCII value of "Q" to the buffer 178 | 179 | // Write the "Q" command to the TLS connection stream 180 | tls.write_all(&exit_buffer).unwrap(); 181 | ciphersuite.suite() 182 | } 183 | 184 | // Test TLS 1.3 Cipher Suites 185 | 186 | #[test] 187 | fn test_tls13_aes_128_256() { 188 | let server_thread = { 189 | let openssl_server = Arc::new(Mutex::new(start_openssl_server())); 190 | thread::spawn(move || { 191 | openssl_server 192 | .lock() 193 | .unwrap() 194 | .0 195 | .wait() 196 | .expect("OpenSSL server crashed unexpectedly"); 197 | }) 198 | }; 199 | 200 | // Wait for the server to start 201 | thread::sleep(std::time::Duration::from_secs(5)); 202 | 203 | let expected_suite = test_with_config(TLS13_AES_128_GCM_SHA256, SECP384R1); 204 | assert_eq!(expected_suite, CipherSuite::TLS13_AES_128_GCM_SHA256); 205 | drop(server_thread); 206 | } 207 | 208 | #[test] 209 | fn test_tls13_aes_256_384() { 210 | let server_thread = { 211 | let openssl_server = Arc::new(Mutex::new(start_openssl_server())); 212 | thread::spawn(move || { 213 | openssl_server 214 | .lock() 215 | .unwrap() 216 | .0 217 | .wait() 218 | .expect("OpenSSL server crashed unexpectedly"); 219 | }) 220 | }; 221 | 222 | // Wait for the server to start 223 | thread::sleep(std::time::Duration::from_secs(5)); 224 | 225 | let expected_suite = test_with_config(TLS13_AES_256_GCM_SHA384, SECP256R1); 226 | assert_eq!(expected_suite, CipherSuite::TLS13_AES_256_GCM_SHA384); 227 | drop(server_thread); 228 | } 229 | 230 | #[cfg(feature = "chacha")] 231 | #[test] 232 | fn test_tls13_chacha_1305() { 233 | let server_thread = { 234 | let openssl_server = Arc::new(Mutex::new(start_openssl_server())); 235 | thread::spawn(move || { 236 | openssl_server 237 | .lock() 238 | .unwrap() 239 | .0 240 | .wait() 241 | .expect("OpenSSL server crashed unexpectedly"); 242 | }) 243 | }; 244 | 245 | // Wait for the server to start 246 | thread::sleep(std::time::Duration::from_secs(5)); 247 | 248 | let expected_suite = test_with_config(TLS13_CHACHA20_POLY1305_SHA256, SECP256R1); 249 | assert_eq!(expected_suite, CipherSuite::TLS13_CHACHA20_POLY1305_SHA256); 250 | drop(server_thread); 251 | } 252 | 253 | // Test TLS 1.2 Cipher Suites 254 | 255 | #[test] 256 | fn test_tls12_rsa_256_384() { 257 | let server_thread = { 258 | let openssl_server = Arc::new(Mutex::new(start_openssl_server())); 259 | thread::spawn(move || { 260 | openssl_server 261 | .lock() 262 | .unwrap() 263 | .0 264 | .wait() 265 | .expect("OpenSSL server crashed unexpectedly"); 266 | }) 267 | }; 268 | 269 | // Wait for the server to start 270 | thread::sleep(std::time::Duration::from_secs(5)); 271 | 272 | let expected_suite = test_with_config(TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, SECP256R1); 273 | assert_eq!( 274 | expected_suite, 275 | CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 276 | ); 277 | drop(server_thread); 278 | } 279 | 280 | #[test] 281 | fn test_tls12_rsa_128_256() { 282 | let server_thread = { 283 | let openssl_server = Arc::new(Mutex::new(start_openssl_server())); 284 | thread::spawn(move || { 285 | openssl_server 286 | .lock() 287 | .unwrap() 288 | .0 289 | .wait() 290 | .expect("OpenSSL server crashed unexpectedly"); 291 | }) 292 | }; 293 | 294 | // Wait for the server to start 295 | thread::sleep(std::time::Duration::from_secs(5)); 296 | 297 | let expected_suite = test_with_config(TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, SECP256R1); 298 | assert_eq!( 299 | expected_suite, 300 | CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 301 | ); 302 | drop(server_thread); 303 | } 304 | 305 | #[cfg(feature = "x25519")] 306 | #[test] 307 | fn test_tls13_256_384_with_25519() { 308 | let server_thread = { 309 | let openssl_server = Arc::new(Mutex::new(start_openssl_server())); 310 | thread::spawn(move || { 311 | openssl_server 312 | .lock() 313 | .unwrap() 314 | .0 315 | .wait() 316 | .expect("OpenSSL server crashed unexpectedly"); 317 | }) 318 | }; 319 | 320 | // Wait for the server to start 321 | thread::sleep(std::time::Duration::from_secs(5)); 322 | 323 | let expected_suite = test_with_config(TLS13_AES_256_GCM_SHA384, X25519); 324 | assert_eq!(expected_suite, CipherSuite::TLS13_AES_256_GCM_SHA384); 325 | drop(server_thread); 326 | } 327 | 328 | #[test] 329 | fn test_tls13_256_384_with_nist384() { 330 | let server_thread = { 331 | let openssl_server = Arc::new(Mutex::new(start_openssl_server())); 332 | thread::spawn(move || { 333 | openssl_server 334 | .lock() 335 | .unwrap() 336 | .0 337 | .wait() 338 | .expect("OpenSSL server crashed unexpectedly"); 339 | }) 340 | }; 341 | 342 | // Wait for the server to start 343 | thread::sleep(std::time::Duration::from_secs(5)); 344 | 345 | let expected_suite = test_with_config(TLS13_AES_256_GCM_SHA384, SECP384R1); 346 | assert_eq!(expected_suite, CipherSuite::TLS13_AES_256_GCM_SHA384); 347 | drop(server_thread); 348 | } 349 | 350 | // Test TLS connection to internet 351 | #[cfg(feature = "chacha")] 352 | #[test] 353 | fn test_chacha_to_internet() { 354 | let expected_suite = test_with_custom_config_to_internet( 355 | TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, 356 | SECP384R1, 357 | "www.rust-lang.org".to_string(), 358 | ); 359 | assert_eq!( 360 | expected_suite, 361 | CipherSuite::TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 362 | ); 363 | } 364 | 365 | #[test] 366 | fn test_tls12_rsa_128_256_to_internet() { 367 | let expected_suite = test_with_custom_config_to_internet( 368 | TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, 369 | SECP256R1, 370 | "www.rust-lang.org".to_string(), 371 | ); 372 | assert_eq!( 373 | expected_suite, 374 | CipherSuite::TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 375 | ); 376 | } 377 | 378 | #[test] 379 | fn test_tls12_rsa_256_384_to_internet() { 380 | let expected_suite = test_with_custom_config_to_internet( 381 | TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, 382 | SECP384R1, 383 | "rust-lang.org".to_string(), 384 | ); 385 | assert_eq!( 386 | expected_suite, 387 | CipherSuite::TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 388 | ); 389 | } 390 | 391 | #[test] 392 | fn test_tls12_ecdsa_128_256_to_internet() { 393 | let expected_suite = test_with_custom_config_to_internet( 394 | TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 395 | SECP256R1, 396 | "www.ssl.org".to_string(), 397 | ); 398 | assert_eq!( 399 | expected_suite, 400 | CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, 401 | ); 402 | } 403 | 404 | #[test] 405 | fn test_tls12_ecdsa_256_384_to_internet() { 406 | let expected_suite = test_with_custom_config_to_internet( 407 | TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, 408 | SECP256R1, 409 | "www.ssl.org".to_string(), 410 | ); 411 | assert_eq!( 412 | expected_suite, 413 | CipherSuite::TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, 414 | ); 415 | } 416 | 417 | #[test] 418 | #[cfg(feature = "chacha")] 419 | fn test_tls12_ecdsa_poly_1305_to_internet() { 420 | let expected_suite = test_with_custom_config_to_internet( 421 | TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 422 | SECP256R1, 423 | "www.ssl.org".to_string(), 424 | ); 425 | assert_eq!( 426 | expected_suite, 427 | CipherSuite::TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256, 428 | ); 429 | } 430 | 431 | #[test] 432 | fn test_default_client() { 433 | // Spawn a concurrent thread that starts the server 434 | let server_thread = { 435 | let openssl_server = Arc::new(Mutex::new(start_openssl_server())); 436 | thread::spawn(move || { 437 | openssl_server 438 | .lock() 439 | .unwrap() 440 | .0 441 | .wait() 442 | .expect("OpenSSL server crashed unexpectedly"); 443 | }) 444 | }; 445 | 446 | // Wait for the server to start 447 | thread::sleep(std::time::Duration::from_secs(5)); 448 | 449 | // Add default webpki roots to the root store 450 | let mut root_store = rustls::RootCertStore { 451 | roots: webpki_roots::TLS_SERVER_ROOTS.to_vec(), 452 | }; 453 | 454 | let cert_path = TEST_CERT_PATH 455 | .join("RootCA.pem") 456 | .into_os_string() 457 | .into_string() 458 | .unwrap(); 459 | 460 | let certs = rustls_pemfile::certs(&mut BufReader::new(&mut File::open(cert_path).unwrap())) 461 | .collect::, _>>() 462 | .unwrap(); 463 | 464 | root_store.add_parsable_certificates(certs); 465 | 466 | let config = rustls::ClientConfig::builder_with_provider(Arc::new(default_symcrypt_provider())) 467 | .with_safe_default_protocol_versions() 468 | .unwrap() 469 | .with_root_certificates(root_store) 470 | .with_no_client_auth(); 471 | 472 | let server_name = "localhost".try_into().unwrap(); 473 | 474 | let mut sock = TcpStream::connect("localhost:4443").unwrap(); 475 | 476 | let mut conn = rustls::ClientConnection::new(Arc::new(config), server_name).unwrap(); 477 | let mut tls = rustls::Stream::new(&mut conn, &mut sock); 478 | tls.write_all( 479 | concat!( 480 | "GET / HTTP/1.1\r\n", 481 | "Host: localhost\r\n", 482 | "Connection: close\r\n", 483 | "Accept-Encoding: identity\r\n", 484 | "\r\n" 485 | ) 486 | .as_bytes(), 487 | ) 488 | .unwrap(); 489 | 490 | let ciphersuite = tls.conn.negotiated_cipher_suite().unwrap(); 491 | 492 | let mut exit_buffer: [u8; 1] = [0]; // Size 1 because "Q" is a single byte command 493 | exit_buffer[0] = b'Q'; // Assign the ASCII value of "Q" to the buffer 494 | 495 | // Write the "Q" command to the TLS connection stream 496 | tls.write_all(&exit_buffer).unwrap(); 497 | 498 | assert_eq!(ciphersuite.suite(), CipherSuite::TLS13_AES_256_GCM_SHA384); 499 | drop(server_thread); 500 | } 501 | --------------------------------------------------------------------------------