├── .cargo └── config.toml ├── .gitattributes ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── bindgen.yml │ ├── build_linux.yml │ ├── build_windows.yml │ ├── check-submodule.yml │ ├── clippy.yml │ ├── fmt.yml │ └── linelint.yml ├── .gitignore ├── .gitmodules ├── CODE_OF_CONDUCT.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── SECURITY.md ├── SUPPORT.md ├── rust-symcrypt ├── Cargo.toml ├── DEVELOPER.md ├── INSTALL.md ├── README.md └── src │ ├── chacha.rs │ ├── cipher │ ├── cbc.rs │ └── mod.rs │ ├── ecc │ ├── ecdh.rs │ ├── ecdsa.rs │ └── mod.rs │ ├── errors.rs │ ├── gcm.rs │ ├── hash.rs │ ├── hkdf.rs │ ├── hmac.rs │ ├── lib.rs │ └── rsa │ ├── mod.rs │ ├── oaep.rs │ ├── pkcs1.rs │ └── pss.rs ├── scripts ├── generate-all-bindings.ps1 └── run.sh ├── symcrypt-bindgen ├── Cargo.toml ├── README.md └── src │ └── main.rs └── symcrypt-sys ├── Cargo.toml ├── README.md ├── VERSION.md ├── build ├── main.rs ├── static_link.rs └── triple.rs ├── inc ├── buildInfo.h ├── static_LinuxDefault.c ├── static_WindowsDefault.c ├── symcrypt_internal_shared.inc └── wrapper.h └── src ├── bindings.rs ├── bindings ├── aarch64_pc_windows_msvc.rs ├── aarch64_unknown_linux_gnu.rs ├── x86_64_pc_windows_msvc.rs └── x86_64_unknown_linux_gnu.rs └── lib.rs /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.aarch64-unknown-linux-gnu] 2 | linker = "aarch64-linux-gnu-gcc" 3 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sh text eol=lf 2 | -------------------------------------------------------------------------------- /.github/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 | 16 | Check the Developer Guidelines in [DEVELOPER.md](./DEVELOPER.md) for more info. -------------------------------------------------------------------------------- /.github/workflows/bindgen.yml: -------------------------------------------------------------------------------- 1 | name: Bindgen 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | 11 | jobs: 12 | bindgen: 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | target: [x86_64-pc-windows-msvc, aarch64-pc-windows-msvc, x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu] 17 | include: 18 | - target: x86_64-pc-windows-msvc 19 | os: windows-latest 20 | - target: aarch64-pc-windows-msvc 21 | os: windows-latest 22 | - target: x86_64-unknown-linux-gnu 23 | os: ubuntu-24.04 24 | - target: aarch64-unknown-linux-gnu 25 | os: ubuntu-24.04 26 | 27 | runs-on: ${{ matrix.os }} 28 | name: Bindgen ${{ matrix.target }} 29 | 30 | steps: 31 | - uses: actions/checkout@v4 # Checks out SymCrypt based on Github submodule 32 | with: 33 | submodules: true 34 | 35 | - name: Install host target 36 | shell: pwsh 37 | run: | 38 | rustup target add ${{ matrix.target }} 39 | if ("${{ matrix.target }}" -match "aarch64-unknown-linux-gnu") { 40 | sudo apt update 41 | sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu 42 | } 43 | 44 | - uses: Swatinem/rust-cache@v2 45 | 46 | - name: Generate bindings 47 | shell: pwsh 48 | run: | 49 | cargo run --locked --bin symcrypt-bindgen ${{ matrix.target }} ${{ runner.temp }} 50 | $targetName = "${{ matrix.target }}".Replace("-", "_") 51 | $newBindingsFilePath = "${{ runner.temp }}/$targetName.rs" 52 | echo "NEW_BINDINGS_FILE_NAME=$targetName.rs" >> $env:GITHUB_ENV 53 | echo "NEW_BINDINGS_FILE_PATH=$newBindingsFilePath" >> $env:GITHUB_ENV 54 | 55 | - name: Check for 'publish_new_bindings' label 56 | id: check_label 57 | if: github.event_name == 'pull_request' 58 | uses: actions/github-script@v6 59 | with: 60 | script: | 61 | const labels = context.payload.pull_request.labels.map(label => label.name); 62 | return labels.includes('publish_new_bindings'); 63 | 64 | - name: Upload bindings as artifact 65 | if: steps.check_label.outputs.result == 'true' 66 | uses: actions/upload-artifact@v4 67 | with: 68 | name: ${{ env.NEW_BINDINGS_FILE_NAME }} 69 | path: ${{ env.NEW_BINDINGS_FILE_PATH }} 70 | 71 | - name: Compare generated bindings 72 | shell: pwsh 73 | run: | 74 | $oldBindingsFile = "./symcrypt-sys/src/bindings/$($env:NEW_BINDINGS_FILE_NAME)" 75 | $diff = git diff --no-index $env:NEW_BINDINGS_FILE_PATH $oldBindingsFile 76 | if ($diff) { 77 | Write-Output "Bindings have changed" 78 | Write-Output $diff 79 | exit 1 80 | } else { 81 | Write-Output "Bindings are up to date" 82 | } 83 | -------------------------------------------------------------------------------- /.github/workflows/build_linux.yml: -------------------------------------------------------------------------------- 1 | name: Build Linux 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | FEATURES_TO_TEST: md5,sha1,pkcs1-encrypt-decrypt 11 | 12 | jobs: 13 | build: 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | include: 18 | - target: x86_64-unknown-linux-gnu 19 | os: ubuntu-latest 20 | runs-on: ubuntu-latest 21 | run-tests: true 22 | - target: aarch64-unknown-linux-gnu 23 | os: ubuntu-24.04-arm 24 | runs-on: ubuntu-24.04-arm 25 | run-tests: true 26 | 27 | runs-on: ${{ matrix.os }} 28 | name: ${{ matrix.target }} 29 | env: 30 | CARGO_BUILD_TARGET: ${{ matrix.target }} 31 | 32 | steps: 33 | - uses: actions/checkout@v4 # Checks out SymCrypt based on Github submodule 34 | with: 35 | submodules: true 36 | 37 | - uses: Swatinem/rust-cache@v2 38 | 39 | # Download SymCrypt via PMC 40 | - name: Install SymCrypt via PMC 41 | shell: bash 42 | run: | 43 | curl -sSL -O https://packages.microsoft.com/config/ubuntu/24.04/packages-microsoft-prod.deb 44 | sudo dpkg -i packages-microsoft-prod.deb 45 | sudo apt-get update 46 | sudo apt-get install -y symcrypt 47 | 48 | - name: Install host target 49 | shell: pwsh 50 | run: | 51 | rustup target add ${{ matrix.target }} 52 | if ("${{ matrix.target }}" -match "aarch64-unknown-linux-gnu") { 53 | sudo apt update 54 | sudo apt install gcc-aarch64-linux-gnu g++-aarch64-linux-gnu 55 | } 56 | 57 | - name: Debug build 58 | run: cargo build --locked --verbose --target ${{ matrix.target }} 59 | 60 | - name: Release build 61 | run: cargo build --release --locked --verbose --target ${{ matrix.target }} 62 | 63 | - name: Run tests (Debug, dynamic) 64 | if: matrix.run-tests 65 | run: cargo test --locked --verbose --all-features --target ${{ matrix.target }} 66 | 67 | - name: Run tests (Release, dynamic) 68 | if: matrix.run-tests 69 | run: cargo test --release --locked --verbose --all-features --target ${{ matrix.target }} 70 | 71 | - name: Run test (Debug, static) 72 | if: matrix.run-tests 73 | run: cargo test --features ${{ env.FEATURES_TO_TEST }} --locked --target ${{ matrix.target }} 74 | 75 | - name: Run test (Release, static) 76 | if: matrix.run-tests 77 | run: cargo test --features ${{ env.FEATURES_TO_TEST }} --locked --target ${{ matrix.target }} 78 | -------------------------------------------------------------------------------- /.github/workflows/build_windows.yml: -------------------------------------------------------------------------------- 1 | name: Build Windows 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | 8 | env: 9 | CARGO_TERM_COLOR: always 10 | FEATURES_TO_TEST: md5,sha1,pkcs1-encrypt-decrypt 11 | 12 | jobs: 13 | build: 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | include: 18 | - target: x86_64-pc-windows-msvc 19 | os: windows-latest 20 | runs-on: windows-latest 21 | symcrypt: "https://github.com/microsoft/SymCrypt/releases/download/v103.8.0/symcrypt-windows-amd64-release-103.8.0-53be637d.zip" 22 | run-tests: true 23 | - target: aarch64-pc-windows-msvc 24 | os: windows-latest 25 | runs-on: windows-latest 26 | symcrypt: "https://github.com/microsoft/SymCrypt/releases/download/v103.8.0/symcrypt-windows-arm64-release-103.8.0-53be637d.zip" 27 | run-tests: false # Windows doesn't support ARM64 emulation 28 | runs-on: ${{ matrix.os }} 29 | name: ${{ matrix.target }} 30 | env: 31 | CARGO_BUILD_TARGET: ${{ matrix.target }} 32 | 33 | steps: 34 | - uses: actions/checkout@v4 # Checks out SymCrypt based on Github submodule 35 | with: 36 | submodules: true 37 | 38 | - uses: Swatinem/rust-cache@v2 39 | 40 | # Install host architecture, required for cross-compilation since there is no arm64-windows-msvc runner 41 | - name: Install host target 42 | shell: pwsh 43 | run: | 44 | rustup target add ${{ matrix.target }} 45 | 46 | - name: Download SymCrypt and Set Environment Variables 47 | shell: pwsh 48 | run: | 49 | Invoke-WebRequest -Uri ${{ matrix.symcrypt }} -OutFile symcrypt.zip 50 | New-Item -ItemType Directory -Force -Path symcrypt 51 | Expand-Archive -Path symcrypt.zip -DestinationPath symcrypt 52 | echo "$env:GITHUB_WORKSPACE\symcrypt\dll" >> $env:GITHUB_PATH 53 | echo "SYMCRYPT_LIB_PATH=$env:GITHUB_WORKSPACE\symcrypt\dll" >> $env:GITHUB_ENV 54 | echo "PATH=$env:GITHUB_WORKSPACE\symcrypt\dll;$env:PATH" >> $env:GITHUB_ENV 55 | 56 | - name: Debug build 57 | run: cargo build --locked --verbose --target ${{ matrix.target }} 58 | 59 | - name: Release build 60 | run: cargo build --release --locked --verbose --target ${{ matrix.target }} 61 | 62 | - name: Run tests (Debug, dynamic) 63 | if: matrix.run-tests 64 | run: cargo test --locked --verbose --all-features --target ${{ matrix.target }} 65 | 66 | - name: Run tests (Release, dynamic) 67 | if: matrix.run-tests 68 | run: cargo test --release --locked --verbose --all-features --target ${{ matrix.target }} 69 | 70 | - name: Run test (Debug, static) 71 | if: matrix.run-tests 72 | run: cargo test --features ${{ env.FEATURES_TO_TEST }} --locked --target ${{ matrix.target }} 73 | 74 | - name: Run test (Release, static) 75 | if: matrix.run-tests 76 | run: cargo test --features ${{ env.FEATURES_TO_TEST }} --locked --target ${{ matrix.target }} 77 | -------------------------------------------------------------------------------- /.github/workflows/check-submodule.yml: -------------------------------------------------------------------------------- 1 | name: Check Submodule Version 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | 8 | jobs: 9 | check-submodule: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Checkout Repository with Submodules 13 | uses: actions/checkout@v4 14 | with: 15 | submodules: true 16 | 17 | - name: Get Expected Commit from VERSION.md 18 | id: expected_commit 19 | run: echo "EXPECTED_COMMIT=$(cat symcrypt-sys/VERSION.md | tr -d '[:space:]')" >> $GITHUB_ENV 20 | 21 | - name: Get Current Submodule Commit 22 | id: current_commit 23 | run: echo "CURRENT_COMMIT=$(git rev-parse HEAD:symcrypt-sys/symcrypt | tr -d '[:space:]')" >> $GITHUB_ENV 24 | 25 | - name: Compare Commits 26 | run: | 27 | if [ "$EXPECTED_COMMIT" != "$CURRENT_COMMIT" ]; then 28 | echo "❌ Submodule commit mismatch!" 29 | echo "Expected: $EXPECTED_COMMIT" 30 | echo "Found: $CURRENT_COMMIT" 31 | exit 1 32 | else 33 | echo "✅ Submodule commit matches VERSION.md" 34 | fi 35 | -------------------------------------------------------------------------------- /.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 | /target/ 2 | .vscode/ 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "SymCrypt"] 2 | path = symcrypt-sys/symcrypt 3 | url = https://github.com/microsoft/SymCrypt.git 4 | -------------------------------------------------------------------------------- /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.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 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 = "bindgen" 22 | version = "0.71.1" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "5f58bf3d7db68cfbac37cfc485a8d711e87e064c3d0fe0435b92f7a407f9d6b3" 25 | dependencies = [ 26 | "bitflags 2.8.0", 27 | "cexpr", 28 | "clang-sys", 29 | "itertools", 30 | "log", 31 | "prettyplease", 32 | "proc-macro2", 33 | "quote", 34 | "regex", 35 | "rustc-hash", 36 | "shlex", 37 | "syn", 38 | ] 39 | 40 | [[package]] 41 | name = "bitflags" 42 | version = "1.3.2" 43 | source = "registry+https://github.com/rust-lang/crates.io-index" 44 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 45 | 46 | [[package]] 47 | name = "bitflags" 48 | version = "2.8.0" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36" 51 | 52 | [[package]] 53 | name = "cc" 54 | version = "1.2.10" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" 57 | dependencies = [ 58 | "shlex", 59 | ] 60 | 61 | [[package]] 62 | name = "cexpr" 63 | version = "0.6.0" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" 66 | dependencies = [ 67 | "nom", 68 | ] 69 | 70 | [[package]] 71 | name = "cfg-if" 72 | version = "1.0.0" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 75 | 76 | [[package]] 77 | name = "clang-sys" 78 | version = "1.8.1" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" 81 | dependencies = [ 82 | "glob", 83 | "libc", 84 | "libloading", 85 | ] 86 | 87 | [[package]] 88 | name = "cmd_lib" 89 | version = "1.9.5" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "371c15a3c178d0117091bd84414545309ca979555b1aad573ef591ad58818d41" 92 | dependencies = [ 93 | "cmd_lib_macros", 94 | "env_logger", 95 | "faccess", 96 | "lazy_static", 97 | "log", 98 | "os_pipe", 99 | ] 100 | 101 | [[package]] 102 | name = "cmd_lib_macros" 103 | version = "1.9.5" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "cb844bd05be34d91eb67101329aeba9d3337094c04fd8507d821db7ebb488eaf" 106 | dependencies = [ 107 | "proc-macro-error2", 108 | "proc-macro2", 109 | "quote", 110 | "syn", 111 | ] 112 | 113 | [[package]] 114 | name = "either" 115 | version = "1.13.0" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 118 | 119 | [[package]] 120 | name = "env_logger" 121 | version = "0.10.2" 122 | source = "registry+https://github.com/rust-lang/crates.io-index" 123 | checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" 124 | dependencies = [ 125 | "humantime", 126 | "is-terminal", 127 | "log", 128 | "regex", 129 | "termcolor", 130 | ] 131 | 132 | [[package]] 133 | name = "faccess" 134 | version = "0.2.4" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "59ae66425802d6a903e268ae1a08b8c38ba143520f227a205edf4e9c7e3e26d5" 137 | dependencies = [ 138 | "bitflags 1.3.2", 139 | "libc", 140 | "winapi", 141 | ] 142 | 143 | [[package]] 144 | name = "glob" 145 | version = "0.3.2" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" 148 | 149 | [[package]] 150 | name = "hermit-abi" 151 | version = "0.4.0" 152 | source = "registry+https://github.com/rust-lang/crates.io-index" 153 | checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" 154 | 155 | [[package]] 156 | name = "hex" 157 | version = "0.4.3" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 160 | 161 | [[package]] 162 | name = "humantime" 163 | version = "2.1.0" 164 | source = "registry+https://github.com/rust-lang/crates.io-index" 165 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 166 | 167 | [[package]] 168 | name = "is-terminal" 169 | version = "0.4.13" 170 | source = "registry+https://github.com/rust-lang/crates.io-index" 171 | checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" 172 | dependencies = [ 173 | "hermit-abi", 174 | "libc", 175 | "windows-sys 0.52.0", 176 | ] 177 | 178 | [[package]] 179 | name = "itertools" 180 | version = "0.13.0" 181 | source = "registry+https://github.com/rust-lang/crates.io-index" 182 | checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" 183 | dependencies = [ 184 | "either", 185 | ] 186 | 187 | [[package]] 188 | name = "itoa" 189 | version = "1.0.14" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 192 | 193 | [[package]] 194 | name = "lazy_static" 195 | version = "1.5.0" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 198 | 199 | [[package]] 200 | name = "libc" 201 | version = "0.2.169" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" 204 | 205 | [[package]] 206 | name = "libloading" 207 | version = "0.8.6" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" 210 | dependencies = [ 211 | "cfg-if", 212 | "windows-targets", 213 | ] 214 | 215 | [[package]] 216 | name = "log" 217 | version = "0.4.25" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" 220 | 221 | [[package]] 222 | name = "memchr" 223 | version = "2.7.4" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 226 | 227 | [[package]] 228 | name = "minimal-lexical" 229 | version = "0.2.1" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" 232 | 233 | [[package]] 234 | name = "nom" 235 | version = "7.1.3" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" 238 | dependencies = [ 239 | "memchr", 240 | "minimal-lexical", 241 | ] 242 | 243 | [[package]] 244 | name = "os_pipe" 245 | version = "1.2.1" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982" 248 | dependencies = [ 249 | "libc", 250 | "windows-sys 0.59.0", 251 | ] 252 | 253 | [[package]] 254 | name = "prettyplease" 255 | version = "0.2.29" 256 | source = "registry+https://github.com/rust-lang/crates.io-index" 257 | checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" 258 | dependencies = [ 259 | "proc-macro2", 260 | "syn", 261 | ] 262 | 263 | [[package]] 264 | name = "proc-macro-error-attr2" 265 | version = "2.0.0" 266 | source = "registry+https://github.com/rust-lang/crates.io-index" 267 | checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" 268 | dependencies = [ 269 | "proc-macro2", 270 | "quote", 271 | ] 272 | 273 | [[package]] 274 | name = "proc-macro-error2" 275 | version = "2.0.1" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" 278 | dependencies = [ 279 | "proc-macro-error-attr2", 280 | "proc-macro2", 281 | "quote", 282 | "syn", 283 | ] 284 | 285 | [[package]] 286 | name = "proc-macro2" 287 | version = "1.0.93" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" 290 | dependencies = [ 291 | "unicode-ident", 292 | ] 293 | 294 | [[package]] 295 | name = "quote" 296 | version = "1.0.38" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" 299 | dependencies = [ 300 | "proc-macro2", 301 | ] 302 | 303 | [[package]] 304 | name = "regex" 305 | version = "1.11.1" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 308 | dependencies = [ 309 | "aho-corasick", 310 | "memchr", 311 | "regex-automata", 312 | "regex-syntax", 313 | ] 314 | 315 | [[package]] 316 | name = "regex-automata" 317 | version = "0.4.9" 318 | source = "registry+https://github.com/rust-lang/crates.io-index" 319 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 320 | dependencies = [ 321 | "aho-corasick", 322 | "memchr", 323 | "regex-syntax", 324 | ] 325 | 326 | [[package]] 327 | name = "regex-syntax" 328 | version = "0.8.5" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 331 | 332 | [[package]] 333 | name = "rustc-hash" 334 | version = "2.1.0" 335 | source = "registry+https://github.com/rust-lang/crates.io-index" 336 | checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" 337 | 338 | [[package]] 339 | name = "ryu" 340 | version = "1.0.18" 341 | source = "registry+https://github.com/rust-lang/crates.io-index" 342 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 343 | 344 | [[package]] 345 | name = "serde" 346 | version = "1.0.217" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" 349 | dependencies = [ 350 | "serde_derive", 351 | ] 352 | 353 | [[package]] 354 | name = "serde_derive" 355 | version = "1.0.217" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" 358 | dependencies = [ 359 | "proc-macro2", 360 | "quote", 361 | "syn", 362 | ] 363 | 364 | [[package]] 365 | name = "serde_json" 366 | version = "1.0.135" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "2b0d7ba2887406110130a978386c4e1befb98c674b4fba677954e4db976630d9" 369 | dependencies = [ 370 | "itoa", 371 | "memchr", 372 | "ryu", 373 | "serde", 374 | ] 375 | 376 | [[package]] 377 | name = "shlex" 378 | version = "1.3.0" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 381 | 382 | [[package]] 383 | name = "symcrypt" 384 | version = "0.5.1" 385 | dependencies = [ 386 | "hex", 387 | "lazy_static", 388 | "libc", 389 | "symcrypt-sys", 390 | ] 391 | 392 | [[package]] 393 | name = "symcrypt-bindgen" 394 | version = "0.2.0" 395 | dependencies = [ 396 | "anyhow", 397 | "bindgen", 398 | "cmd_lib", 399 | "regex", 400 | "serde_json", 401 | ] 402 | 403 | [[package]] 404 | name = "symcrypt-sys" 405 | version = "0.4.0" 406 | dependencies = [ 407 | "cc", 408 | "libc", 409 | ] 410 | 411 | [[package]] 412 | name = "syn" 413 | version = "2.0.96" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" 416 | dependencies = [ 417 | "proc-macro2", 418 | "quote", 419 | "unicode-ident", 420 | ] 421 | 422 | [[package]] 423 | name = "termcolor" 424 | version = "1.4.1" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 427 | dependencies = [ 428 | "winapi-util", 429 | ] 430 | 431 | [[package]] 432 | name = "unicode-ident" 433 | version = "1.0.14" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 436 | 437 | [[package]] 438 | name = "winapi" 439 | version = "0.3.9" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 442 | dependencies = [ 443 | "winapi-i686-pc-windows-gnu", 444 | "winapi-x86_64-pc-windows-gnu", 445 | ] 446 | 447 | [[package]] 448 | name = "winapi-i686-pc-windows-gnu" 449 | version = "0.4.0" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 452 | 453 | [[package]] 454 | name = "winapi-util" 455 | version = "0.1.9" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 458 | dependencies = [ 459 | "windows-sys 0.59.0", 460 | ] 461 | 462 | [[package]] 463 | name = "winapi-x86_64-pc-windows-gnu" 464 | version = "0.4.0" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 467 | 468 | [[package]] 469 | name = "windows-sys" 470 | version = "0.52.0" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 473 | dependencies = [ 474 | "windows-targets", 475 | ] 476 | 477 | [[package]] 478 | name = "windows-sys" 479 | version = "0.59.0" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 482 | dependencies = [ 483 | "windows-targets", 484 | ] 485 | 486 | [[package]] 487 | name = "windows-targets" 488 | version = "0.52.6" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 491 | dependencies = [ 492 | "windows_aarch64_gnullvm", 493 | "windows_aarch64_msvc", 494 | "windows_i686_gnu", 495 | "windows_i686_gnullvm", 496 | "windows_i686_msvc", 497 | "windows_x86_64_gnu", 498 | "windows_x86_64_gnullvm", 499 | "windows_x86_64_msvc", 500 | ] 501 | 502 | [[package]] 503 | name = "windows_aarch64_gnullvm" 504 | version = "0.52.6" 505 | source = "registry+https://github.com/rust-lang/crates.io-index" 506 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 507 | 508 | [[package]] 509 | name = "windows_aarch64_msvc" 510 | version = "0.52.6" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 513 | 514 | [[package]] 515 | name = "windows_i686_gnu" 516 | version = "0.52.6" 517 | source = "registry+https://github.com/rust-lang/crates.io-index" 518 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 519 | 520 | [[package]] 521 | name = "windows_i686_gnullvm" 522 | version = "0.52.6" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 525 | 526 | [[package]] 527 | name = "windows_i686_msvc" 528 | version = "0.52.6" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 531 | 532 | [[package]] 533 | name = "windows_x86_64_gnu" 534 | version = "0.52.6" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 537 | 538 | [[package]] 539 | name = "windows_x86_64_gnullvm" 540 | version = "0.52.6" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 543 | 544 | [[package]] 545 | name = "windows_x86_64_msvc" 546 | version = "0.52.6" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 549 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "rust-symcrypt", 4 | "symcrypt-bindgen", 5 | "symcrypt-sys" 6 | ] 7 | resolver = "2" 8 | 9 | [workspace.package] 10 | edition = "2021" 11 | rust-version = "1.64.0" 12 | -------------------------------------------------------------------------------- /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 on Rust 2 | 3 | ## Introduction 4 | 5 | Within this repository, there are 2 crates: 6 | 7 | 1. **symcrypt-sys**: Modified Rust/C FFI bindings over SymCrypt. 8 | 2. **symcrypt**: Provides friendly Rust wrappers over `symcrypt-sys`. 9 | 10 | Additionally, the repository includes **`symcrypt-bindgen`**, a folder that contains tooling to generate Rust/C FFI bindings used by **symcrypt-sys**. 11 | 12 | The purpose of these crates is to bring FIPS-compliant cryptography to the Rust Ecosystem. 13 | 14 | ## Contribute 15 | We love to receive comments and suggestions. Unfortunately we cannot accept external code contributions except in specific circumstances from vetted partners with whom we have a pre-arranged agreement. If you are a developer of `symcrypt` please see DEVELOPER.md -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /rust-symcrypt/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "symcrypt" 3 | authors = ["nnmkhang"] 4 | version = "0.5.1" 5 | license = "MIT OR Apache-2.0" 6 | description = "Friendly and Idiomatic Wrappers for SymCrypt" 7 | edition.workspace = true 8 | rust-version.workspace = true 9 | homepage = "https://github.com/microsoft/SymCrypt" 10 | repository = "https://github.com/microsoft/rust-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 | # uses '../symcrypt-sys' when compiled locally, and uses 17 | # crates.io versioning when published 18 | symcrypt-sys = {path = "../symcrypt-sys", version = "0.4.0"} 19 | libc = "0.2.0" 20 | lazy_static = "1.4.0" 21 | 22 | [features] 23 | default = [] 24 | md5 = [] 25 | sha1 = [] 26 | pkcs1-encrypt-decrypt = [] 27 | dynamic = ["symcrypt-sys/dynamic"] 28 | 29 | [dev-dependencies] 30 | hex = "0.4.3" 31 | -------------------------------------------------------------------------------- /rust-symcrypt/DEVELOPER.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This document is intended to be read by the current developers of `rust-symcrypt`. 4 | 5 | ## Structure 6 | 7 | This repo contains `rust-symcrypt` as well as `symcrypt-sys` with the former depending on the latter. We utilize the cargo workspaces feature to organize the cargo environment between both sub crates. 8 | 9 | ## How To Generate Bindings 10 | Bindings can be generated 2 ways. 11 | 1. With the `generate-all-bindings.ps1` script. 12 | 13 | Using this option will require you to have some build dependencies installed on your machine, more in depth installation instructions are provided in the `generate-all-bindings.ps1` file. 14 | 15 | 2. Via Github actions 16 | 17 | Bindings can be created by creating a PR with the label `publish_new_bindings` attached to the PR. More info around labels can be found on the [Github documentation](https://docs.Github.com/en/issues/using-labels-and-milestones-to-track-work/managing-labels). This will create an artifact via Github actions that has the bindings for each supported triple. This Github action will not use the `SymCrypt` that is tied to the PR and instead will pull `SymCrypt` from the Github submodule. To update this please see the `Updating SymCrypt Submodule/SymCrypt Version` section. 18 | 19 | **Note:** As part of the Github actions workflow, the CI will check the generated bindings in the Github actions against the new bindings that you are checking in. 20 | 21 | ### Adding New APIs 22 | To add safe wrappers for a new `symcrypt` API you must first generate the required bindings: 23 | 24 | 1. Ensure that you have the correct `SymCrypt` submodule. If you are updating the version of `SymCrypt` that `symcrypt-sys`, please see the `Updating SymCrypt Submodule/SymCrypt Version` section. 25 | 2. Add the new SymCrypt APIs to `symcrypt-bindgen/src/main`. Ensure to use regex semantics as to not include more that is needed for the API you are going to expose. 26 | 3. Generate new bindings with Github actions or the with `generate-all-bindings.ps1` script. 27 | 4. Implement wrapper code in the `rust-symcrypt` layer. Ensure that you are properly documenting your changes and updating the API master list on `rust-symcrypt`'s README.md. 28 | 5. Once you are complete with your API additions, push and ensure that all CI checks pass. 29 | 6. Depending on the type of change you are making you may or may not need to update the cargo version, for more info please see the `Cargo Publishing Guidelines` section. 30 | 31 | ### Updating SymCrypt Submodule/SymCrypt Version. 32 | You may have to update the under-lying `SymCrypt` version from time to time, this will require you to update the `SymCrypt` submodule. Some examples of requiring to update the `SymCrypt` version include: 33 | - Adding new bindings for APIs that have been released by `SymCrypt`. 34 | - Applying a security patch provided by `SymCrypt`. 35 | - When you update the static linking version. 36 | 37 | `symcrypt-sys` depends on a `SymCrypt` via a Github submodule. The `SymCrypt` dependency is tied to a specific version of `SymCrypt`, and a specific commit from the `SymCrypt` Github repo. The specific commit that we depend on can be seen on VERSION.md. There is also a CI check that will ensure that your Github submodule matches the version specified in VERSION.md 38 | 39 | If you need to make an update to the `SymCrypt` submodule dependency, you can do so by: 40 | 1. First going to the `SymCrypt` submodule in `symcrypt-sys/symcrypt` and checking out the required commit. This should be tied to tagged release of `SymCrypt`. 41 | 2. Check in the change via `git add symcrypt-sys/symcrypt`. 42 | 3. Update `VERSION.md` in `symcrypt-sys` to include the new commit HASH that you have checked out in `Step 1`. 43 | 4. Update `README.md` with the new version of `SymCrypt` that `rust-symcrypt` depends on. 44 | 5. Update the `build.yaml` file to ensure that you are downloading the correct SymCrypt dll and .so from the Github artifacts page. 45 | 6. Push your changes and ensure that the CI check passes. 46 | 47 | 48 | ### Cargo Publishing Guidelines 49 | 1. Check out a branch and make your desired changes. ex: `user//bump_version_0.X.X`. 50 | 2. Update version number in the `Cargo.toml` of `rust-symcrypt`, or `symcrypt-sys` or both if required. 51 | 3. Update the `README.md` with the updated version of `rust-symcrypt` or `symcrypt-sys` or both if required. 52 | 4. Adhere to [semver](https://semver.org/) guidelines when bumping version numbers. 53 | 5. Test and validate your changes. As a minimum, manual `cargo test --all-features` must be ran on `Windows` and `WSL`. 54 | 6. After your change is completed, create a PR against `main`, wait for review and ensure that all Github checks have passed. 55 | 7. Once your PR is completed, check out `main` and prep `Cargo` release. 56 | 8. It is important to ensure that you have no dangling un-commited changes on your active branch. When publishing to `Crates.io`, these files will be published in as well. 57 | 9. You can verify which files will be published via `cargo package --list`. 58 | 10. Do a dry publish of the crate via `cargo publish --dry-run`. 59 | 11. If that was successful, publish the crate via `cargo publish`. 60 | 12. Update the release docs by assigning a new `tag` and detailing the changes associated with the new version. Make sure to highlight breaking changes. 61 | 13. If the reason for bumping the version was a critical bug such as a danging pointer, or invalid memory reference, discuss doing a [cargo yank](https://doc.rust-lang.org/cargo/commands/cargo-yank.html) with the rest of the team. 62 | -------------------------------------------------------------------------------- /rust-symcrypt/INSTALL.md: -------------------------------------------------------------------------------- 1 | # Detailed Build and Install 2 | 3 | This page provides more detailed installation instructions 4 | 5 | ## Installation 6 | For ease of use, the recommended usage is to obtain these binaries from the official SymCrypt [Repo](https://github.com/microsoft/SymCrypt/releases/tag/v103.4.2). 7 | 8 | **Note:** If you wish to build your own version please follow the [Build Instructions](https://github.com/microsoft/SymCrypt/blob/main/BUILD.md) that are provided by SymCrypt to install SymCrypt for your target architecture. 9 | 10 | Once SymCrypt is installed on your machine, we must configure your machine so that the SymCrypt crate's build script can easily find `symcrypt.dll` and `symcrypt.lib` which are needed on Windows, or the `libsymcrypt.so*` files which are needed for Linux. 11 | 12 | ### Windows Install 13 | 14 | The `symcrypt.lib` can be found in the the following path after SymCrypt has been downloaded and unzipped. 15 | 16 | `C:\Your-Path-To-SymCrypt-Release-Download\dll\` 17 | 18 | The SymCrypt crate needs to link against the SymCrypt import library during build. 19 | 20 | To do so you must set the `SYMCRYPT_LIB_PATH` environment variable. You can do this by using the following command: 21 | 22 | `setx SYMCRYPT_LIB_PATH ""` 23 | 24 | The `symcrypt.dll` can be found in the the following path after SymCrypt has been downloaded and unzipped. 25 | 26 | `C:\Your-Path-To-SymCrypt-Release-Download\dll\` 27 | 28 | During runtime, Windows will handle finding all needed `dll`'s in order to run the intended program, this includes our `symcrypt.dll` file. The places Windows will look are: 29 | 30 | 1. The folder from which the application loaded. 31 | 2. The system folder. Use the `GetSystemDirectory` function to retrieve the path of this folder. 32 | 3. The Windows folder. Use the `GetWindowsDirectory` function to get the path of this folder. 33 | 4. The current folder. 34 | 5. The directories listed in the PATH environment variable. 35 | 36 | For more info please see: [Dynamic-link library search order](https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order) 37 | 38 | Here are 2 recommended options to ensure your `symcrypt.dll` is found by Windows during runtime. 39 | 40 | 1. Put the `symcrypt.dll` in the same folder as your output `.exe` file. If you are doing development (not release), the common path will be: `C:\your-project\target\debug\`. 41 | 2. Permanently add the `symcrypt.dll` path into your System PATH environment variable. Doing this will ensure that any project that uses the SymCrypt crate will be able to access `symcrypt.lib` 42 | 43 | ### Linux Install 44 | 45 | After installing and unzipping SymCrypt on a Linux distro, the required `libsymcrypt.so*` files can be found in the following path: 46 | `~/Your-Path-To-SymCrypt-Release-Download/lib/` 47 | 48 | The symcrypt crate needs to be able to link with these libs during build/run time. In order to mimic the installation path for other libraries, you must place the `libsymcrypt.so*` files into linker load path. The way that this is set will vary between distros. On most distros it set via the environment variable `$LD_LIBRARY_PATH`. 49 | -------------------------------------------------------------------------------- /rust-symcrypt/README.md: -------------------------------------------------------------------------------- 1 | # SymCrypt Rust Wrapper 2 | 3 | This crate provides friendly and idiomatic Rust wrappers over [SymCrypt](https://github.com/microsoft/SymCrypt), an open-source cryptographic library. 4 | 5 | This crate has a dependency on `symcrypt-sys`, which utilizes `bindgen` to create Rust/C FFI bindings. 6 | 7 | **`symcrypt` version `0.5.1` is based off of `SymCrypt v103.4.2`.**. You must use a version that is greater than or equal to `SymCrypt v103.4.2`. 8 | 9 | To view a detailed list of changes please see the [releases page](https://github.com/microsoft/rust-symcrypt/releases/). 10 | 11 | 12 | ### Supported Configurations 13 | 14 | | Operating Environment | Architecture | Dynamic Linking | 15 | | --------------------- | ----------------- | ----------- | 16 | | Windows user mode | AMD64, ARM64 | ✅ | 17 | | Ubuntu | AMD64, ARM64 | ✅ | 18 | | Azure Linux 3 | AMD64, ARM64 | ✅ | 19 | 20 | ## Supported APIs 21 | 22 | Hashing: 23 | - Md5 ( stateful/stateless ) 24 | - Sha1 ( stateful/stateless ) 25 | - Sha256 ( stateful/stateless ) 26 | - Sha384 ( stateful/stateless ) 27 | - Sha512 ( stateful/stateless ) 28 | - Sha3_256 ( stateful/stateless ) 29 | - Sha3_384 ( stateful/stateless ) 30 | - Sha3_512 ( stateful/stateless ) 31 | 32 | HMAC: 33 | - HmacMd5 ( stateful/stateless ) 34 | - HmacSha1 ( stateful/stateless ) 35 | - HmacSha256 ( stateful/stateless ) 36 | - HmacSha384 ( stateful/stateless ) 37 | - HmacSha512 ( stateful/stateless ) 38 | 39 | HKDF: 40 | - HmacMd5 41 | - HmacSha1 42 | - HmacSha256 43 | - HmacSha384 44 | - HmacSha512 45 | 46 | Encryption: 47 | - AES-GCM Encrypt/Decrypt 48 | - ChaCha20-Poly1305 Encrypt/Decrypt 49 | - AES-CBC Encrypt/Decrypt 50 | 51 | ECC: 52 | - ECDH Secret Agreement ( NistP256, NistP384, NistP521, Curve25519) 53 | - ECDSA Sign / Verify ( NistP256, NistP384, NistP521 ) 54 | 55 | RSA: 56 | - PKCS1 ( Sign, Verify, Encrypt, Decrypt ) 57 | - PSS ( Sign, Verify ) 58 | - OAEP ( Encrypt, Decrypt ) 59 | 60 | **Note**: `Md5` and `Sha1`, and `PKCS1 Encrypt/Decrypt` are considered weak crypto, and are only added for interop purposes. 61 | To enable either `Md5` or `Sha1`, or `Pkcs1 Encrypt/Decrypt` pass the `md5` or `sha1` or `pkcs1-encrypt-decrypt` flag into your `Cargo.toml`. 62 | 63 | --- 64 | 65 | 66 | ## Quick Start Guide 67 | 68 | `symcrypt` requires the `SymCrypt` library to be present at both build time and run time. 69 | 70 | ### Windows: 71 | Download the latest `symcrypt.dll` and `symcrypt.lib` for your corresponding CPU architecture from the [SymCrypt Releases Page](https://github.com/microsoft/SymCrypt/releases) and place them somewhere accessible on your machine. 72 | 73 | Set the required `SYMCRYPT_LIB_PATH` environment variable. You can do this by using the following command: 74 | 75 | `setx SYMCRYPT_LIB_PATH ""` 76 | 77 | You will need to restart `terminal` / `cmd` after setting the environment variable. 78 | 79 | For more information please see the `INSTALL.md` file on the [`rust-symcrypt`](https://github.com/microsoft/rust-symcrypt/tree/main/rust-symcrypt) page. 80 | 81 | ### Linux: 82 | 83 | #### Azure Linux 3: 84 | SymCrypt is pre-installed on Azure Linux 3 machines. Please ensure that you have the most up to date version of SymCrypt by updating via `tdnf`. 85 | 86 | 87 | #### Other distros: 88 | 89 | For Ubuntu, you can install SymCrypt via package manager by connecting to PMC. 90 | 91 | 1. [Connect to PMC](https://learn.microsoft.com/en-us/linux/packages) 92 | 2. `sudo apt-get install symcrypt` 93 | 94 | Alternatively, you can manually install the lib files: 95 | 96 | Download the latest `libsymcrypt.so*` files for your corresponding CPU architecture from the [SymCrypt Releases Page](https://github.com/microsoft/SymCrypt/releases) and place them in your machines `$LD_LIBRARY_PATH`. 97 | 98 | For more information please see the `INSTALL.md` file on the [`rust-symcrypt`](https://github.com/microsoft/rust-symcrypt/tree/main/rust-symcrypt) page 99 | 100 | **Note:** This path may be different depending on your flavour of Linux, and architecture. The goal is to place the `libsymcrypt.so*` files in a location where the your Linux distro can find the required libs at build/run time. 101 | 102 | --- 103 | 104 | ## Usage 105 | There are unit tests attached to each file that show how to use each function. Included is some sample code to do a stateless Sha256 hash. 106 | 107 | **Note:** This code snippet also uses the [hex](https://crates.io/crates/hex) crate. 108 | 109 | ### Instructions: 110 | 111 | add symcrypt to your `Cargo.toml` file. 112 | 113 | ```cargo 114 | [dependencies] 115 | symcrypt = "0.5.1" 116 | hex = "0.4.3" 117 | ``` 118 | 119 | include symcrypt in your code 120 | 121 | ```rust 122 | use symcrypt::hash::sha256; 123 | use hex; 124 | 125 | let data = hex::decode("641ec2cf711e").unwrap(); 126 | let expected: &str = "cfdbd6c9acf9842ce04e8e6a0421838f858559cf22d2ea8a38bd07d5e4692233"; 127 | 128 | let result = sha256(&data); 129 | assert_eq!(hex::encode(result), expected); 130 | ``` 131 | -------------------------------------------------------------------------------- /rust-symcrypt/src/chacha.rs: -------------------------------------------------------------------------------- 1 | //! ChaChaPoly1305 Functions. For further documentation please refer to symcrypt.h 2 | //! 3 | //! # Examples 4 | //! 5 | //! ## Encrypt in place 6 | //! 7 | //! ```rust 8 | //! use hex::*; 9 | //! use symcrypt::chacha::chacha20_poly1305_encrypt_in_place; 10 | //! 11 | //! // Set up inputs 12 | //! let mut key_array = [0u8; 32]; 13 | //! hex::decode_to_slice( "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f",&mut key_array,).unwrap(); 14 | //! 15 | //! let mut nonce_array = [0u8; 12]; 16 | //! hex::decode_to_slice("070000004041424344454647", &mut nonce_array).unwrap(); 17 | //! 18 | //! let mut auth_data = [0u8; 12]; 19 | //! hex::decode_to_slice("50515253c0c1c2c3c4c5c6c7", &mut auth_data).unwrap(); 20 | //! 21 | //! let mut buffer = [0u8; 114]; 22 | //! hex::decode_to_slice("4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73637265656e20776f756c642062652069742e", &mut buffer).unwrap(); 23 | //! 24 | //! let expected_cipher = "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b6116"; 25 | //! let expected_tag = "1ae10b594f09e26a7e902ecbd0600691"; 26 | //! let mut tag = [0u8; 16]; 27 | //! 28 | //! // Encrypt in place, must check this does not fail before checking the buffer or tag values. 29 | //! chacha20_poly1305_encrypt_in_place(&key_array, &nonce_array, &auth_data, &mut buffer, &mut tag).unwrap(); 30 | //! 31 | //! assert_eq!(hex::encode(buffer), expected_cipher); 32 | //! assert_eq!(hex::encode(tag), expected_tag); 33 | //! 34 | //! ``` 35 | //! 36 | //! ## Decrypt in place 37 | //! 38 | //! ```rust 39 | //! use hex::*; 40 | //! use symcrypt::chacha::chacha20_poly1305_decrypt_in_place; 41 | //! 42 | //! // Set up inputs 43 | //! let mut key_array = [0u8; 32]; 44 | //! hex::decode_to_slice( 45 | //! "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f", 46 | //! &mut key_array, 47 | //! ) 48 | //! .unwrap(); 49 | //! let mut nonce_array = [0u8; 12]; 50 | //! hex::decode_to_slice("070000004041424344454647", &mut nonce_array).unwrap(); 51 | //! 52 | //! let mut auth_data = [0u8; 12]; 53 | //! hex::decode_to_slice("50515253c0c1c2c3c4c5c6c7", &mut auth_data).unwrap(); 54 | //! 55 | //! let mut buffer = [0u8; 114]; 56 | //! hex::decode_to_slice("d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b6116", &mut buffer).unwrap(); 57 | //! 58 | //! let dst = "4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73637265656e20776f756c642062652069742e"; 59 | //! 60 | //! let mut tag_array = [0u8; 16]; 61 | //! hex::decode_to_slice("1ae10b594f09e26a7e902ecbd0600691", &mut tag_array).unwrap(); 62 | //! 63 | //! // Decrypt in place, must check this does not fail before checking buffer values. 64 | //! chacha20_poly1305_decrypt_in_place( 65 | //! &key_array, 66 | //! &nonce_array, 67 | //! &auth_data, 68 | //! &mut buffer, 69 | //! &tag_array, 70 | //! ) 71 | //! .unwrap(); 72 | //! 73 | //! assert_eq!(hex::encode(buffer), dst); 74 | //! ``` 75 | //! } 76 | //! 77 | use crate::errors::SymCryptError; 78 | use crate::symcrypt_init; 79 | use symcrypt_sys; 80 | 81 | /// Stateless call to encrypt using ChaChaPoly1305. 82 | /// 83 | /// You must check if this function fails before using the values stored in the buffer. 84 | /// 85 | /// `key` must be 32 bytes 86 | /// 87 | /// `nonce` must be 12 bytes 88 | /// 89 | /// `auth_data` is an optional parameter that can be provided, if you do not wish to provide any auth data, input an empty array. 90 | /// 91 | /// `buffer` is an out parameter that contains the plain text data to be encrypted. after the encryption is complete, the 92 | /// resulting cipher text will be written to the `buffer` parameter. 93 | /// 94 | /// `tag` must be 16 bytes and is an in/out parameter that the tag result will be written to. 95 | /// 96 | /// There is no return value since `buffer` will be modified in place, if this function fails a `error::SymCryptError` will be returned. 97 | /// If the function succeeds nothing will be returned and the `buffer` and `tag` parameters will be modified in place. 98 | pub fn chacha20_poly1305_encrypt_in_place( 99 | key: &[u8; 32], // ChaCha key length must be 32 bytes 100 | nonce: &[u8; 12], // ChaCha nonce length must be 12 bytes 101 | auth_data: &[u8], 102 | buffer: &mut [u8], 103 | tag: &mut [u8; 16], // ChaCha tag must be 16 bytes 104 | ) -> Result<(), SymCryptError> { 105 | symcrypt_init(); 106 | unsafe { 107 | // SAFETY: FFI calls 108 | match symcrypt_sys::SymCryptChaCha20Poly1305Encrypt( 109 | key.as_ptr(), 110 | key.len() as symcrypt_sys::SIZE_T, 111 | nonce.as_ptr(), 112 | nonce.len() as symcrypt_sys::SIZE_T, 113 | auth_data.as_ptr(), 114 | auth_data.len() as symcrypt_sys::SIZE_T, 115 | buffer.as_ptr(), 116 | buffer.as_mut_ptr(), 117 | buffer.len() as symcrypt_sys::SIZE_T, 118 | tag.as_mut_ptr(), 119 | tag.len() as symcrypt_sys::SIZE_T, 120 | ) { 121 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_NO_ERROR => Ok(()), 122 | err => Err(err.into()), 123 | } 124 | } 125 | } 126 | 127 | /// Stateless call to decrypt using ChaChaPoly1305. 128 | /// 129 | /// You must check if this function fails before using the values stored in the in/out parameters. 130 | /// 131 | /// `key` must be 32 bytes 132 | /// 133 | /// `nonce` must be 12 bytes 134 | /// 135 | /// `auth_data` is an optional parameter that can be provided, if you do not wish to provide auth data, input an empty array. 136 | /// 137 | /// `buffer` is an in/out parameter that contains the cipher text data to be decrypted. after the decryption is complete, the 138 | /// resulting plain text will be written to the `buffer` parameter 139 | /// 140 | /// `tag` must be 16 bytes and will be used to check if the decryption is successful. 141 | /// 142 | /// There is no return value since `buffer` will be modified in place, if this function fails a `error::SymCryptError` will be returned. 143 | /// If the function succeeds nothing will be returned and `buffer` will be modified in place. 144 | pub fn chacha20_poly1305_decrypt_in_place( 145 | key: &[u8; 32], // ChaCha key length must be 32 bytes 146 | nonce: &[u8; 12], // ChaCha nonce length must be 12 bytes 147 | auth_data: &[u8], 148 | buffer: &mut [u8], 149 | tag: &[u8; 16], // ChaCha tag must be 16 bytes 150 | ) -> Result<(), SymCryptError> { 151 | symcrypt_init(); 152 | unsafe { 153 | // SAFETY: FFI calls 154 | match symcrypt_sys::SymCryptChaCha20Poly1305Decrypt( 155 | key.as_ptr(), 156 | key.len() as symcrypt_sys::SIZE_T, 157 | nonce.as_ptr(), 158 | nonce.len() as symcrypt_sys::SIZE_T, 159 | auth_data.as_ptr(), 160 | auth_data.len() as symcrypt_sys::SIZE_T, 161 | buffer.as_ptr(), 162 | buffer.as_mut_ptr(), 163 | buffer.len() as symcrypt_sys::SIZE_T, 164 | tag.as_ptr(), 165 | tag.len() as symcrypt_sys::SIZE_T, 166 | ) { 167 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_NO_ERROR => Ok(()), 168 | err => Err(err.into()), 169 | } 170 | } 171 | } 172 | 173 | #[cfg(test)] 174 | mod test { 175 | use super::*; 176 | 177 | #[test] 178 | fn test_chacha_encrypt() { 179 | let mut key_array = [0u8; 32]; 180 | hex::decode_to_slice( 181 | "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f", 182 | &mut key_array, 183 | ) 184 | .unwrap(); 185 | 186 | let mut nonce_array = [0u8; 12]; 187 | hex::decode_to_slice("070000004041424344454647", &mut nonce_array).unwrap(); 188 | 189 | let mut auth_data = [0u8; 12]; 190 | hex::decode_to_slice("50515253c0c1c2c3c4c5c6c7", &mut auth_data).unwrap(); 191 | 192 | let mut buffer = [0u8; 114]; 193 | hex::decode_to_slice("4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73637265656e20776f756c642062652069742e", &mut buffer).unwrap(); 194 | 195 | let expected_cipher = "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b6116"; 196 | let expected_tag = "1ae10b594f09e26a7e902ecbd0600691"; 197 | 198 | let mut tag = [0u8; 16]; 199 | chacha20_poly1305_encrypt_in_place( 200 | &key_array, 201 | &nonce_array, 202 | &auth_data, 203 | &mut buffer, 204 | &mut tag, 205 | ) 206 | .unwrap(); 207 | 208 | assert_eq!(hex::encode(buffer), expected_cipher); 209 | assert_eq!(hex::encode(tag), expected_tag); 210 | } 211 | 212 | #[test] 213 | fn test_chacha_encrypt_no_auth_data() { 214 | let mut key_array = [0u8; 32]; 215 | hex::decode_to_slice( 216 | "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f", 217 | &mut key_array, 218 | ) 219 | .unwrap(); 220 | 221 | let mut nonce_array = [0u8; 12]; 222 | hex::decode_to_slice("070000004041424344454647", &mut nonce_array).unwrap(); 223 | 224 | let mut buffer = [0u8; 114]; 225 | hex::decode_to_slice("4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73637265656e20776f756c642062652069742e", &mut buffer).unwrap(); 226 | 227 | let expected_cipher = "d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b6116"; 228 | 229 | let mut tag = [0u8; 16]; 230 | chacha20_poly1305_encrypt_in_place(&key_array, &nonce_array, &[], &mut buffer, &mut tag) 231 | .unwrap(); 232 | 233 | assert_eq!(hex::encode(buffer), expected_cipher); 234 | } 235 | 236 | #[test] 237 | fn test_chacha_decrypt() { 238 | let mut key_array = [0u8; 32]; 239 | hex::decode_to_slice( 240 | "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f", 241 | &mut key_array, 242 | ) 243 | .unwrap(); 244 | let mut nonce_array = [0u8; 12]; 245 | hex::decode_to_slice("070000004041424344454647", &mut nonce_array).unwrap(); 246 | 247 | let mut auth_data = [0u8; 12]; 248 | hex::decode_to_slice("50515253c0c1c2c3c4c5c6c7", &mut auth_data).unwrap(); 249 | 250 | let mut buffer = [0u8; 114]; 251 | hex::decode_to_slice("d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b6116", &mut buffer).unwrap(); 252 | 253 | let dst = "4c616469657320616e642047656e746c656d656e206f662074686520636c617373206f66202739393a204966204920636f756c64206f6666657220796f75206f6e6c79206f6e652074697020666f7220746865206675747572652c2073756e73637265656e20776f756c642062652069742e"; 254 | 255 | let mut tag_array = [0u8; 16]; 256 | hex::decode_to_slice("1ae10b594f09e26a7e902ecbd0600691", &mut tag_array).unwrap(); 257 | 258 | chacha20_poly1305_decrypt_in_place( 259 | &key_array, 260 | &nonce_array, 261 | &auth_data, 262 | &mut buffer, 263 | &tag_array, 264 | ) 265 | .unwrap(); 266 | 267 | assert_eq!(hex::encode(buffer), dst); 268 | } 269 | 270 | #[test] 271 | fn test_chacha_decrypt_failure() { 272 | let mut key_array = [0u8; 32]; 273 | hex::decode_to_slice( 274 | "808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9f", 275 | &mut key_array, 276 | ) 277 | .unwrap(); 278 | let mut nonce_array = [0u8; 12]; 279 | hex::decode_to_slice("000000000000000000000000", &mut nonce_array).unwrap(); 280 | 281 | let mut auth_data = [0u8; 12]; 282 | hex::decode_to_slice("50515253c0c1c2c3c4c5c6c7", &mut auth_data).unwrap(); 283 | 284 | let mut buffer = [0u8; 114]; 285 | hex::decode_to_slice("d31a8d34648e60db7b86afbc53ef7ec2a4aded51296e08fea9e2b5a736ee62d63dbea45e8ca9671282fafb69da92728b1a71de0a9e060b2905d6a5b67ecd3b3692ddbd7f2d778b8c9803aee328091b58fab324e4fad675945585808b4831d7bc3ff4def08e4b7a9de576d26586cec64b6116", &mut buffer).unwrap(); 286 | 287 | let mut tag_array = [0u8; 16]; 288 | hex::decode_to_slice("1ae10b594f09e26a7e902ecbd0600691", &mut tag_array).unwrap(); 289 | 290 | let result = chacha20_poly1305_decrypt_in_place( 291 | &key_array, 292 | &nonce_array, 293 | &auth_data, 294 | &mut buffer, 295 | &tag_array, 296 | ); 297 | 298 | assert_eq!(result.unwrap_err(), SymCryptError::AuthenticationFailure); 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /rust-symcrypt/src/cipher/mod.rs: -------------------------------------------------------------------------------- 1 | //! Block Cipher functions related to creating expanded keys. For further information please see symcrypt.h for more info 2 | //! 3 | //! This module provides a way to create and use Block Ciphers. Currently only AES is supported. 4 | //! Once an ExpandedKey is created you are able to call Encrypt or Decrypt functions depending on your desired mode 5 | //! 6 | //! # Examples 7 | //! 8 | //! ## AES Expanded Key Creation 9 | //! ```rust 10 | //! 11 | //! use symcrypt::cipher::AesExpandedKey; 12 | //! use hex::*; 13 | //! 14 | //! let key = hex::decode("5d98398b5e3b98d87e07ecf1332df4ac").unwrap(); 15 | //! let aes_key = AesExpandedKey::new(&key).unwrap(); 16 | //! 17 | //! ``` 18 | //! 19 | use crate::errors::SymCryptError; 20 | use crate::symcrypt_init; 21 | use symcrypt_sys; 22 | 23 | use std::marker::PhantomPinned; 24 | use std::pin::Pin; 25 | 26 | // export ciphers 27 | pub mod cbc; 28 | 29 | /// 16 30 | pub const AES_BLOCK_SIZE: u32 = symcrypt_sys::SYMCRYPT_AES_BLOCK_SIZE; 31 | 32 | /// `BlockCipherType` is an enum that enumerates all possible block ciphers that are supported. 33 | /// Currently the only supported type is `AesBlock`. 34 | pub enum BlockCipherType { 35 | AesBlock, 36 | } 37 | struct AesInnerKey { 38 | inner: symcrypt_sys::SYMCRYPT_AES_EXPANDED_KEY, 39 | _pinned: PhantomPinned, 40 | } 41 | 42 | impl AesInnerKey { 43 | pub(crate) fn new() -> Pin> { 44 | Box::pin(AesInnerKey { 45 | inner: symcrypt_sys::SYMCRYPT_AES_EXPANDED_KEY::default(), 46 | _pinned: PhantomPinned, 47 | }) 48 | } 49 | 50 | pub(crate) fn get_inner_mut( 51 | self: Pin<&mut Self>, 52 | ) -> *mut symcrypt_sys::SYMCRYPT_AES_EXPANDED_KEY { 53 | unsafe { 54 | // SAFETY: Accessing the inner state of the pinned data. 55 | &mut self.get_unchecked_mut().inner as *mut _ 56 | } 57 | } 58 | 59 | pub(crate) fn get_inner(&self) -> *const symcrypt_sys::SYMCRYPT_AES_EXPANDED_KEY { 60 | &self.inner as *const _ 61 | } 62 | } 63 | 64 | /// `AesExpandedKey` is a struct that represents an expanded AES key. This struct holds no state and is used to encrypt and decrypt data. 65 | pub struct AesExpandedKey { 66 | // Owned expanded key, this has no state, other calls will take reference to this key. 67 | expanded_key: Pin>, 68 | } 69 | 70 | impl AesExpandedKey { 71 | /// `new()` returns an `AesExpandedKey` or a [`SymCryptError`] if the operation fails. 72 | pub fn new(key: &[u8]) -> Result { 73 | symcrypt_init(); 74 | let mut expanded_key = AesInnerKey::new(); 75 | 76 | unsafe { 77 | // SAFETY: FFI call 78 | match symcrypt_sys::SymCryptAesExpandKey( 79 | expanded_key.as_mut().get_inner_mut(), 80 | key.as_ptr(), 81 | key.len() as symcrypt_sys::SIZE_T, 82 | ) { 83 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_NO_ERROR => { 84 | Ok(AesExpandedKey { expanded_key }) 85 | } 86 | err => Err(err.into()), 87 | } 88 | } 89 | } 90 | 91 | pub fn get_block_size() -> u32 { 92 | AES_BLOCK_SIZE 93 | } 94 | } 95 | 96 | // No custom Send / Sync impl. needed for AesInnerKey and AesExpandedKey and BlockCipherType since the 97 | // underlying data is a pointer to a SymCrypt struct that is not modified after it is created. 98 | unsafe impl Send for AesInnerKey {} 99 | unsafe impl Sync for AesInnerKey {} 100 | unsafe impl Send for AesExpandedKey {} 101 | unsafe impl Sync for AesExpandedKey {} 102 | unsafe impl Send for BlockCipherType {} 103 | unsafe impl Sync for BlockCipherType {} 104 | 105 | pub(crate) fn convert_cipher(cipher: BlockCipherType) -> symcrypt_sys::PCSYMCRYPT_BLOCKCIPHER { 106 | match cipher { 107 | // SAFETY: FFI calls 108 | BlockCipherType::AesBlock => unsafe { symcrypt_sys::SymCryptAesBlockCipher }, 109 | } 110 | } 111 | 112 | #[cfg(test)] 113 | mod tests { 114 | use super::*; 115 | #[test] 116 | fn test_aes_expanded_key_creation_valid_key() { 117 | let key_hex = "00112233445566778899aabbccddeeff"; 118 | let key = hex::decode(key_hex).unwrap(); 119 | 120 | let result = AesExpandedKey::new(&key); 121 | assert!(result.is_ok()); 122 | } 123 | 124 | #[test] 125 | fn test_aes_expanded_key_creation_invalid_key() { 126 | let key = vec![0u8; 10]; // Invalid length for AES key 127 | 128 | let result = AesExpandedKey::new(&key); 129 | assert!( 130 | matches!(result, Err(SymCryptError::WrongKeySize)), 131 | "Expected WrongKeySize error" 132 | ); 133 | } 134 | 135 | #[test] 136 | fn test_get_block_size() { 137 | assert_eq!(AesExpandedKey::get_block_size(), AES_BLOCK_SIZE); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /rust-symcrypt/src/ecc/ecdh.rs: -------------------------------------------------------------------------------- 1 | //! EcDh functions. For further documentation please refer to symcrypt.h 2 | //! 3 | //! # Example 4 | //! 5 | //! ## EcDh with generated keys 6 | //! 7 | //! ```rust 8 | //! use symcrypt::ecc::{EcKey, CurveType, EcKeyUsage}; 9 | //! 10 | //! // Set up 2 separate EcDH structs with public/private key pair attached. 11 | //! 12 | //! let key_1 = EcKey::generate_key_pair(CurveType::NistP256, EcKeyUsage::EcDh).unwrap(); 13 | //! let key_2 = EcKey::generate_key_pair(CurveType::NistP256, EcKeyUsage::EcDh).unwrap(); 14 | //! 15 | //! // Assert that the CurveType matches NistP256. 16 | //! assert_eq!(key_1.get_curve_type(), CurveType::NistP256); 17 | //! assert_eq!(key_2.get_curve_type(), CurveType::NistP256); 18 | //! 19 | //! // Get the public bytes from each EcDh struct generated. 20 | //! let public_bytes_1 = key_1.export_public_key().unwrap(); 21 | //! let public_bytes_2 = key_2.export_public_key().unwrap(); 22 | //! 23 | //! // Calculates secret agreements between private/public keys of two EcKey structs. The result 24 | //! // from each secret agreement should be the same. 25 | //! let ecdh_1_public = 26 | //! EcKey::set_public_key(CurveType::NistP256, public_bytes_1.as_slice(), EcKeyUsage::EcDh).unwrap(); 27 | //! let ecdh_2_public = 28 | //! EcKey::set_public_key(CurveType::NistP256, public_bytes_2.as_slice(), EcKeyUsage::EcDh).unwrap(); 29 | //! 30 | //! let secret_agreement_1 = key_1.ecdh_secret_agreement(ecdh_2_public).unwrap(); 31 | //! let secret_agreement_2 = key_2.ecdh_secret_agreement(ecdh_1_public).unwrap(); 32 | //! 33 | //! assert_eq!(secret_agreement_1, secret_agreement_2); 34 | //! 35 | //! ``` 36 | //! 37 | use crate::ecc::{curve_to_num_format, EcKey}; 38 | use crate::errors::SymCryptError; 39 | use std::vec; 40 | use symcrypt_sys; 41 | 42 | /// Impl for EcDh struct. 43 | impl EcKey { 44 | /// `ecdh_secret_agreement()` returns a `Vec` that represents the secret agreement, or a [`SymCryptError`] if the operation failed. 45 | /// 46 | /// `public_key` is an [`EcKey`] that represents the public key that the secret agreement is being calculated with. 47 | /// 48 | /// If the key usage is not `EcKeyUsage::EcDhAndEcDsa`, or `EcKeyUsage::EcDh`, the function will return a [`SymCryptError::InvalidArgument`]. 49 | pub fn ecdh_secret_agreement(&self, public_key: EcKey) -> Result, SymCryptError> { 50 | let num_format = curve_to_num_format(self.get_curve_type()); 51 | let secret_length = self.get_curve_size(); 52 | let mut secret = vec![0u8; secret_length as usize]; 53 | unsafe { 54 | // SAFETY: FFI calls 55 | match symcrypt_sys::SymCryptEcDhSecretAgreement( 56 | self.inner_key(), 57 | public_key.inner_key(), 58 | num_format, 59 | 0, 60 | secret.as_mut_ptr(), 61 | secret.len() as symcrypt_sys::SIZE_T, 62 | ) { 63 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_NO_ERROR => Ok(secret), 64 | err => Err(err.into()), 65 | } 66 | } 67 | } 68 | } 69 | 70 | #[cfg(test)] 71 | mod test { 72 | use super::*; 73 | use crate::ecc::{CurveType, EcKeyUsage}; 74 | 75 | #[test] 76 | fn test_ecdh_nist_p256() { 77 | let ecdh_1_private = 78 | EcKey::generate_key_pair(CurveType::NistP256, EcKeyUsage::EcDh).unwrap(); 79 | let ecdh_2_private = 80 | EcKey::generate_key_pair(CurveType::NistP256, EcKeyUsage::EcDh).unwrap(); 81 | 82 | let public_bytes_1 = ecdh_1_private.export_public_key().unwrap(); 83 | let public_bytes_2 = ecdh_2_private.export_public_key().unwrap(); 84 | 85 | let ecdh_1_public = EcKey::set_public_key( 86 | CurveType::NistP256, 87 | public_bytes_1.as_slice(), 88 | EcKeyUsage::EcDh, 89 | ) 90 | .unwrap(); 91 | let ecdh_2_public = EcKey::set_public_key( 92 | CurveType::NistP256, 93 | public_bytes_2.as_slice(), 94 | EcKeyUsage::EcDh, 95 | ) 96 | .unwrap(); 97 | 98 | let secret_agreement_1 = ecdh_1_private.ecdh_secret_agreement(ecdh_2_public).unwrap(); 99 | let secret_agreement_2 = ecdh_2_private.ecdh_secret_agreement(ecdh_1_public).unwrap(); 100 | 101 | assert_eq!(secret_agreement_1, secret_agreement_2); 102 | } 103 | 104 | #[test] 105 | fn test_ecdh_nist_p384() { 106 | let ecdh_1_private = 107 | EcKey::generate_key_pair(CurveType::NistP384, EcKeyUsage::EcDh).unwrap(); 108 | let ecdh_2_private = 109 | EcKey::generate_key_pair(CurveType::NistP384, EcKeyUsage::EcDh).unwrap(); 110 | 111 | let public_bytes_1 = ecdh_1_private.export_public_key().unwrap(); 112 | let public_bytes_2 = ecdh_2_private.export_public_key().unwrap(); 113 | 114 | let ecdh_1_public = EcKey::set_public_key( 115 | CurveType::NistP384, 116 | public_bytes_1.as_slice(), 117 | EcKeyUsage::EcDh, 118 | ) 119 | .unwrap(); 120 | 121 | let ecdh_2_public = EcKey::set_public_key( 122 | CurveType::NistP384, 123 | public_bytes_2.as_slice(), 124 | EcKeyUsage::EcDh, 125 | ) 126 | .unwrap(); 127 | 128 | let secret_agreement_1 = ecdh_1_private.ecdh_secret_agreement(ecdh_2_public).unwrap(); 129 | let secret_agreement_2 = ecdh_2_private.ecdh_secret_agreement(ecdh_1_public).unwrap(); 130 | 131 | assert_eq!(secret_agreement_1, secret_agreement_2); 132 | } 133 | 134 | #[test] 135 | fn test_ecdh_nist_p521() { 136 | let ecdh_1_private = 137 | EcKey::generate_key_pair(CurveType::NistP521, EcKeyUsage::EcDh).unwrap(); 138 | let ecdh_2_private = 139 | EcKey::generate_key_pair(CurveType::NistP521, EcKeyUsage::EcDh).unwrap(); 140 | 141 | let public_bytes_1 = ecdh_1_private.export_public_key().unwrap(); 142 | let public_bytes_2 = ecdh_2_private.export_public_key().unwrap(); 143 | 144 | let ecdh_1_public = EcKey::set_public_key( 145 | CurveType::NistP521, 146 | public_bytes_1.as_slice(), 147 | EcKeyUsage::EcDh, 148 | ) 149 | .unwrap(); 150 | 151 | let ecdh_2_public = EcKey::set_public_key( 152 | CurveType::NistP521, 153 | public_bytes_2.as_slice(), 154 | EcKeyUsage::EcDh, 155 | ) 156 | .unwrap(); 157 | 158 | let secret_agreement_1 = ecdh_1_private.ecdh_secret_agreement(ecdh_2_public).unwrap(); 159 | let secret_agreement_2 = ecdh_2_private.ecdh_secret_agreement(ecdh_1_public).unwrap(); 160 | 161 | assert_eq!(secret_agreement_1, secret_agreement_2); 162 | } 163 | 164 | #[test] 165 | fn test_ecdh_curve_25519() { 166 | let ecdh_1_private = 167 | EcKey::generate_key_pair(CurveType::Curve25519, EcKeyUsage::EcDh).unwrap(); 168 | let ecdh_2_private = 169 | EcKey::generate_key_pair(CurveType::Curve25519, EcKeyUsage::EcDh).unwrap(); 170 | 171 | let public_bytes_1 = ecdh_1_private.export_public_key().unwrap(); 172 | let public_bytes_2 = ecdh_2_private.export_public_key().unwrap(); 173 | 174 | let ecdh_1_public = EcKey::set_public_key( 175 | CurveType::Curve25519, 176 | public_bytes_1.as_slice(), 177 | EcKeyUsage::EcDh, 178 | ) 179 | .unwrap(); 180 | 181 | let ecdh_2_public = EcKey::set_public_key( 182 | CurveType::Curve25519, 183 | public_bytes_2.as_slice(), 184 | EcKeyUsage::EcDh, 185 | ) 186 | .unwrap(); 187 | 188 | let secret_agreement_1 = ecdh_1_private.ecdh_secret_agreement(ecdh_2_public).unwrap(); 189 | let secret_agreement_2 = ecdh_2_private.ecdh_secret_agreement(ecdh_1_public).unwrap(); 190 | 191 | assert_eq!(secret_agreement_1, secret_agreement_2); 192 | } 193 | 194 | #[test] 195 | fn test_ecdh_different_curve_types() { 196 | let ecdh_1_private = 197 | EcKey::generate_key_pair(CurveType::Curve25519, EcKeyUsage::EcDh).unwrap(); 198 | let ecdh_2_private = 199 | EcKey::generate_key_pair(CurveType::NistP384, EcKeyUsage::EcDh).unwrap(); 200 | 201 | let public_bytes_1 = ecdh_1_private.export_public_key().unwrap(); 202 | let public_bytes_2 = ecdh_2_private.export_public_key().unwrap(); 203 | 204 | let _ecdh_1_public = EcKey::set_public_key( 205 | CurveType::Curve25519, 206 | public_bytes_1.as_slice(), 207 | EcKeyUsage::EcDh, 208 | ) 209 | .unwrap(); 210 | 211 | let ecdh_2_public = EcKey::set_public_key( 212 | CurveType::Curve25519, 213 | public_bytes_2.as_slice(), 214 | EcKeyUsage::EcDh, 215 | ) 216 | .unwrap_err(); 217 | 218 | assert_eq!(ecdh_2_public, SymCryptError::InvalidArgument); 219 | } 220 | 221 | #[test] 222 | fn test_ecdh_wrong_usage() { 223 | let ecdh_1_private = 224 | EcKey::generate_key_pair(CurveType::Curve25519, EcKeyUsage::EcDsa).unwrap(); 225 | let ecdh_2_private = 226 | EcKey::generate_key_pair(CurveType::Curve25519, EcKeyUsage::EcDsa).unwrap(); 227 | 228 | let public_bytes_1 = ecdh_1_private.export_public_key().unwrap(); 229 | let public_bytes_2 = ecdh_2_private.export_public_key().unwrap(); 230 | 231 | let _ecdh_1_public = EcKey::set_public_key( 232 | CurveType::Curve25519, 233 | public_bytes_1.as_slice(), 234 | EcKeyUsage::EcDh, 235 | ) 236 | .unwrap(); 237 | 238 | let ecdh_2_public = EcKey::set_public_key( 239 | CurveType::Curve25519, 240 | public_bytes_2.as_slice(), 241 | EcKeyUsage::EcDh, 242 | ) 243 | .unwrap(); 244 | 245 | let secret_agreement_1 = ecdh_1_private 246 | .ecdh_secret_agreement(ecdh_2_public) 247 | .unwrap_err(); 248 | 249 | assert_eq!(secret_agreement_1, SymCryptError::InvalidArgument); 250 | } 251 | 252 | #[test] 253 | fn test_ecdh_no_private_key_set() { 254 | let dummy_eckey = EcKey::generate_key_pair(CurveType::NistP256, EcKeyUsage::EcDh).unwrap(); 255 | let dummy_public_key_bytes = dummy_eckey.export_public_key().unwrap(); 256 | let ecdh_1_no_private_key = EcKey::set_public_key( 257 | CurveType::NistP256, 258 | dummy_public_key_bytes.as_slice(), 259 | EcKeyUsage::EcDh, 260 | ) 261 | .unwrap(); 262 | 263 | let ecdh_2_private = 264 | EcKey::generate_key_pair(CurveType::NistP256, EcKeyUsage::EcDh).unwrap(); 265 | let public_bytes_2 = ecdh_2_private.export_public_key().unwrap(); 266 | let ecdh_2_public = EcKey::set_public_key( 267 | CurveType::NistP256, 268 | public_bytes_2.as_slice(), 269 | EcKeyUsage::EcDh, 270 | ) 271 | .unwrap(); 272 | 273 | let secret_agreement_1 = ecdh_1_no_private_key 274 | .ecdh_secret_agreement(ecdh_2_public) 275 | .unwrap_err(); 276 | 277 | assert_eq!(SymCryptError::InvalidArgument, secret_agreement_1); 278 | } 279 | } 280 | -------------------------------------------------------------------------------- /rust-symcrypt/src/ecc/ecdsa.rs: -------------------------------------------------------------------------------- 1 | //! EcDsa functions. For further documentation please refer to symcrypt.h 2 | //! 3 | //! # Example 4 | //! 5 | //! ## ECDSA Sign and Verify 6 | //! 7 | //! ```rust 8 | //! use symcrypt::ecc::{EcKey, CurveType, EcKeyUsage}; 9 | //! use hex::*; 10 | //! 11 | //! // Set up input hash value 12 | //! let hashed_message = hex::decode("4d55c99ef6bd54621662c3d110c3cb627c03d6311393b264ab97b90a4b15214a5593ba2510a53d63fb34be251facb697c973e11b665cb7920f1684b0031b4dd370cb927ca7168b0bf8ad285e05e9e31e34bc24024739fdc10b78586f29eff94412034e3b606ed850ec2c1900e8e68151fc4aee5adebb066eb6da4eaa5681378e").unwrap(); 13 | //! 14 | //! // Generate a new ECDSA key pair 15 | //! let key = EcKey::generate_key_pair(CurveType::NistP256, EcKeyUsage::EcDsa).unwrap(); 16 | //! 17 | //! // Sign the hash value 18 | //! let signature = key.ecdsa_sign(&hashed_message).unwrap(); 19 | //! 20 | //! // Verify the signature 21 | //! let result = key.ecdsa_verify(&signature, &hashed_message); 22 | //! 23 | //! // Assert the signature is valid 24 | //! assert!(result.is_ok()); 25 | //! 26 | //! ``` 27 | //! 28 | use crate::ecc::{curve_to_num_format, CurveType, EcKey, EcKeyUsage}; 29 | use crate::errors::SymCryptError; 30 | use std::vec; 31 | use symcrypt_sys; 32 | 33 | impl EcKey { 34 | /// `ecdsa_sign()` returns a signature as a `Vec`, or a [`SymCryptError`] if the operation fails. 35 | /// 36 | /// `hashed_message` is a `&[u8]` that represents the hash value to sign. 37 | /// 38 | /// If the key usage is not [`EcKeyUsage::EcDsa`], or [`EcKeyUsage::EcDhAndEcDsa`] the function will 39 | /// fail with a [`SymCryptError`] with the value [`SymCryptError::InvalidArgument`]. 40 | pub fn ecdsa_sign(&self, hash_value: &[u8]) -> Result, SymCryptError> { 41 | // SymCrypt code AV's with SymCrypt Fatal Error. 42 | // Panic'ing is not normal here in Rust so we are handling the error instead and returning SymCryptError::InvalidArgument 43 | if self.get_ec_curve_usage() == EcKeyUsage::EcDh { 44 | return Err(SymCryptError::InvalidArgument); 45 | } 46 | // SymCrypt code AV's with SymCrypt Fatal Error for trying to sign with Curve25519 as it is not supported. 47 | // Panic'ing is not normal here in Rust so we are handling the error instead and returning SymCryptError::InvalidArgument 48 | if self.get_curve_type() == CurveType::Curve25519 { 49 | return Err(SymCryptError::InvalidArgument); 50 | } 51 | 52 | // Per SymCrypt docs, the size of the signature will be 2 x the size of the private key. 53 | let signature_size = self.get_size_of_private_key() * 2; 54 | let mut signature = vec![0u8; signature_size as usize]; // must be 2x size of private key SymCryptEckeySizeofPrivateKey 55 | unsafe { 56 | // SAFETY: FFI calls 57 | match symcrypt_sys::SymCryptEcDsaSign( 58 | self.inner_key(), 59 | hash_value.as_ptr(), 60 | hash_value.len() as symcrypt_sys::SIZE_T, 61 | curve_to_num_format(self.curve_type), // Derive number format from curve type 62 | 0, 63 | signature.as_mut_ptr(), 64 | signature.len() as symcrypt_sys::SIZE_T, 65 | ) { 66 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_NO_ERROR => Ok(signature), 67 | err => Err(err.into()), 68 | } 69 | } 70 | } 71 | 72 | /// `ecdsa_verify()` returns `Ok(())` if the signature is valid, or a [`SymCryptError`] if the operation fails. 73 | /// 74 | /// Caller must check the return value to determine if the signature is valid before continuing. 75 | /// 76 | /// `signature` is a `&[u8]` that represents the signature to verify. 77 | /// 78 | /// `hashed_message` is a `&[u8]` that represents the hashed message to verify. 79 | /// 80 | /// if the key usage is not [`EcKeyUsage::EcDsa`], or [`EcKeyUsage::EcDhAndEcDsa`] the function will 81 | /// fail with a [`SymCryptError`] with the value [`SymCryptError::SignatureVerificationFailure`]. 82 | pub fn ecdsa_verify( 83 | &self, 84 | signature: &[u8], 85 | hashed_message: &[u8], 86 | ) -> Result<(), SymCryptError> { 87 | // SymCrypt code does not support EcDsa operations with Curve25519. 88 | // SymCrypt does not panic here, but to remain consistent with ecdsa_sign, returning SymCryptError::InvalidArgument 89 | if self.get_curve_type() == CurveType::Curve25519 { 90 | return Err(SymCryptError::InvalidArgument); 91 | } 92 | unsafe { 93 | // SAFETY: FFI calls 94 | match symcrypt_sys::SymCryptEcDsaVerify( 95 | self.inner_key(), 96 | hashed_message.as_ptr(), 97 | hashed_message.len() as symcrypt_sys::SIZE_T, 98 | signature.as_ptr(), 99 | signature.len() as symcrypt_sys::SIZE_T, 100 | curve_to_num_format(self.curve_type), // Derive number format from curve type 101 | 0, 102 | ) { 103 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_NO_ERROR => Ok(()), 104 | err => Err(err.into()), 105 | } 106 | } 107 | } 108 | } 109 | 110 | #[cfg(test)] 111 | mod tests { 112 | use super::*; 113 | use crate::ecc::CurveType; 114 | use crate::ecc::EcKeyUsage; 115 | 116 | #[test] 117 | fn test_ecdsa_sign_and_verify_same_key_p256() { 118 | let key = EcKey::generate_key_pair(CurveType::NistP256, EcKeyUsage::EcDsa).unwrap(); 119 | 120 | let hash_value = hex::decode("4d55c99ef6bd54621662c3d110c3cb627c03d6311393b264ab97b90a4b15214a5593ba2510a53d63fb34be251facb697c973e11b665cb7920f1684b0031b4dd370cb927ca7168b0bf8ad285e05e9e31e34bc24024739fdc10b78586f29eff94412034e3b606ed850ec2c1900e8e68151fc4aee5adebb066eb6da4eaa5681378e").unwrap(); 121 | 122 | let signature = key.ecdsa_sign(&hash_value).unwrap(); 123 | 124 | let verify_result = key.ecdsa_verify(&signature, &hash_value); 125 | assert!(verify_result.is_ok()); 126 | } 127 | 128 | #[test] 129 | fn test_ecdsa_sign_and_verify_same_key_p384() { 130 | let key = EcKey::generate_key_pair(CurveType::NistP384, EcKeyUsage::EcDsa).unwrap(); 131 | 132 | let hash_value = hex::decode("4d55c99ef6bd54621662c3d110c3cb627c03d6311393b264ab97b90a4b15214a5593ba2510a53d63fb34be251facb697c973e11b665cb7920f1684b0031b4dd370cb927ca7168b0bf8ad285e05e9e31e34bc24024739fdc10b78586f29eff94412034e3b606ed850ec2c1900e8e68151fc4aee5adebb066eb6da4eaa5681378e").unwrap(); 133 | 134 | let signature = key.ecdsa_sign(&hash_value).unwrap(); 135 | 136 | let verify_result = key.ecdsa_verify(&signature, &hash_value); 137 | assert!(verify_result.is_ok()); 138 | } 139 | 140 | #[test] 141 | fn test_ecdsa_sign_and_verify_same_key_p521() { 142 | let key = EcKey::generate_key_pair(CurveType::NistP521, EcKeyUsage::EcDsa).unwrap(); 143 | 144 | let hash_value = hex::decode("4d55c99ef6bd54621662c3d110c3cb627c03d6311393b264ab97b90a4b15214a5593ba2510a53d63fb34be251facb697c973e11b665cb7920f1684b0031b4dd370cb927ca7168b0bf8ad285e05e9e31e34bc24024739fdc10b78586f29eff94412034e3b606ed850ec2c1900e8e68151fc4aee5adebb066eb6da4eaa5681378e").unwrap(); 145 | 146 | let signature = key.ecdsa_sign(&hash_value).unwrap(); 147 | 148 | let verify_result = key.ecdsa_verify(&signature, &hash_value); 149 | assert!(verify_result.is_ok()); 150 | } 151 | 152 | #[test] 153 | fn test_ecsda_sign_with_25519_failure() { 154 | let key = EcKey::generate_key_pair(CurveType::Curve25519, EcKeyUsage::EcDsa).unwrap(); 155 | let hash_value = hex::decode("4d55c99ef6bd54621662c3d110c3cb627c03d6311393b264ab97b90a4b15214a5593ba2510a53d63fb34be251facb697c973e11b665cb7920f1684b0031b4dd370cb927ca7168b0bf8ad285e05e9e31e34bc24024739fdc10b78586f29eff94412034e3b606ed850ec2c1900e8e68151fc4aee5adebb066eb6da4eaa5681378e").unwrap(); 156 | 157 | let signature = key.ecdsa_sign(&hash_value).unwrap_err(); 158 | assert!(signature == SymCryptError::InvalidArgument); 159 | } 160 | 161 | #[test] 162 | fn test_ecsda_verify_with_25519_failure() { 163 | let key = EcKey::generate_key_pair(CurveType::Curve25519, EcKeyUsage::EcDsa).unwrap(); 164 | let hash_value = hex::decode("4d55c99ef6bd54621662c3d110c3cb627c03d6311393b264ab97b90a4b15214a5593ba2510a53d63fb34be251facb697c973e11b665cb7920f1684b0031b4dd370cb927ca7168b0bf8ad285e05e9e31e34bc24024739fdc10b78586f29eff94412034e3b606ed850ec2c1900e8e68151fc4aee5adebb066eb6da4eaa5681378e").unwrap(); 165 | 166 | let key2 = EcKey::generate_key_pair(CurveType::NistP256, EcKeyUsage::EcDsa).unwrap(); 167 | let signature = key2.ecdsa_sign(&hash_value).unwrap(); 168 | 169 | let result = key.ecdsa_verify(&signature, &hash_value).unwrap_err(); 170 | assert!(result == SymCryptError::InvalidArgument); 171 | } 172 | 173 | #[test] 174 | fn test_ecdsa_sign_and_verify_wrong_key_usage() { 175 | let key = EcKey::generate_key_pair(CurveType::NistP256, EcKeyUsage::EcDh).unwrap(); 176 | let hash_value = hex::decode("4d55c99ef6bd54621662c3d110c3cb627c03d6311393b264ab97b90a4b15214a5593ba2510a53d63fb34be251facb697c973e11b665cb7920f1684b0031b4dd370cb927ca7168b0bf8ad285e05e9e31e34bc24024739fdc10b78586f29eff94412034e3b606ed850ec2c1900e8e68151fc4aee5adebb066eb6da4eaa5681378e").unwrap(); 177 | let result = key.ecdsa_sign(&hash_value).unwrap_err(); 178 | assert_eq!(result, SymCryptError::InvalidArgument); 179 | } 180 | 181 | #[test] 182 | fn test_ecdsa_verify_wrong_key_usage() { 183 | let hash_value = hex::decode("4d55c99ef6bd54621662c3d110c3cb627c03d6311393b264ab97b90a4b15214a5593ba2510a53d63fb34be251facb697c973e11b665cb7920f1684b0031b4dd370cb927ca7168b0bf8ad285e05e9e31e34bc24024739fdc10b78586f29eff94412034e3b606ed850ec2c1900e8e68151fc4aee5adebb066eb6da4eaa5681378e").unwrap(); 184 | 185 | let key = EcKey::generate_key_pair(CurveType::NistP256, EcKeyUsage::EcDsa).unwrap(); 186 | let signature = key.ecdsa_sign(&hash_value).unwrap(); 187 | 188 | let key2 = EcKey::generate_key_pair(CurveType::NistP256, EcKeyUsage::EcDh).unwrap(); 189 | let result = key2.ecdsa_verify(&signature, &hash_value).unwrap_err(); 190 | assert_eq!(result, SymCryptError::SignatureVerificationFailure); 191 | } 192 | 193 | #[test] 194 | fn test_ecdsa_sign_and_verify_ecdsa_and_ecdh() { 195 | let key = EcKey::generate_key_pair(CurveType::NistP256, EcKeyUsage::EcDhAndEcDsa).unwrap(); 196 | let hash_value = hex::decode("4d55c99ef6bd54621662c3d110c3cb627c03d6311393b264ab97b90a4b15214a5593ba2510a53d63fb34be251facb697c973e11b665cb7920f1684b0031b4dd370cb927ca7168b0bf8ad285e05e9e31e34bc24024739fdc10b78586f29eff94412034e3b606ed850ec2c1900e8e68151fc4aee5adebb066eb6da4eaa5681378e").unwrap(); 197 | let verify_result = key.ecdsa_sign(&hash_value); 198 | assert!(verify_result.is_ok()); 199 | } 200 | 201 | #[test] 202 | fn test_ecdsa_sign_and_verify_different_key() { 203 | let key = EcKey::generate_key_pair(CurveType::NistP256, EcKeyUsage::EcDsa).unwrap(); 204 | let hash_value = hex::decode("4d55c99ef6bd54621662c3d110c3cb627c03d6311393b264ab97b90a4b15214a5593ba2510a53d63fb34be251facb697c973e11b665cb7920f1684b0031b4dd370cb927ca7168b0bf8ad285e05e9e31e34bc24024739fdc10b78586f29eff94412034e3b606ed850ec2c1900e8e68151fc4aee5adebb066eb6da4eaa5681378e").unwrap(); 205 | let signature = key.ecdsa_sign(&hash_value).unwrap(); 206 | 207 | let key2 = EcKey::generate_key_pair(CurveType::NistP256, EcKeyUsage::EcDsa).unwrap(); 208 | 209 | let result = key2.ecdsa_verify(&signature, &hash_value).unwrap_err(); 210 | assert_eq!(result, SymCryptError::SignatureVerificationFailure); 211 | } 212 | 213 | #[test] 214 | fn test_ecdsa_sign_without_private_key() { 215 | let key = EcKey::generate_key_pair(CurveType::NistP256, EcKeyUsage::EcDsa).unwrap(); 216 | let hash_value = hex::decode("4d55c99ef6bd54621662c3d110c3cb627c03d6311393b264ab97b90a4b15214a5593ba2510a53d63fb34be251facb697c973e11b665cb7920f1684b0031b4dd370cb927ca7168b0bf8ad285e05e9e31e34bc24024739fdc10b78586f29eff94412034e3b606ed850ec2c1900e8e68151fc4aee5adebb066eb6da4eaa5681378e").unwrap(); 217 | let public_key_bytes = key.export_public_key().unwrap(); 218 | let key = EcKey::set_public_key(CurveType::NistP256, &public_key_bytes, EcKeyUsage::EcDsa) 219 | .unwrap(); 220 | let result = key.ecdsa_sign(&hash_value); 221 | assert_eq!(result, Err(SymCryptError::InvalidArgument)); 222 | } 223 | 224 | #[test] 225 | fn test_ecdsa_sign_and_verify_different_curve_type() { 226 | let key = EcKey::generate_key_pair(CurveType::NistP256, EcKeyUsage::EcDsa).unwrap(); 227 | 228 | let hash_value = hex::decode("4d55c99ef6bd54621662c3d110c3cb627c03d6311393b264ab97b90a4b15214a5593ba2510a53d63fb34be251facb697c973e11b665cb7920f1684b0031b4dd370cb927ca7168b0bf8ad285e05e9e31e34bc24024739fdc10b78586f29eff94412034e3b606ed850ec2c1900e8e68151fc4aee5adebb066eb6da4eaa5681378e").unwrap(); 229 | 230 | let _signature = key.ecdsa_sign(&hash_value).unwrap(); 231 | 232 | let public_key_bytes = key.export_public_key().unwrap(); 233 | 234 | let key2 = 235 | EcKey::set_public_key(CurveType::Curve25519, &public_key_bytes, EcKeyUsage::EcDsa) 236 | .unwrap_err(); 237 | assert_eq!(key2, SymCryptError::InvalidArgument); 238 | } 239 | 240 | #[test] 241 | fn test_ecdsa_sign_with_imported_key() { 242 | let private_key = 243 | hex::decode("b20d705d9bd7c2b8dc60393a5357f632990e599a0975573ac67fd89b49187906") 244 | .unwrap(); 245 | let key = EcKey::set_key_pair(CurveType::NistP256, &private_key, None, EcKeyUsage::EcDsa) 246 | .unwrap(); 247 | 248 | let hash_value = hex::decode("4d55c99ef6bd54621662c3d110c3cb627c03d6311393b264ab97b90a4b15214a5593ba2510a53d63fb34be251facb697c973e11b665cb7920f1684b0031b4dd370cb927ca7168b0bf8ad285e05e9e31e34bc24024739fdc10b78586f29eff94412034e3b606ed850ec2c1900e8e68151fc4aee5adebb066eb6da4eaa5681378e").unwrap(); 249 | 250 | let signature = key.ecdsa_sign(&hash_value).unwrap(); 251 | 252 | let verify_result = key.ecdsa_verify(&signature, &hash_value); 253 | assert!(verify_result.is_ok()); 254 | } 255 | } 256 | -------------------------------------------------------------------------------- /rust-symcrypt/src/errors.rs: -------------------------------------------------------------------------------- 1 | //! Friendly rust errors for SYMCRYPT_ERROR. For more info on SYMCRYPT_ERRORS please refer to symcrypt.h 2 | 3 | use std::convert::From; 4 | use std::fmt; 5 | use symcrypt_sys; 6 | 7 | /// `SymCryptError` is an enum that enumerates all of the errors from `SymCrypt`. 8 | #[non_exhaustive] 9 | #[derive(Debug, PartialEq)] 10 | pub enum SymCryptError { 11 | NoError, 12 | Unused, 13 | WrongKeySize, 14 | WrongBlockSize, 15 | WrongDataSize, 16 | WrongNonceSize, 17 | WrongTagSize, 18 | WrongIterationCount, 19 | AuthenticationFailure, 20 | ExternalFailure, 21 | FipsFailure, 22 | HardwareFailure, 23 | NotImplemented, 24 | InvalidBlob, 25 | BufferTooSmall, 26 | InvalidArgument, 27 | MemoryAllocationFailure, 28 | SignatureVerificationFailure, 29 | IncompatibleFormat, 30 | ValueTooLarge, 31 | SessionReplayFailure, 32 | UnknownError(symcrypt_sys::SYMCRYPT_ERROR), // Catch-all for unknown error codes 33 | } 34 | 35 | /// Matches raw `SymCrypt` error to the [`SymCryptError`] enum. 36 | impl From for SymCryptError { 37 | fn from(err: symcrypt_sys::SYMCRYPT_ERROR) -> Self { 38 | match err { 39 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_NO_ERROR => SymCryptError::NoError, 40 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_UNUSED => SymCryptError::Unused, 41 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_WRONG_KEY_SIZE => SymCryptError::WrongKeySize, 42 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_WRONG_BLOCK_SIZE => SymCryptError::WrongBlockSize, 43 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_WRONG_DATA_SIZE => SymCryptError::WrongDataSize, 44 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_WRONG_NONCE_SIZE => SymCryptError::WrongNonceSize, 45 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_WRONG_TAG_SIZE => SymCryptError::WrongTagSize, 46 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_WRONG_ITERATION_COUNT => { 47 | SymCryptError::WrongIterationCount 48 | } 49 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_AUTHENTICATION_FAILURE => { 50 | SymCryptError::AuthenticationFailure 51 | } 52 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_EXTERNAL_FAILURE => { 53 | SymCryptError::ExternalFailure 54 | } 55 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_FIPS_FAILURE => SymCryptError::FipsFailure, 56 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_HARDWARE_FAILURE => { 57 | SymCryptError::HardwareFailure 58 | } 59 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_NOT_IMPLEMENTED => SymCryptError::NotImplemented, 60 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_INVALID_BLOB => SymCryptError::InvalidBlob, 61 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_BUFFER_TOO_SMALL => SymCryptError::BufferTooSmall, 62 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_INVALID_ARGUMENT => { 63 | SymCryptError::InvalidArgument 64 | } 65 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_MEMORY_ALLOCATION_FAILURE => { 66 | SymCryptError::MemoryAllocationFailure 67 | } 68 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_SIGNATURE_VERIFICATION_FAILURE => { 69 | SymCryptError::SignatureVerificationFailure 70 | } 71 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_INCOMPATIBLE_FORMAT => { 72 | SymCryptError::IncompatibleFormat 73 | } 74 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_VALUE_TOO_LARGE => SymCryptError::ValueTooLarge, 75 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_SESSION_REPLAY_FAILURE => { 76 | SymCryptError::SessionReplayFailure 77 | } 78 | _ => SymCryptError::UnknownError(err), 79 | } 80 | } 81 | } 82 | 83 | /// Implements `Display` for the [`SymCryptError`] enum to allow for better print usage. 84 | impl fmt::Display for SymCryptError { 85 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 86 | let message = match *self { 87 | SymCryptError::NoError => "No error", 88 | SymCryptError::Unused => "Unused", 89 | SymCryptError::WrongKeySize => "Wrong key size", 90 | SymCryptError::WrongBlockSize => "Wrong block size", 91 | SymCryptError::WrongDataSize => "Wrong data size", 92 | SymCryptError::WrongNonceSize => "Wrong nonce size", 93 | SymCryptError::WrongTagSize => "Wrong tag size", 94 | SymCryptError::WrongIterationCount => "Wrong iteration count", 95 | SymCryptError::AuthenticationFailure => "Authentication failure", 96 | SymCryptError::ExternalFailure => "External failure", 97 | SymCryptError::FipsFailure => "FIPS failure", 98 | SymCryptError::HardwareFailure => "Hardware failure", 99 | SymCryptError::NotImplemented => "Not implemented", 100 | SymCryptError::InvalidBlob => "Invalid blob", 101 | SymCryptError::BufferTooSmall => "Buffer too small", 102 | SymCryptError::InvalidArgument => "Invalid argument", 103 | SymCryptError::MemoryAllocationFailure => "Memory allocation failure", 104 | SymCryptError::SignatureVerificationFailure => "Signature verification failure", 105 | SymCryptError::IncompatibleFormat => "Incompatible format", 106 | SymCryptError::ValueTooLarge => "Value too large", 107 | SymCryptError::SessionReplayFailure => "Session replay failure", 108 | SymCryptError::UnknownError(code) => return write!(f, "Unknown error: {}", code), 109 | }; 110 | write!(f, "{}", message) 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /rust-symcrypt/src/gcm.rs: -------------------------------------------------------------------------------- 1 | //! Galois Counter Mode functions. For further documentation please refer to symcrypt.h 2 | //! 3 | //! # Examples 4 | //! 5 | //! ## Encrypt in place 6 | //! 7 | //! ```rust 8 | //! use symcrypt::cipher::BlockCipherType; 9 | //! use symcrypt::gcm::GcmExpandedKey; 10 | //! 11 | //! // Set up input 12 | //! let p_key = hex::decode("feffe9928665731c6d6a8f9467308308").unwrap(); 13 | //! let mut tag = [0u8; 16]; 14 | //! 15 | //! let mut nonce_array = [0u8; 12]; 16 | //! hex::decode_to_slice("cafebabefacedbaddecaf888", &mut nonce_array).unwrap(); 17 | //! 18 | //! let auth_data = hex::decode("feedfacedeadbeeffeedfacedeadbeefabaddad2").unwrap(); 19 | //! let expected_result = "42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091"; 20 | //! 21 | //! let mut buffer = [0u8; 60]; 22 | //! hex::decode_to_slice("d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", &mut buffer).unwrap(); 23 | //! 24 | //! let expected_tag = "5bc94fbc3221a5db94fae95ae7121a47"; 25 | //! let cipher = BlockCipherType::AesBlock; 26 | //! 27 | //! // Perform encryption in place 28 | //! let gcm_state = GcmExpandedKey::new(&p_key, cipher).unwrap(); 29 | //! gcm_state.encrypt_in_place(&nonce_array, &auth_data, &mut buffer, &mut tag); 30 | //! 31 | //! assert_eq!(hex::encode(buffer), expected_result); 32 | //! assert_eq!(hex::encode(tag), expected_tag); 33 | //! ``` 34 | //! 35 | //! ## Decrypt in place 36 | //! ```rust 37 | //! use symcrypt::cipher::BlockCipherType; 38 | //! use symcrypt::gcm::GcmExpandedKey; 39 | //! 40 | //! // Set up input 41 | //! let p_key = hex::decode("feffe9928665731c6d6a8f9467308308").unwrap(); 42 | //! let cipher = BlockCipherType::AesBlock; 43 | //! 44 | //! let mut nonce_array = [0u8; 12]; 45 | //! hex::decode_to_slice("cafebabefacedbaddecaf888", &mut nonce_array).unwrap(); 46 | //! 47 | //! let auth_data = hex::decode("feedfacedeadbeeffeedfacedeadbeefabaddad2").unwrap(); 48 | //! let expected_result = "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39"; 49 | 50 | //! let mut tag = [0u8; 16]; 51 | //! hex::decode_to_slice("5bc94fbc3221a5db94fae95ae7121a47", &mut tag).unwrap(); 52 | 53 | //! let mut buffer = [0u8; 60]; 54 | //! hex::decode_to_slice("42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091", &mut buffer).unwrap(); 55 | //! 56 | //! // Perform the decryption in place 57 | //! let gcm_state = GcmExpandedKey::new(&p_key, cipher).unwrap(); 58 | //! gcm_state 59 | //! .decrypt_in_place(&nonce_array, &auth_data, &mut buffer, &mut tag) 60 | //! .unwrap(); 61 | //! assert_eq!(hex::encode(buffer), expected_result); 62 | //! ``` 63 | //! 64 | use crate::cipher::{convert_cipher, BlockCipherType}; 65 | use crate::errors::SymCryptError; 66 | use crate::symcrypt_init; 67 | use core::ffi::c_void; 68 | use std::marker::PhantomPinned; 69 | use std::mem; 70 | use std::pin::Pin; 71 | use std::ptr; 72 | use symcrypt_sys; 73 | 74 | /// [`GcmExpandedKey`] is a struct that holds the Gcm expanded key from SymCrypt. 75 | pub struct GcmExpandedKey { 76 | // expanded_key holds the key from SymCrypt which is Pin>'d since the memory address for Self is moved around when 77 | // returning from GcmExpandedKey::new() 78 | 79 | // key_length holds the length of the expanded key. This value is normally 16 or 32 bytes. 80 | 81 | // SymCrypt expects the address for its structs to stay static through the structs lifetime to guarantee that structs are not memcpy'd as 82 | // doing so would lead to use-after-free and inconsistent states. 83 | expanded_key: Pin>, 84 | key_length: usize, 85 | } 86 | 87 | /// [`GcmInnerKey`] is a struct that holds the underlying SymCrypt state for GCM. 88 | struct GcmInnerKey { 89 | // inner represents the actual state of the hash from SymCrypt 90 | inner: symcrypt_sys::SYMCRYPT_GCM_EXPANDED_KEY, 91 | 92 | // _pinned is a marker to ensure that instances of the inner state cannot be moved once pinned. 93 | // This prevents the struct from implementing the Unpin trait, enforcing that any 94 | // references to this structure remain valid throughout its lifetime. 95 | _pinned: PhantomPinned, 96 | } 97 | 98 | impl GcmInnerKey { 99 | /// Creates a new GcmInnerKey and returns a pinned Box 100 | fn new() -> Pin> { 101 | Box::pin(GcmInnerKey { 102 | inner: symcrypt_sys::SYMCRYPT_GCM_EXPANDED_KEY::default(), 103 | _pinned: PhantomPinned, 104 | }) 105 | } 106 | 107 | /// Provides a mutable pointer to the inner SymCrypt state. 108 | /// 109 | /// This is primarily meant to be used while making calls to the underlying SymCrypt APIs. 110 | /// The pointer returned is pinned and cannot be moved 111 | /// This function returns pointer to pinned data, which means callers must not use the pointer to move the data out of its location. 112 | fn get_inner_mut(self: Pin<&mut Self>) -> *mut symcrypt_sys::SYMCRYPT_GCM_EXPANDED_KEY { 113 | // SAFETY: Accessing the inner state of the pinned data 114 | unsafe { &mut self.get_unchecked_mut().inner as *mut _ } 115 | } 116 | 117 | // Safe method to access the inner state immutably 118 | pub(crate) fn get_inner(&self) -> *const symcrypt_sys::SYMCRYPT_GCM_EXPANDED_KEY { 119 | &self.inner as *const _ 120 | } 121 | } 122 | 123 | impl Drop for GcmInnerKey { 124 | fn drop(&mut self) { 125 | unsafe { 126 | // SAFETY: FFI calls 127 | symcrypt_sys::SymCryptWipe( 128 | ptr::addr_of_mut!(self.inner) as *mut c_void, // Using addr_of_mut! so we don't access in the inner field 129 | mem::size_of_val(&self.inner) as symcrypt_sys::SIZE_T, // Using size_of_val! so we don't access in the inner field 130 | ); 131 | } 132 | } 133 | } 134 | 135 | /// `encrypt_in_place` and `decrypt_in_place` take in an allocated `buffer` as an in/out parameter for performance reasons. 136 | /// This is for scenarios such as encrypting over a stream of data; allocating and copying data from a return will be costly performance wise. 137 | impl GcmExpandedKey { 138 | /// `new` takes in a reference to a key and a [`BlockCipherType`] and returns an expanded key that is Pin>'d. 139 | /// 140 | /// This function can fail and will propagate the error back to the caller. This call will fail if the wrong key size is provided. 141 | /// 142 | /// The only accepted Cipher for GCM is [`BlockCipherType::AesBlock`] 143 | pub fn new(key: &[u8], cipher: BlockCipherType) -> Result { 144 | symcrypt_init(); 145 | let mut expanded_key = GcmInnerKey::new(); // Get expanded_key that is already Pin>'d 146 | 147 | // Use as_mut() to get a Pin<&mut GcmInnerKey> and then call get_inner_mut to get *mut 148 | gcm_expand_key( 149 | key, 150 | expanded_key.as_mut().get_inner_mut(), 151 | convert_cipher(cipher), 152 | )?; 153 | let gcm_expanded_key = GcmExpandedKey { 154 | expanded_key, 155 | key_length: key.len(), 156 | }; 157 | Ok(gcm_expanded_key) 158 | } 159 | 160 | /// `encrypt_in_place` performs an in-place encryption on the `&mut buffer` that is passed. This call cannot fail. 161 | /// 162 | /// `nonce` is a `&[u8; 12]` that is used as the nonce for the encryption. 163 | /// 164 | /// `auth_data` is an optional `&[u8]` that can be provided, if you do not wish to provide any auth data, input an empty array. 165 | /// 166 | /// `buffer` is a `&mut [u8]` that contains the plain text data to be encrypted. After the encryption has been completed, 167 | /// `buffer` will be over-written to contain the cipher text data. 168 | /// 169 | /// `tag` is a `&mut [u8]` which is the buffer where the resulting tag will be written to. Tag size must be 12, 13, 14, 15, 16 per SP800-38D. 170 | /// Tag sizes of 4 and 8 are not supported. 171 | pub fn encrypt_in_place( 172 | &self, 173 | nonce: &[u8; 12], 174 | auth_data: &[u8], 175 | buffer: &mut [u8], 176 | tag: &mut [u8], 177 | ) { 178 | symcrypt_init(); 179 | unsafe { 180 | // SAFETY: FFI calls 181 | symcrypt_sys::SymCryptGcmEncrypt( 182 | self.expanded_key.get_inner(), 183 | nonce.as_ptr(), 184 | nonce.len() as symcrypt_sys::SIZE_T, 185 | auth_data.as_ptr(), 186 | auth_data.len() as symcrypt_sys::SIZE_T, 187 | buffer.as_ptr(), 188 | buffer.as_mut_ptr(), 189 | buffer.len() as symcrypt_sys::SIZE_T, 190 | tag.as_mut_ptr(), 191 | tag.len() as symcrypt_sys::SIZE_T, 192 | ); 193 | } 194 | } 195 | 196 | /// `decrypt_in_place` performs an in-place decryption on the `&mut buffer` that is passed. This call can fail and the caller must check the result. 197 | /// 198 | /// `nonce` is a `&[u8; 12]` that is used as the nonce for the decryption. It must match the nonce used during encryption. 199 | /// 200 | /// `auth_data` is an optional `&[u8]` that can be provided. If you do not wish to provide any auth data, input an empty array. 201 | /// 202 | /// `buffer` is a `&mut [u8]` that contains the cipher text data to be decrypted. After the decryption has been completed, 203 | /// `buffer` will be over-written to contain the plain text data. 204 | /// 205 | /// `tag` is a `&[u8]` that contains the authentication tag generated during encryption. This is used to verify the integrity of the cipher text. 206 | /// 207 | /// If decryption succeeds, the function will return `Ok(())`, and `buffer` will contain the plain text. If it fails, an error of type `SymCryptError` will be returned. 208 | pub fn decrypt_in_place( 209 | &self, 210 | nonce: &[u8; 12], 211 | auth_data: &[u8], 212 | buffer: &mut [u8], 213 | tag: &[u8], 214 | ) -> Result<(), SymCryptError> { 215 | symcrypt_init(); 216 | unsafe { 217 | // SAFETY: FFI calls 218 | match symcrypt_sys::SymCryptGcmDecrypt( 219 | self.expanded_key.get_inner(), 220 | nonce.as_ptr(), 221 | nonce.len() as symcrypt_sys::SIZE_T, 222 | auth_data.as_ptr(), 223 | auth_data.len() as symcrypt_sys::SIZE_T, 224 | buffer.as_ptr(), 225 | buffer.as_mut_ptr(), 226 | buffer.len() as symcrypt_sys::SIZE_T, 227 | tag.as_ptr(), 228 | tag.len() as symcrypt_sys::SIZE_T, 229 | ) { 230 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_NO_ERROR => Ok(()), 231 | err => Err(err.into()), 232 | } 233 | } 234 | } 235 | 236 | /// `key_len` returns a the length of the [`GcmExpandedKey`] as a `usize`. 237 | pub fn key_len(&self) -> usize { 238 | self.key_length 239 | } 240 | } 241 | 242 | // No custom Send / Sync impl. needed for GcmExpandedKey since the 243 | // underlying data is a pointer to a SymCrypt struct that is not modified after it is created. 244 | unsafe impl Send for GcmExpandedKey {} 245 | unsafe impl Sync for GcmExpandedKey {} 246 | 247 | // Internal function to expand the SymCrypt Gcm Key. 248 | fn gcm_expand_key( 249 | key: &[u8], 250 | expanded_key: *mut symcrypt_sys::SYMCRYPT_GCM_EXPANDED_KEY, 251 | cipher: *const symcrypt_sys::SYMCRYPT_BLOCKCIPHER, 252 | ) -> Result<(), SymCryptError> { 253 | unsafe { 254 | // SAFETY: FFI calls 255 | match symcrypt_sys::SymCryptGcmExpandKey( 256 | expanded_key, 257 | cipher, 258 | key.as_ptr(), 259 | key.len() as symcrypt_sys::SIZE_T, 260 | ) { 261 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_NO_ERROR => Ok(()), 262 | err => Err(err.into()), 263 | } 264 | } 265 | } 266 | 267 | /// [`validate_gcm_parameters`] is a utility function that validates the input parameters for a GCM call. 268 | /// 269 | /// `cipher` will only accept [`BlockCipherType::AesBlock`] 270 | /// 271 | /// `nonce` is a `&[u8; 12]` that represents a nonce array. 272 | /// 273 | /// `auth_data` is an optional `&[u8]` that can be provided, if you do not wish to provide 274 | /// any auth data, input an empty array. 275 | /// 276 | /// `data` is a `&[u8]` that represents the data array to be encrypted 277 | /// 278 | /// `tag` is a `&[u8]` that represents the tag buffer, the size of the tag buffer will be checked and must be 12, 13, 14, 15, 16 per SP800-38D. 279 | /// Tag sizes of 4 and 8 are not supported. 280 | pub fn validate_gcm_parameters( 281 | cipher: BlockCipherType, 282 | nonce: &[u8; 12], // GCM nonce length must be 12 bytes 283 | auth_data: &[u8], 284 | data: &[u8], 285 | tag: &[u8], 286 | ) -> Result<(), SymCryptError> { 287 | unsafe { 288 | // SAFETY: FFI calls 289 | match symcrypt_sys::SymCryptGcmValidateParameters( 290 | convert_cipher(cipher), 291 | nonce.len() as symcrypt_sys::SIZE_T, 292 | auth_data.len() as symcrypt_sys::UINT64, 293 | data.len() as symcrypt_sys::UINT64, 294 | tag.len() as symcrypt_sys::SIZE_T, 295 | ) { 296 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_NO_ERROR => Ok(()), 297 | err => Err(err.into()), 298 | } 299 | } 300 | } 301 | 302 | #[cfg(test)] 303 | mod test { 304 | use super::*; 305 | use crate::cipher::BlockCipherType; 306 | 307 | #[test] 308 | fn test_gcm_expand_key_will_fail_wrong_key_size() { 309 | let p_key = hex::decode("feffe9928665731c6d6a8f9467308308ad").unwrap(); 310 | let cipher = BlockCipherType::AesBlock; 311 | 312 | let result = GcmExpandedKey::new(&p_key, cipher); 313 | 314 | match result { 315 | Ok(_) => { 316 | panic!("Test passed when it should fail"); 317 | } 318 | Err(err) => { 319 | assert_eq!(err, SymCryptError::WrongKeySize); 320 | } 321 | } 322 | } 323 | 324 | #[test] 325 | fn test_gcm_encrypt() { 326 | let p_key = hex::decode("feffe9928665731c6d6a8f9467308308").unwrap(); 327 | let mut nonce_array = [0u8; 12]; 328 | hex::decode_to_slice("cafebabefacedbaddecaf888", &mut nonce_array).unwrap(); 329 | let auth_data = hex::decode("feedfacedeadbeeffeedfacedeadbeefabaddad2").unwrap(); 330 | let expected_result = "42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091"; 331 | 332 | let mut buffer = [0u8; 60]; 333 | hex::decode_to_slice("d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39", &mut buffer).unwrap(); 334 | 335 | let mut tag = [0u8; 16]; 336 | 337 | let expected_tag = "5bc94fbc3221a5db94fae95ae7121a47"; 338 | let cipher = BlockCipherType::AesBlock; 339 | 340 | let gcm_state = GcmExpandedKey::new(&p_key, cipher).unwrap(); 341 | gcm_state.encrypt_in_place(&nonce_array, &auth_data, &mut buffer, &mut tag); 342 | 343 | assert_eq!(hex::encode(buffer), expected_result); 344 | assert_eq!(hex::encode(tag), expected_tag); 345 | } 346 | 347 | #[test] 348 | fn test_gcm_decrypt() { 349 | let p_key = hex::decode("feffe9928665731c6d6a8f9467308308").unwrap(); 350 | let mut nonce_array = [0u8; 12]; 351 | hex::decode_to_slice("cafebabefacedbaddecaf888", &mut nonce_array).unwrap(); 352 | let auth_data = hex::decode("feedfacedeadbeeffeedfacedeadbeefabaddad2").unwrap(); 353 | let expected_result = "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39"; 354 | 355 | let mut tag = [0u8; 16]; 356 | hex::decode_to_slice("5bc94fbc3221a5db94fae95ae7121a47", &mut tag).unwrap(); 357 | 358 | let mut buffer = [0u8; 60]; 359 | hex::decode_to_slice("42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091", &mut buffer).unwrap(); 360 | let cipher = BlockCipherType::AesBlock; 361 | 362 | let gcm_state = GcmExpandedKey::new(&p_key, cipher).unwrap(); 363 | gcm_state 364 | .decrypt_in_place(&nonce_array, &auth_data, &mut buffer, &tag) 365 | .unwrap(); 366 | assert_eq!(hex::encode(buffer), expected_result); 367 | } 368 | 369 | #[test] 370 | fn test_gcm_decrypt_will_fail_wrong_tag() { 371 | let p_key = hex::decode("feffe9928665731c6d6a8f9467308308").unwrap(); 372 | let mut nonce_array = [0u8; 12]; 373 | hex::decode_to_slice("cafebabefacedbaddecaf888", &mut nonce_array).unwrap(); 374 | let auth_data = hex::decode("feedfacedeadbeeffeedfacedeadbeefabaddad2").unwrap(); 375 | 376 | let mut tag = [0u8; 16]; 377 | hex::decode_to_slice("5bc94fbc3221a5db94fae95ae7121aaa", &mut tag).unwrap(); 378 | 379 | let mut buffer = [0u8; 60]; 380 | hex::decode_to_slice("42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091", &mut buffer).unwrap(); 381 | let cipher = BlockCipherType::AesBlock; 382 | 383 | let gcm_state = GcmExpandedKey::new(&p_key, cipher).unwrap(); 384 | let result = gcm_state.decrypt_in_place(&nonce_array, &auth_data, &mut buffer, &tag); 385 | 386 | match result { 387 | Ok(_) => { 388 | panic!("Test passed when it should fail"); 389 | } 390 | Err(err) => { 391 | assert_eq!(err, SymCryptError::AuthenticationFailure); 392 | } 393 | } 394 | } 395 | 396 | #[test] 397 | fn test_validate_parameters() { 398 | let mut nonce_array = [0u8; 12]; 399 | hex::decode_to_slice("cafebabefacedbaddecaf888", &mut nonce_array).unwrap(); 400 | let auth_data = hex::decode("feedfacedeadbeeffeedfacedeadbeefabaddad2").unwrap(); 401 | let expected_tag = hex::decode("5bc94fbc3221a5db94fae95ae7121a47").unwrap(); 402 | let pt = hex::decode("d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39").unwrap(); 403 | let cipher = BlockCipherType::AesBlock; 404 | 405 | validate_gcm_parameters(cipher, &nonce_array, &auth_data, &pt, &expected_tag).unwrap(); 406 | } 407 | 408 | #[test] 409 | fn test_validate_parameters_fail() { 410 | let mut nonce_array = [0u8; 12]; 411 | hex::decode_to_slice("cafebabefacedbaddecaf888", &mut nonce_array).unwrap(); 412 | let auth_data = hex::decode("feedfacedeadbeeffeedfacedeadbeefabaddad2").unwrap(); 413 | let expected_tag = hex::decode("5bc94fbc3242121a47").unwrap(); 414 | let pt = hex::decode("d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39").unwrap(); 415 | let cipher = BlockCipherType::AesBlock; 416 | 417 | let result = validate_gcm_parameters(cipher, &nonce_array, &auth_data, &pt, &expected_tag); 418 | assert_eq!(result.unwrap_err(), SymCryptError::WrongTagSize); 419 | } 420 | 421 | #[test] 422 | fn test_gcm_expanded_key_get_key_length() { 423 | let p_key = hex::decode("feffe9928665731c6d6a8f9467308308").unwrap(); 424 | let cipher = BlockCipherType::AesBlock; 425 | let gcm_state = GcmExpandedKey::new(&p_key, cipher).unwrap(); 426 | assert_eq!(gcm_state.key_len(), 16); 427 | } 428 | } 429 | -------------------------------------------------------------------------------- /rust-symcrypt/src/hkdf.rs: -------------------------------------------------------------------------------- 1 | //! HKDF functions. For more info please refer to symcrypt.h 2 | //! 3 | //! # Example 4 | //! 5 | //! ## Hkdf with Sha256 and with no salt or info 6 | //! 7 | //! ```rust 8 | //! use symcrypt::hkdf::hkdf; 9 | //! use symcrypt::hmac::HmacAlgorithm; 10 | //! use hex::*; 11 | //! 12 | //! // Setup initial keying material (IKM) 13 | //! let key_material = hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(); 14 | //! let hmac_algorithm = HmacAlgorithm::HmacSha256; 15 | //! 16 | //! let expected = "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8"; 17 | //! let res = hkdf(hmac_algorithm, &key_material, &[], &[], 42).unwrap(); 18 | //! assert_eq!(res.len(), 42); 19 | //! assert_eq!(expected, hex::encode(res)); 20 | //! ``` 21 | //! 22 | //! ## Hkdf with Sha384 and with salt and info 23 | //! 24 | //! ```rust 25 | //! use symcrypt::hkdf::hkdf; 26 | //! use symcrypt::hmac::HmacAlgorithm; 27 | //! use hex::*; 28 | //! 29 | //! // Setup initial keying material (IKM) 30 | //! let key_material = hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(); 31 | //! let salt = hex::decode("000102030405060708090a0b0c").unwrap(); 32 | //! let info = hex::decode("f0f1f2f3f4f5f6f7f8f9").unwrap(); 33 | //! let hmac_algorithm = HmacAlgorithm::HmacSha384; 34 | //! 35 | //! let expected = "9b5097a86038b805309076a44b3a9f38063e25b516dcbf369f394cfab43685f748b6457763e4f0204fc5"; 36 | //! let res = hkdf(hmac_algorithm, &key_material, &salt, &info, 42).unwrap(); 37 | //! assert_eq!(res.len(), 42); 38 | //! assert_eq!(expected, hex::encode(res)); 39 | //! ``` 40 | //! 41 | use crate::errors::SymCryptError; 42 | use crate::hmac::HmacAlgorithm; 43 | use symcrypt_sys; 44 | 45 | /// `hkdf()` derives a key using the HKDF algorithm and returns a `Vec`, or a [`SymCryptError`] if the operation fails. 46 | /// 47 | /// `hmac_algorithm` is an [`HmacAlgorithm`] that represents the HMAC algorithm to use. 48 | /// 49 | /// `key_material` is a `&[u8]` that represents the initial keying material (IKM), which is the primary source input for HKDF. 50 | /// 51 | /// `salt` is a `&[u8]` that represents an optional, random value used to enhance security during the extraction phase. If you do not 52 | /// wish to use a salt, you can pass an empty slice. 53 | /// 54 | /// `info` is a `&[u8]` that represents an optional application-specific context that customizes the derived key. If you do not 55 | /// wish to use a salt, you can pass an empty slice. 56 | /// 57 | /// `output_key_size` is an `u64` that represents the desired length of the derived key in bytes, the `Vec` returned will be of this length. 58 | pub fn hkdf( 59 | hmac_algorithm: HmacAlgorithm, 60 | key_material: &[u8], 61 | salt: &[u8], 62 | info: &[u8], 63 | output_key_size: u64, 64 | ) -> Result, SymCryptError> { 65 | let mut hmac_res = vec![0u8; output_key_size as usize]; 66 | unsafe { 67 | // UNSAFE: FFI calls 68 | match symcrypt_sys::SymCryptHkdf( 69 | hmac_algorithm.to_symcrypt_hmac_algorithm(), 70 | key_material.as_ptr(), 71 | key_material.len() as symcrypt_sys::SIZE_T, 72 | salt.as_ptr(), 73 | salt.len() as symcrypt_sys::SIZE_T, 74 | info.as_ptr(), 75 | info.len() as symcrypt_sys::SIZE_T, 76 | hmac_res.as_mut_ptr(), 77 | hmac_res.len() as symcrypt_sys::SIZE_T, 78 | ) { 79 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_NO_ERROR => Ok(hmac_res), 80 | err => Err(SymCryptError::from(err)), 81 | } 82 | } 83 | } 84 | 85 | #[cfg(test)] 86 | mod test { 87 | use super::*; 88 | use hex; 89 | 90 | #[test] 91 | fn test_hkdf_256() { 92 | let key_material = hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(); 93 | let salt = hex::decode("000102030405060708090a0b0c").unwrap(); 94 | let info = hex::decode("f0f1f2f3f4f5f6f7f8f9").unwrap(); 95 | let hmac_algorithm = HmacAlgorithm::HmacSha256; 96 | 97 | let expected = 98 | "3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865"; 99 | let res = hkdf(hmac_algorithm, &key_material, &salt, &info, 42).unwrap(); 100 | assert_eq!(res.len(), 42); 101 | assert_eq!(expected, hex::encode(res)); 102 | } 103 | 104 | #[test] 105 | fn test_hkdf_256_very_small_output_key_size() { 106 | let key_material = hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(); 107 | let salt = hex::decode("000102030405060708090a0b0c").unwrap(); 108 | let info = hex::decode("f0f1f2f3f4f5f6f7f8f9").unwrap(); 109 | let hmac_algorithm = HmacAlgorithm::HmacSha256; 110 | 111 | let expected = "3cb25f25faac"; 112 | let res = hkdf(hmac_algorithm, &key_material, &salt, &info, 6).unwrap(); 113 | assert_eq!(res.len(), 6); 114 | assert_eq!(expected, hex::encode(res)); 115 | } 116 | 117 | #[test] 118 | fn test_hkdf_sha256_long() { 119 | let key_material = hex::decode("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f").unwrap(); 120 | let salt = hex::decode("606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeaf").unwrap(); 121 | let info = hex::decode("b0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff").unwrap(); 122 | let hmac_algorithm = HmacAlgorithm::HmacSha256; 123 | 124 | let expected = "b11e398dc80327a1c8e7f78c596a49344f012eda2d4efad8a050cc4c19afa97c59045a99cac7827271cb41c65e590e09da3275600c2f09b8367793a9aca3db71cc30c58179ec3e87c14c01d5c1f3434f1d87"; 125 | let res = hkdf(hmac_algorithm, &key_material, &salt, &info, 82).unwrap(); 126 | assert_eq!(res.len(), 82); 127 | assert_eq!(expected, hex::encode(res)); 128 | } 129 | 130 | #[test] 131 | fn test_hkdf_sha256_no_salt_no_info() { 132 | let key_material = hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(); 133 | let hmac_algorithm = HmacAlgorithm::HmacSha256; 134 | 135 | let expected = 136 | "8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8"; 137 | let res = hkdf(hmac_algorithm, &key_material, &[], &[], 42).unwrap(); 138 | assert_eq!(res.len(), 42); 139 | assert_eq!(expected, hex::encode(res)); 140 | } 141 | 142 | #[test] 143 | fn test_hkdf_sha384() { 144 | let key_material = hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(); 145 | let salt = hex::decode("000102030405060708090a0b0c").unwrap(); 146 | let info = hex::decode("f0f1f2f3f4f5f6f7f8f9").unwrap(); 147 | let hmac_algorithm = HmacAlgorithm::HmacSha384; 148 | 149 | let expected = 150 | "9b5097a86038b805309076a44b3a9f38063e25b516dcbf369f394cfab43685f748b6457763e4f0204fc5"; 151 | let res = hkdf(hmac_algorithm, &key_material, &salt, &info, 42).unwrap(); 152 | assert_eq!(res.len(), 42); 153 | assert_eq!(expected, hex::encode(res)); 154 | } 155 | 156 | #[test] 157 | fn test_hkdf_sha512() { 158 | let key_material = hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(); 159 | let salt = hex::decode("000102030405060708090a0b0c").unwrap(); 160 | let info = hex::decode("f0f1f2f3f4f5f6f7f8f9").unwrap(); 161 | let hmac_algorithm = HmacAlgorithm::HmacSha512; 162 | 163 | let expected = 164 | "832390086cda71fb47625bb5ceb168e4c8e26a1a16ed34d9fc7fe92c1481579338da362cb8d9f925d7cb"; 165 | let res = hkdf(hmac_algorithm, &key_material, &salt, &info, 42).unwrap(); 166 | assert_eq!(res.len(), 42); 167 | assert_eq!(expected, hex::encode(res)); 168 | } 169 | 170 | #[cfg(feature = "sha1")] 171 | #[test] 172 | fn test_hkdf_sha1() { 173 | let key_material = hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(); 174 | let salt = hex::decode("000102030405060708090a0b0c").unwrap(); 175 | let info = hex::decode("f0f1f2f3f4f5f6f7f8f9").unwrap(); 176 | let hmac_algorithm = HmacAlgorithm::HmacSha1; 177 | 178 | let expected = 179 | "d6000ffb5b50bd3970b260017798fb9c8df9ce2e2c16b6cd709cca07dc3cf9cf26d6c6d750d0aaf5ac94"; 180 | let res = hkdf(hmac_algorithm, &key_material, &salt, &info, 42).unwrap(); 181 | assert_eq!(res.len(), 42); 182 | assert_eq!(expected, hex::encode(res)); 183 | } 184 | 185 | #[cfg(feature = "md5")] 186 | #[test] 187 | fn test_hkdf_md5() { 188 | let key_material = hex::decode("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b").unwrap(); 189 | let salt = hex::decode("000102030405060708090a0b0c").unwrap(); 190 | let info = hex::decode("f0f1f2f3f4f5f6f7f8f9").unwrap(); 191 | let hmac_algorithm = HmacAlgorithm::HmacMd5; 192 | 193 | let expected = 194 | "b222c9db38d17b2fea8b3bb511c0d6d86049ef481ba7065ca5c6422618ed9cc9144900e2c72b6a863a31"; 195 | let res = hkdf(hmac_algorithm, &key_material, &salt, &info, 42).unwrap(); 196 | assert_eq!(res.len(), 42); 197 | assert_eq!(expected, hex::encode(res)); 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /rust-symcrypt/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | use std::sync::Once; 3 | 4 | pub mod chacha; 5 | pub mod cipher; 6 | pub mod ecc; 7 | pub mod errors; 8 | pub mod gcm; 9 | pub mod hash; 10 | pub mod hkdf; 11 | pub mod hmac; 12 | pub mod rsa; 13 | 14 | // symcrypt_init must be called before any other API can be called. All subsequent calls to symcrypt_init will be no-ops 15 | fn symcrypt_init() { 16 | // Subsequent calls to `symcrypt_init()` after the first will not be invoked per .call_once docs https://doc.rust-lang.org/std/sync/struct.Once.html 17 | static INIT: Once = Once::new(); 18 | 19 | // `symcrypt_init` calls `SymCryptModuleInit` or `SymCryptInit` depending on the feature flag 20 | // We have also set feature flags on the bindings themselves to only expose the functions we need. 21 | // This is to try and eliminate footguns like calling SymCryptModuleInit on a statically linked module. 22 | unsafe { 23 | // SAFETY: FFI calls, blocking from being run again. 24 | 25 | #[cfg(feature = "dynamic")] 26 | INIT.call_once(|| { 27 | symcrypt_sys::SymCryptModuleInit( 28 | symcrypt_sys::SYMCRYPT_CODE_VERSION_API, 29 | symcrypt_sys::SYMCRYPT_CODE_VERSION_MINOR, 30 | ) 31 | }); 32 | 33 | #[cfg(not(feature = "dynamic"))] 34 | INIT.call_once(|| { 35 | symcrypt_sys::SymCryptInit(); 36 | }); 37 | } 38 | } 39 | 40 | /// Takes in a buffer called `buff` and fills it with random bytes. This function 41 | /// is never expected to fail, but failure (due to OS dependencies) will crash the application. 42 | /// There is no recoverable failure mode. 43 | /// 44 | /// If calling `symcrypt_random` with a dynamically linked module, `SymCryptRandom` will be called. 45 | /// 46 | /// If calling `symcrypt_random` with a statically linked module, `SymCryptCallbackRandom` will be called. 47 | pub fn symcrypt_random(buff: &mut [u8]) { 48 | symcrypt_init(); 49 | 50 | // `symcrypt_random` calls `SymCryptRandom` or `SymCryptCallbackRandom` depending on the feature flag 51 | // We have also set feature flags on the bindings themselves to only expose the functions we need. 52 | // This is to try and eliminate footguns like calling SymCryptRandom on a statically linked module. 53 | unsafe { 54 | // SAFETY: FFI call 55 | 56 | // Call SymCryptRandom for dynamic linking 57 | #[cfg(feature = "dynamic")] 58 | symcrypt_sys::SymCryptRandom(buff.as_mut_ptr(), buff.len() as symcrypt_sys::SIZE_T); 59 | 60 | // Call SymCryptCallbackRandom for static linking 61 | #[cfg(not(feature = "dynamic"))] 62 | symcrypt_sys::SymCryptCallbackRandom(buff.as_mut_ptr(), buff.len() as symcrypt_sys::SIZE_T); 63 | } 64 | } 65 | 66 | /// `NumberFormat` is an enum that contains a friendly representation of endianess 67 | /// 68 | /// `LSB`: Bytes are ordered from the least significant to the most significant, commonly referred to as "little-endian". 69 | /// 70 | /// `MSB`: Bytes are ordered from the most significant to the least significant, commonly referred to as "big-endian". 71 | pub enum NumberFormat { 72 | LSB, 73 | MSB, 74 | } 75 | 76 | impl NumberFormat { 77 | /// Converts `NumberFormat` to the corresponding `SYMCRYPT_NUMBER_FORMAT` 78 | fn to_symcrypt_format(&self) -> symcrypt_sys::SYMCRYPT_NUMBER_FORMAT { 79 | match self { 80 | NumberFormat::LSB => { 81 | symcrypt_sys::_SYMCRYPT_NUMBER_FORMAT_SYMCRYPT_NUMBER_FORMAT_LSB_FIRST 82 | } 83 | NumberFormat::MSB => { 84 | symcrypt_sys::_SYMCRYPT_NUMBER_FORMAT_SYMCRYPT_NUMBER_FORMAT_MSB_FIRST 85 | } 86 | } 87 | } 88 | } 89 | 90 | #[cfg(test)] 91 | mod test { 92 | use super::*; 93 | 94 | #[test] 95 | fn test_symcrypt_random() { 96 | let mut buff_1 = [0u8; 10]; 97 | let mut buff_2 = [0u8; 10]; 98 | 99 | symcrypt_random(&mut buff_1); 100 | symcrypt_random(&mut buff_2); 101 | 102 | assert_ne!(buff_1, buff_2); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /rust-symcrypt/src/rsa/oaep.rs: -------------------------------------------------------------------------------- 1 | //! OAEP functions for [`RsaKey`]. For more info please refer to symcrypt.h 2 | //! 3 | //! # Example 4 | //! 5 | //! ## Encrypt and Decrypt with [`RsaKey`]. 6 | //! ```rust 7 | //! use symcrypt::rsa::{RsaKey, RsaKeyUsage}; 8 | //! use symcrypt::hash::HashAlgorithm; 9 | //! 10 | //! // Generate a new RSA key pair 11 | //! let key_pair = RsaKey::generate_key_pair(2048, None, RsaKeyUsage::SignAndEncrypt).unwrap(); 12 | //! 13 | //! // Set up message to encrypt 14 | //! let message = b"example message"; 15 | //! let hash_algorithm = HashAlgorithm::Sha256; 16 | //! let label = b"label"; 17 | //! 18 | //! // Encrypt the message 19 | //! let encrypted_message = key_pair.oaep_encrypt(message, hash_algorithm, label).unwrap(); 20 | //! 21 | //! // Decrypt the message and verify it matches the original message 22 | //! let decrypted_message = key_pair.oaep_decrypt(&encrypted_message, hash_algorithm, label).unwrap(); 23 | //! assert_eq!(decrypted_message, message); 24 | //! ``` 25 | //! 26 | use crate::errors::SymCryptError; 27 | use crate::hash::HashAlgorithm; 28 | use crate::rsa::RsaKey; 29 | use crate::NumberFormat; 30 | 31 | impl RsaKey { 32 | /// `oaep_decrypt()` returns a decrypted message as a `Vec`, or a [`SymCryptError`] if the operation fails. 33 | /// 34 | /// `encrypted_message` is a `&[u8]` that represents the message to decrypt. 35 | /// 36 | /// `hash_algorithm` is a [`HashAlgorithm`] that represents the hash algorithm to use. 37 | /// 38 | /// `label` is a `&[u8]` that represents the label to use. 39 | /// 40 | /// This function will fail with [`SymCryptError::InvalidArgument`] if [`RsaKey`] does not have a private key attached. 41 | pub fn oaep_decrypt( 42 | &self, 43 | encrypted_message: &[u8], 44 | hash_algorithm: HashAlgorithm, 45 | label: &[u8], 46 | ) -> Result, SymCryptError> { 47 | let mut result_size: symcrypt_sys::SIZE_T = 0; 48 | let modulus_size = self.get_size_of_modulus(); 49 | let mut decrypted_buffer = vec![0u8; modulus_size as usize]; 50 | let hash_algorithm_ptr = hash_algorithm.get_symcrypt_hash(); 51 | 52 | unsafe { 53 | // SAFETY: FFI calls 54 | match symcrypt_sys::SymCryptRsaOaepDecrypt( 55 | self.inner(), 56 | encrypted_message.as_ptr(), 57 | encrypted_message.len() as symcrypt_sys::SIZE_T, 58 | NumberFormat::MSB.to_symcrypt_format(), 59 | hash_algorithm_ptr, 60 | label.as_ptr(), 61 | label.len() as symcrypt_sys::SIZE_T, 62 | 0, // flags must be 0 63 | decrypted_buffer.as_mut_ptr(), 64 | modulus_size as symcrypt_sys::SIZE_T, 65 | &mut result_size, 66 | ) { 67 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_NO_ERROR => { 68 | // SymCrypt fills the buffer with info and returns the size of the decrypted data in result_size 69 | // for the caller to decide if they wish to truncate the buffer to the actual size of the decrypted data. 70 | // Max size for the buffer is the size of the modulus. 71 | decrypted_buffer.truncate(result_size as usize); 72 | Ok(decrypted_buffer) 73 | } 74 | err => Err(err.into()), 75 | } 76 | } 77 | } 78 | 79 | /// `oaep_encrypt()` returns an encrypted message as a `Vec`, or a [`SymCryptError`] if the operation fails. 80 | /// 81 | /// `message` is a `&[u8]` that represents the message to encrypt. 82 | /// 83 | /// `hash_algorithm` is a [`HashAlgorithm`] that represents the hash algorithm to use. 84 | /// 85 | /// `label` is a `&[u8]` that represents the label to use. 86 | pub fn oaep_encrypt( 87 | &self, 88 | message: &[u8], 89 | hash_algorithm: HashAlgorithm, 90 | label: &[u8], 91 | ) -> Result, SymCryptError> { 92 | let mut result_size: symcrypt_sys::SIZE_T = 0; 93 | let modulus_size = self.get_size_of_modulus(); 94 | let mut encrypted = vec![0u8; modulus_size as usize]; 95 | let hash_algorithm_ptr = hash_algorithm.get_symcrypt_hash(); 96 | 97 | unsafe { 98 | // SAFETY: FFI calls 99 | match symcrypt_sys::SymCryptRsaOaepEncrypt( 100 | self.inner(), 101 | message.as_ptr(), 102 | message.len() as symcrypt_sys::SIZE_T, 103 | hash_algorithm_ptr, 104 | label.as_ptr(), 105 | label.len() as symcrypt_sys::SIZE_T, 106 | 0, // flags must be 0 107 | NumberFormat::MSB.to_symcrypt_format(), 108 | encrypted.as_mut_ptr(), 109 | modulus_size as symcrypt_sys::SIZE_T, 110 | &mut result_size, 111 | ) { 112 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_NO_ERROR => Ok(encrypted), 113 | err => Err(err.into()), 114 | } 115 | } 116 | } 117 | } 118 | 119 | #[cfg(test)] 120 | mod tests { 121 | use crate::errors::SymCryptError; 122 | use crate::hash::HashAlgorithm; 123 | use crate::rsa::{RsaKey, RsaKeyUsage}; 124 | 125 | #[test] 126 | fn test_oaep_encrypt_decrypt() { 127 | let key_pair = RsaKey::generate_key_pair(2048, None, RsaKeyUsage::SignAndEncrypt).unwrap(); 128 | 129 | let message = b"example message"; 130 | let hash_algorithm = HashAlgorithm::Sha256; 131 | let label = b"label"; 132 | 133 | let encrypted_message = key_pair 134 | .oaep_encrypt(message, hash_algorithm, label) 135 | .unwrap(); 136 | let decrypted_message = key_pair 137 | .oaep_decrypt(&encrypted_message, hash_algorithm, label) 138 | .unwrap(); 139 | 140 | assert_eq!(decrypted_message, message); 141 | } 142 | 143 | #[test] 144 | fn test_oaep_encrypt_with_public_key() { 145 | let key_pair = RsaKey::generate_key_pair(2048, None, RsaKeyUsage::SignAndEncrypt).unwrap(); 146 | 147 | let public_key_blob = key_pair.export_public_key_blob().unwrap(); 148 | let public_key = RsaKey::set_public_key( 149 | &public_key_blob.modulus, 150 | &public_key_blob.pub_exp, 151 | RsaKeyUsage::Encrypt, 152 | ) 153 | .unwrap(); 154 | 155 | // Message to encrypt 156 | let message = b"example message"; 157 | let hash_algorithm = HashAlgorithm::Sha256; 158 | let label = b"label"; 159 | 160 | let encrypted_message = public_key 161 | .oaep_encrypt(message, hash_algorithm, label) 162 | .unwrap(); 163 | 164 | let decrypted_message = key_pair 165 | .oaep_decrypt(&encrypted_message, hash_algorithm, label) 166 | .unwrap(); 167 | 168 | assert_eq!(decrypted_message, message); 169 | } 170 | 171 | #[test] 172 | fn test_oaep_encrypt_decrypt_with_empty_label() { 173 | let key_pair = RsaKey::generate_key_pair(2048, None, RsaKeyUsage::SignAndEncrypt).unwrap(); 174 | 175 | // Message to encrypt 176 | let message = b"example message"; 177 | let hash_algorithm = HashAlgorithm::Sha256; 178 | let label = b""; 179 | 180 | let encrypted_message = key_pair 181 | .oaep_encrypt(message, hash_algorithm, label) 182 | .unwrap(); 183 | let decrypted_message = key_pair 184 | .oaep_decrypt(&encrypted_message, hash_algorithm, label) 185 | .unwrap(); 186 | 187 | assert_eq!(decrypted_message, message); 188 | } 189 | 190 | #[test] 191 | fn test_oaep_encrypt_decrypt_large_message() { 192 | let key_pair = RsaKey::generate_key_pair(2048, None, RsaKeyUsage::SignAndEncrypt).unwrap(); 193 | 194 | let encrypted_message = [0u8; 1000]; 195 | let hash_algorithm = HashAlgorithm::Sha256; 196 | let label = b""; 197 | 198 | let decrypted_message = key_pair 199 | .oaep_decrypt(&encrypted_message, hash_algorithm, label) 200 | .unwrap_err(); 201 | 202 | assert_eq!(decrypted_message, SymCryptError::InvalidArgument); 203 | } 204 | 205 | #[test] 206 | fn test_oaep_encrypt_with_wrong_key_usage() { 207 | let key_pair = RsaKey::generate_key_pair(2048, None, RsaKeyUsage::Sign).unwrap(); 208 | 209 | let message = b"example message"; 210 | let hash_algorithm = HashAlgorithm::Sha256; 211 | let label = b"label"; 212 | 213 | let result = key_pair 214 | .oaep_encrypt(message, hash_algorithm, label) 215 | .unwrap_err(); 216 | assert_eq!(result, SymCryptError::InvalidArgument); 217 | } 218 | 219 | #[test] 220 | fn test_oaep_decrypt_with_public_key() { 221 | let key_pair = RsaKey::generate_key_pair(2048, None, RsaKeyUsage::SignAndEncrypt).unwrap(); 222 | 223 | let public_key_blob = key_pair.export_public_key_blob().unwrap(); 224 | let public_key = RsaKey::set_public_key( 225 | &public_key_blob.modulus, 226 | &public_key_blob.pub_exp, 227 | RsaKeyUsage::Encrypt, 228 | ) 229 | .unwrap(); 230 | 231 | let result = public_key 232 | .oaep_decrypt(&[0u8; 1000], HashAlgorithm::Sha256, b"label") 233 | .unwrap_err(); 234 | assert_eq!(result, SymCryptError::InvalidArgument); 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /rust-symcrypt/src/rsa/pkcs1.rs: -------------------------------------------------------------------------------- 1 | //! PKCS1 functions for [`RsaKey`]. For more info please refer to symcrypt.h 2 | //! 3 | //! # Examples 4 | //! 5 | //! ## Sign and Verify using RsaKey 6 | //! 7 | //! ```rust 8 | //! use symcrypt::rsa::{RsaKey, RsaKeyUsage}; 9 | //! use symcrypt::hash::{sha256, HashAlgorithm}; 10 | //! 11 | //! // Generate key pair. 12 | //! let key_pair = RsaKey::generate_key_pair(2048, None, RsaKeyUsage::SignAndEncrypt).unwrap(); 13 | //! 14 | //! // Set up message. 15 | //! let hashed_message = sha256(b"hello world"); 16 | //! let hash_algorithm = HashAlgorithm::Sha256; 17 | //! 18 | //! // Create signature. 19 | //! let signature = key_pair.pkcs1_sign(&hashed_message, hash_algorithm).unwrap(); 20 | //! 21 | //! // Create Public Key to verify signature. 22 | //! let public_key_blob = key_pair.export_public_key_blob().unwrap(); 23 | //! let public_key = RsaKey::set_public_key(&public_key_blob.modulus, &public_key_blob.pub_exp, RsaKeyUsage::SignAndEncrypt).unwrap(); 24 | //! 25 | //! // Verify signature. 26 | //! let verify_result = public_key.pkcs1_verify(&hashed_message, &signature, hash_algorithm); 27 | //! assert!(verify_result.is_ok()); 28 | //! ``` 29 | //! 30 | //! ## Encrypt and Decrypt using RsaKey 31 | //! Pkcs1 Encrypt and Decrypt functions are considered weak crypto and are only available when the `pkcs1-encrypt-decrypt` feature is enabled. 32 | //! 33 | //! In your `Cargo.toml` 34 | //! 35 | //! `symcrypt = {version = "0.4.0", features = ["pkcs1-encrypt-decrypt"]}` 36 | //! 37 | //! ```rust 38 | //! #[cfg(feature = "pkcs1-encrypt-decrypt")] 39 | //! { 40 | //! use symcrypt::rsa::{RsaKey, RsaKeyUsage}; 41 | //! use symcrypt::errors::SymCryptError; 42 | //! 43 | //! // Generate key pair. 44 | //! let key_pair = RsaKey::generate_key_pair(2048, None, RsaKeyUsage::SignAndEncrypt).unwrap(); 45 | //! 46 | //! // Set up message. 47 | //! let message = b"example message"; 48 | //! 49 | //! // Encrypt message. 50 | //! let encrypted_message = key_pair.pkcs1_encrypt(message).unwrap(); 51 | //! 52 | //! // Decrypt message. 53 | //! let mut plaintext_buffer = vec![0u8; encrypted_message.len()]; // Buffer must be large enough to store the decrypted message. 54 | //! let mut result_size = 0; 55 | //! let res = key_pair.pkcs1_decrypt(&encrypted_message, &mut plaintext_buffer, &mut result_size); 56 | //! 57 | //! // Check if decryption was successful. 58 | //! assert_eq!(res, SymCryptError::NoError); 59 | //! 60 | //! // Truncate buffer to the size of the decrypted message. 61 | //! plaintext_buffer.truncate(result_size as usize); 62 | //! assert_eq!(plaintext_buffer, message); 63 | //! } 64 | //! ``` 65 | //! 66 | use crate::errors::SymCryptError; 67 | use crate::hash::HashAlgorithm; 68 | use crate::rsa::RsaKey; 69 | #[cfg(feature = "pkcs1-encrypt-decrypt")] 70 | use crate::symcrypt_random; 71 | use crate::NumberFormat; 72 | 73 | /// Impl for Pkcs1 RSA via [`RsaKey`] 74 | impl RsaKey { 75 | /// `pcks1_sign()` signs a hashed message using the private key of [`RsaKey`] and returns a `Vec` representing the signature, 76 | /// or a [`SymCryptError`] if the operation fails. 77 | /// 78 | /// `hashed_message` is a `&[u8]` that represents the message that has been hashed using the hash algorithm specified in `hash_algorithm`. 79 | /// 80 | /// `hash_algorithm` is a [`HashAlgorithm`] representing the hash algorithm used to hash the message. 81 | /// 82 | /// This function will fail with [`SymCryptError::InvalidArgument`] if [`RsaKey`] does not have a private key attached. 83 | pub fn pkcs1_sign( 84 | &self, 85 | hashed_message: &[u8], 86 | hash_algorithm: HashAlgorithm, 87 | ) -> Result, SymCryptError> { 88 | let mut result_size = 0; 89 | let modulus_size = self.get_size_of_modulus(); 90 | let mut signature = vec![0u8; modulus_size as usize]; 91 | let converted_hash_oids = hash_algorithm.get_oid_list(); 92 | unsafe { 93 | // SAFETY: FFI calls 94 | match symcrypt_sys::SymCryptRsaPkcs1Sign( 95 | self.inner(), 96 | hashed_message.as_ptr(), 97 | hashed_message.len() as symcrypt_sys::SIZE_T, 98 | converted_hash_oids.as_ptr(), 99 | converted_hash_oids.len() as symcrypt_sys::SIZE_T, 100 | 0, // Setting ASN.1 OID in previous parameters. 101 | NumberFormat::MSB.to_symcrypt_format(), 102 | signature.as_mut_ptr(), 103 | modulus_size as symcrypt_sys::SIZE_T, 104 | &mut result_size, 105 | ) { 106 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_NO_ERROR => Ok(signature), 107 | err => Err(err.into()), 108 | } 109 | } 110 | } 111 | 112 | /// `pcks1_decrypt()` decrypts an encrypted message using the private key of [`RsaKey`] and returns [`SymCryptError`] which the caller must check before continuing. 113 | /// 114 | /// Pkcs1 decryption is weak crypto, is very fragile, and susceptible to padding attacks. It is recommended to use `RsaKey::oaep_decrypt` instead. 115 | /// Pkcs1 decryption should only be used for compatibility with legacy systems, to enable pkcs1 decryption, enable the `pkcs1-encrypt-decrypt` feature. 116 | /// 117 | /// `encrypted_message` is a `&[u8]` representing the encrypted message to be decrypted. 118 | /// 119 | /// `plaintext_buffer` takes in a `&mut [u8]` representing a mutable buffer to store the decrypted message. The buffer will be filled with the decrypted message if the operation is successful. 120 | /// This buffer will be randomized before `pcks1_decrypt` is called, you must extract your result from the buffer via the `result_size` parameter if the operation is successful. 121 | /// The size of the buffer should should be large enough to store the result of the decrypted message, if the buffer is to small, this function will return a [`SymCryptError::BufferTooSmall`]. 122 | /// 123 | /// `result_size` is a `&mut usize` representing the size of the decrypted message. This value will be set to the size of the decrypted message if the operation is successful. 124 | /// 125 | /// This function will always return a `SymCryptError`, caller must check the return value to determine if the decryption was successful before using the `plaintext_buffer` or `result_size`. 126 | #[cfg(feature = "pkcs1-encrypt-decrypt")] 127 | pub fn pkcs1_decrypt( 128 | &self, 129 | encrypted_message: &[u8], 130 | plaintext_buffer: &mut [u8], 131 | result_size: &mut usize, 132 | ) -> SymCryptError { 133 | symcrypt_random(plaintext_buffer); 134 | 135 | // Create a local SIZE_T variable to hold the result size 136 | let mut internal_result_size: symcrypt_sys::SIZE_T = 0; 137 | 138 | let error = unsafe { 139 | // SAFETY: FFI call with appropriate type casting 140 | symcrypt_sys::SymCryptRsaPkcs1Decrypt( 141 | self.inner(), 142 | encrypted_message.as_ptr(), 143 | encrypted_message.len() as symcrypt_sys::SIZE_T, 144 | NumberFormat::MSB.to_symcrypt_format(), 145 | 0, // No flags can be set 146 | plaintext_buffer.as_mut_ptr(), 147 | plaintext_buffer.len() as symcrypt_sys::SIZE_T, 148 | &mut internal_result_size as *mut symcrypt_sys::SIZE_T, 149 | ) 150 | }; 151 | // Convert internal_result_size to usize and assign it to result_size 152 | *result_size = internal_result_size as usize; 153 | 154 | error.into() 155 | } 156 | 157 | /// `pkcs1_verify()` returns a [`SymCryptError`] if the signature verification fails and `Ok(())` if the verification is successful. 158 | /// 159 | /// Caller must check the return value to determine if the signature is valid before continuing. 160 | /// 161 | /// `hashed_message` is a `&[u8]` that represents the message that has been hashed using the hash algorithm specified in `hash_algorithm`. 162 | /// 163 | /// `signature` is a `&[u8]` representing the signature to be verified. 164 | /// 165 | /// `hash_algorithm` is a [`HashAlgorithm`] representing the hash algorithm used to hash the message. 166 | pub fn pkcs1_verify( 167 | &self, 168 | hashed_message: &[u8], 169 | signature: &[u8], 170 | hash_algorithm: HashAlgorithm, 171 | ) -> Result<(), SymCryptError> { 172 | let converted_hash_oids = hash_algorithm.get_oid_list(); 173 | unsafe { 174 | // SAFETY: FFI calls 175 | match symcrypt_sys::SymCryptRsaPkcs1Verify( 176 | self.inner(), 177 | hashed_message.as_ptr(), 178 | hashed_message.len() as symcrypt_sys::SIZE_T, 179 | signature.as_ptr(), 180 | signature.len() as symcrypt_sys::SIZE_T, 181 | NumberFormat::MSB.to_symcrypt_format(), // Only MSB is supported 182 | converted_hash_oids.as_ptr(), 183 | converted_hash_oids.len() as symcrypt_sys::SIZE_T, 184 | 0, 185 | ) { 186 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_NO_ERROR => Ok(()), 187 | err => Err(err.into()), 188 | } 189 | } 190 | } 191 | 192 | /// `pkcs1_encrypt()` encrypts a message using the public key of the key pair and returns a `Vec` representing the encrypted message, 193 | /// or a [`SymCryptError`] if the operation fails. 194 | /// 195 | /// pkcs1 encryption is considered weak crypto, and should only be used for compatibility with legacy systems. To enable pkcs1 encryption, enable the `pkcs1-encrypt-decrypt` feature. 196 | /// 197 | /// `message` is a `&[u8]` representing the message to be encrypted. 198 | #[cfg(feature = "pkcs1-encrypt-decrypt")] 199 | pub fn pkcs1_encrypt(&self, message: &[u8]) -> Result, SymCryptError> { 200 | let mut result_size = 0; 201 | let size_of_modulus = self.get_size_of_modulus(); 202 | let mut encrypted_buffer = vec![0u8; size_of_modulus as usize]; 203 | unsafe { 204 | // SAFETY: FFI calls 205 | match symcrypt_sys::SymCryptRsaPkcs1Encrypt( 206 | self.inner(), 207 | message.as_ptr(), 208 | message.len() as symcrypt_sys::SIZE_T, 209 | 0, // No flags can be set 210 | NumberFormat::MSB.to_symcrypt_format(), 211 | encrypted_buffer.as_mut_ptr(), 212 | size_of_modulus as symcrypt_sys::SIZE_T, 213 | &mut result_size, 214 | ) { 215 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_NO_ERROR => Ok(encrypted_buffer), 216 | err => Err(err.into()), 217 | } 218 | } 219 | } 220 | } 221 | 222 | #[cfg(test)] 223 | mod tests { 224 | use super::*; 225 | use crate::hash::{sha256, HashAlgorithm}; 226 | use crate::rsa::{RsaKey, RsaKeyUsage}; 227 | 228 | #[test] 229 | fn test_pkcs1_sign_verify() { 230 | let key_pair = RsaKey::generate_key_pair(2048, None, RsaKeyUsage::SignAndEncrypt).unwrap(); 231 | 232 | let public_key_blob = key_pair.export_public_key_blob().unwrap(); 233 | let public_key = RsaKey::set_public_key( 234 | &public_key_blob.modulus, 235 | &public_key_blob.pub_exp, 236 | RsaKeyUsage::SignAndEncrypt, 237 | ) 238 | .unwrap(); 239 | 240 | let hashed_message = sha256(b"hello world"); 241 | let hash_algorithm = HashAlgorithm::Sha256; 242 | 243 | let signature = key_pair 244 | .pkcs1_sign(&hashed_message, hash_algorithm) 245 | .unwrap(); 246 | let verify_result = public_key.pkcs1_verify(&hashed_message, &signature, hash_algorithm); 247 | 248 | assert!(verify_result.is_ok()); 249 | } 250 | 251 | #[test] 252 | #[cfg(feature = "pkcs1-encrypt-decrypt")] 253 | fn test_pkcs1_encrypt_decrypt() { 254 | let key_pair = RsaKey::generate_key_pair(2048, None, RsaKeyUsage::SignAndEncrypt).unwrap(); 255 | let message = b"example message"; 256 | 257 | let encrypted_message = key_pair.pkcs1_encrypt(message).unwrap(); 258 | 259 | let mut plaintext_buffer = vec![0u8; encrypted_message.len()]; 260 | let mut result_size = 0; 261 | let res = 262 | key_pair.pkcs1_decrypt(&encrypted_message, &mut plaintext_buffer, &mut result_size); 263 | assert_eq!(res, SymCryptError::NoError); 264 | plaintext_buffer.truncate(result_size); 265 | assert_eq!(plaintext_buffer, message); 266 | } 267 | 268 | #[test] 269 | #[cfg(feature = "pkcs1-encrypt-decrypt")] 270 | fn test_pkcs1_encrypt_decrypt_big_buffer() { 271 | let key_pair = RsaKey::generate_key_pair(2048, None, RsaKeyUsage::SignAndEncrypt).unwrap(); 272 | let message = b"example message"; 273 | 274 | let encrypted_message = key_pair.pkcs1_encrypt(message).unwrap(); 275 | 276 | let mut plaintext_buffer = vec![0u8; 100]; // very large buffer in relation to the provided message 277 | let mut result_size = 0; 278 | let res = 279 | key_pair.pkcs1_decrypt(&encrypted_message, &mut plaintext_buffer, &mut result_size); 280 | assert_eq!(res, SymCryptError::NoError); 281 | assert_eq!(result_size, message.len()); 282 | assert_eq!(plaintext_buffer.len(), 100); 283 | plaintext_buffer.truncate(result_size); 284 | assert_eq!(plaintext_buffer, message); 285 | } 286 | 287 | #[test] 288 | #[cfg(feature = "pkcs1-encrypt-decrypt")] 289 | fn test_pkcs1_encrypt_decrypt_buffer_too_small() { 290 | let key_pair = RsaKey::generate_key_pair(2048, None, RsaKeyUsage::SignAndEncrypt).unwrap(); 291 | let message = b"example message"; 292 | 293 | let encrypted_message = key_pair.pkcs1_encrypt(message).unwrap(); 294 | 295 | let mut plaintext_buffer = vec![0u8; 5]; // buffer must be at least the size of the message. 296 | let mut result_size = 0; 297 | let res = 298 | key_pair.pkcs1_decrypt(&encrypted_message, &mut plaintext_buffer, &mut result_size); 299 | assert_eq!(res, SymCryptError::BufferTooSmall); 300 | plaintext_buffer.truncate(result_size); 301 | assert_ne!(plaintext_buffer, message); 302 | } 303 | 304 | #[test] 305 | #[cfg(feature = "pkcs1-encrypt-decrypt")] 306 | fn test_pkcs1_encrypt_with_public_key() { 307 | let key_pair = RsaKey::generate_key_pair(2048, None, RsaKeyUsage::SignAndEncrypt).unwrap(); 308 | let message = b"example message"; 309 | 310 | let public_key_blob = key_pair.export_public_key_blob().unwrap(); 311 | let public_key = RsaKey::set_public_key( 312 | &public_key_blob.modulus, 313 | &public_key_blob.pub_exp, 314 | RsaKeyUsage::Encrypt, 315 | ) 316 | .unwrap(); 317 | 318 | let mut plaintext_buffer = vec![0u8; message.len()]; 319 | let mut result_size = 0; 320 | let encrypted_message = public_key.pkcs1_encrypt(message).unwrap(); 321 | let res = 322 | key_pair.pkcs1_decrypt(&encrypted_message, &mut plaintext_buffer, &mut result_size); 323 | assert_eq!(res, SymCryptError::NoError); 324 | plaintext_buffer.truncate(result_size); 325 | assert_eq!(plaintext_buffer, message); 326 | } 327 | 328 | #[test] 329 | fn test_pkcs1_sign_verify_with_different_keys() { 330 | let signing_key_pair = 331 | RsaKey::generate_key_pair(2048, None, RsaKeyUsage::SignAndEncrypt).unwrap(); 332 | 333 | let verifying_key_pair = 334 | RsaKey::generate_key_pair(2048, None, RsaKeyUsage::SignAndEncrypt).unwrap(); 335 | 336 | let hashed_message = sha256(b"hello world"); 337 | let hash_algorithm = HashAlgorithm::Sha256; 338 | 339 | let signature = signing_key_pair 340 | .pkcs1_sign(&hashed_message, hash_algorithm) 341 | .unwrap(); 342 | 343 | let public_key_blob = verifying_key_pair.export_public_key_blob().unwrap(); 344 | let public_key = RsaKey::set_public_key( 345 | &public_key_blob.modulus, 346 | &public_key_blob.pub_exp, 347 | RsaKeyUsage::SignAndEncrypt, 348 | ) 349 | .unwrap(); 350 | 351 | let verify_result = public_key 352 | .pkcs1_verify(&hashed_message, &signature, hash_algorithm) 353 | .unwrap_err(); 354 | 355 | assert!(matches!( 356 | verify_result, 357 | SymCryptError::SignatureVerificationFailure | SymCryptError::InvalidArgument 358 | )); 359 | } 360 | 361 | #[test] 362 | fn test_pkcs1_verify_with_tampered_message() { 363 | let key_pair = RsaKey::generate_key_pair(2048, None, RsaKeyUsage::SignAndEncrypt).unwrap(); 364 | 365 | let public_key_blob = key_pair.export_public_key_blob().unwrap(); 366 | let public_key = RsaKey::set_public_key( 367 | &public_key_blob.modulus, 368 | &public_key_blob.pub_exp, 369 | RsaKeyUsage::SignAndEncrypt, 370 | ) 371 | .unwrap(); 372 | 373 | let mut hashed_message = sha256(b"hello world"); 374 | let hash_algorithm = HashAlgorithm::Sha256; 375 | 376 | let signature = key_pair 377 | .pkcs1_sign(&hashed_message, hash_algorithm) 378 | .unwrap(); 379 | 380 | // tamper with message 381 | hashed_message[0] ^= 0xFF; 382 | 383 | let verify_result = public_key 384 | .pkcs1_verify(&hashed_message, &signature, hash_algorithm) 385 | .unwrap_err(); 386 | 387 | assert_eq!(verify_result, SymCryptError::SignatureVerificationFailure); 388 | } 389 | 390 | #[test] 391 | #[cfg(feature = "pkcs1-encrypt-decrypt")] 392 | fn test_pkcs1_decrypt_with_invalid_data() { 393 | let key_pair = RsaKey::generate_key_pair(2048, None, RsaKeyUsage::SignAndEncrypt).unwrap(); 394 | 395 | let message = b"example message"; 396 | 397 | let encrypted_message = key_pair.pkcs1_encrypt(message).unwrap(); 398 | 399 | let mut invalid_encrypted_message = encrypted_message.clone(); 400 | invalid_encrypted_message[0] ^= 0xFF; 401 | 402 | let mut plaintext_buffer = vec![0u8; encrypted_message.len()]; 403 | let mut result_size = 0; 404 | 405 | let res = key_pair.pkcs1_decrypt( 406 | &invalid_encrypted_message, 407 | &mut plaintext_buffer, 408 | &mut result_size, 409 | ); 410 | assert_eq!(res, SymCryptError::InvalidArgument); 411 | } 412 | 413 | #[test] 414 | #[cfg(feature = "pkcs1-encrypt-decrypt")] 415 | fn test_pkcs1_encrypt_with_wrong_key_usage() { 416 | let key_pair = RsaKey::generate_key_pair(2048, None, RsaKeyUsage::Sign).unwrap(); 417 | let message = b"example message"; 418 | 419 | let encrypt_result = key_pair.pkcs1_encrypt(message).unwrap_err(); 420 | assert_eq!(encrypt_result, SymCryptError::InvalidArgument); 421 | } 422 | 423 | #[test] 424 | fn test_pkcs1_sign_with_wrong_key_usage() { 425 | let key_pair = RsaKey::generate_key_pair(2048, None, RsaKeyUsage::Encrypt).unwrap(); 426 | 427 | let hashed_message = sha256(b"hello world"); 428 | let hash_algorithm = HashAlgorithm::Sha256; 429 | 430 | let sign_result = key_pair 431 | .pkcs1_sign(&hashed_message, hash_algorithm) 432 | .unwrap_err(); 433 | assert_eq!(sign_result, SymCryptError::InvalidArgument); 434 | } 435 | 436 | #[test] 437 | #[cfg(feature = "pkcs1-encrypt-decrypt")] 438 | fn test_pkcs1_decrypt_with_public_key() { 439 | let key_pair = RsaKey::generate_key_pair(2048, None, RsaKeyUsage::SignAndEncrypt).unwrap(); 440 | let message = b"example message"; 441 | 442 | let public_key_blob = key_pair.export_public_key_blob().unwrap(); 443 | let public_key = RsaKey::set_public_key( 444 | &public_key_blob.modulus, 445 | &public_key_blob.pub_exp, 446 | RsaKeyUsage::Encrypt, 447 | ) 448 | .unwrap(); 449 | 450 | let mut plaintext_buffer = vec![0u8; message.len()]; 451 | let mut result_size = 0; 452 | 453 | let encrypted_message = public_key.pkcs1_encrypt(message).unwrap(); 454 | let result = 455 | public_key.pkcs1_decrypt(&encrypted_message, &mut plaintext_buffer, &mut result_size); 456 | 457 | assert_eq!(result, SymCryptError::InvalidArgument); 458 | } 459 | 460 | #[test] 461 | fn test_pkcs1_sign_with_public_key() { 462 | let key_pair = RsaKey::generate_key_pair(2048, None, RsaKeyUsage::SignAndEncrypt).unwrap(); 463 | 464 | let public_key_blob = key_pair.export_public_key_blob().unwrap(); 465 | let public_key = RsaKey::set_public_key( 466 | &public_key_blob.modulus, 467 | &public_key_blob.pub_exp, 468 | RsaKeyUsage::Encrypt, 469 | ) 470 | .unwrap(); 471 | 472 | let hashed_message = sha256(b"hello world"); 473 | let hash_algorithm = HashAlgorithm::Sha256; 474 | 475 | let result = public_key 476 | .pkcs1_sign(&hashed_message, hash_algorithm) 477 | .unwrap_err(); 478 | assert_eq!(result, SymCryptError::InvalidArgument); 479 | } 480 | } 481 | -------------------------------------------------------------------------------- /rust-symcrypt/src/rsa/pss.rs: -------------------------------------------------------------------------------- 1 | //! PSS functions for [`RsaKey`]. For more info please refer to symcrypt.h 2 | //! 3 | //! # Example 4 | //! 5 | //! ## Sign and Verify with [`RsaKey`] 6 | //! 7 | //! ```rust 8 | //! use symcrypt::rsa::{RsaKey, RsaKeyUsage}; 9 | //! use symcrypt::hash::{sha256, HashAlgorithm, SHA256_RESULT_SIZE}; 10 | //! 11 | //! // Generate key pair 12 | //! let key_pair = RsaKey::generate_key_pair(2048, None, RsaKeyUsage::SignAndEncrypt).unwrap(); 13 | //! 14 | //! // Set up message 15 | //! let hashed_message = sha256(b"hello world"); 16 | //! let hash_algorithm = HashAlgorithm::Sha256; 17 | //! let salt_length = SHA256_RESULT_SIZE; // 32 bytes for SHA256 18 | //! 19 | //! // Create and verify the signature 20 | //! let signature = key_pair.pss_sign(&hashed_message, hash_algorithm, salt_length).unwrap(); 21 | //! let verify_result = key_pair.pss_verify(&hashed_message, &signature, hash_algorithm, salt_length); 22 | //! 23 | //! assert!(verify_result.is_ok()); 24 | //! ``` 25 | //! 26 | use crate::errors::SymCryptError; 27 | use crate::hash::HashAlgorithm; 28 | use crate::rsa::RsaKey; 29 | use crate::NumberFormat; 30 | 31 | impl RsaKey { 32 | /// `pss_sign()` returns a `Vec` that represents the signature of the hashed message, or a [`SymCryptError`] if the operation failed. 33 | /// 34 | /// `hashed_message` is a `&[u8]` that represents the message that has been hashed using the hash algorithm specified in `hash_algorithm`. 35 | /// 36 | /// `hash_algorithm` is a [`HashAlgorithm`] that represents the hash algorithm used to hash the message. 37 | /// 38 | /// `salt_length` is a `usize` that represents the length of the salt to be used in the PSS signature, this value is typically the length of the hash output. 39 | /// 40 | /// This function will fail with [`SymCryptError::InvalidArgument`] if [`RsaKey`] does not have a private key attached. 41 | pub fn pss_sign( 42 | &self, 43 | hashed_message: &[u8], 44 | hash_algorithm: HashAlgorithm, 45 | salt_length: usize, 46 | ) -> Result, SymCryptError> { 47 | let mut result_size = 0; 48 | let modulus_size = self.get_size_of_modulus(); 49 | let mut signature = vec![0u8; modulus_size as usize]; 50 | let hash_algorithm_ptr = hash_algorithm.get_symcrypt_hash(); 51 | 52 | unsafe { 53 | // SAFETY: FFI calls 54 | match symcrypt_sys::SymCryptRsaPssSign( 55 | self.inner(), 56 | hashed_message.as_ptr(), 57 | hashed_message.len() as symcrypt_sys::SIZE_T, 58 | hash_algorithm_ptr, 59 | salt_length as symcrypt_sys::SIZE_T, 60 | 0, // flags must be 0 61 | NumberFormat::MSB.to_symcrypt_format(), 62 | signature.as_mut_ptr(), 63 | modulus_size as symcrypt_sys::SIZE_T, 64 | &mut result_size, 65 | ) { 66 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_NO_ERROR => { 67 | // For signing the size of the output will always be the size of the modulus with the current padding modes. 68 | Ok(signature) 69 | } 70 | err => Err(err.into()), 71 | } 72 | } 73 | } 74 | 75 | /// `pss_verify()` returns a [`SymCryptError`] if the signature verification fails and `Ok(())` if the verification is successful. 76 | /// 77 | /// Caller must check the return value to determine if the signature is valid before continuing. 78 | /// 79 | /// `hashed_message` is a `&[u8]` that represents the message that has been hashed using the hash algorithm specified in `hash_algorithm`. 80 | /// 81 | /// `signature` is a `&[u8]` that represents the signature of the hashed message. 82 | /// 83 | /// `hash_algorithm` is a [`HashAlgorithm`] that represents the hash algorithm used to hash the message. 84 | /// 85 | /// `salt_length` is a `usize` that represents the length of the salt to be used in the PSS signature, this value is typically the length of the hash output. 86 | pub fn pss_verify( 87 | &self, 88 | hashed_message: &[u8], 89 | signature: &[u8], 90 | hash_algorithm: HashAlgorithm, 91 | salt_length: usize, 92 | ) -> Result<(), SymCryptError> { 93 | let hash_algorithm_ptr = hash_algorithm.get_symcrypt_hash(); 94 | 95 | unsafe { 96 | // SAFETY: FFI calls 97 | match symcrypt_sys::SymCryptRsaPssVerify( 98 | self.inner(), 99 | hashed_message.as_ptr(), 100 | hashed_message.len() as symcrypt_sys::SIZE_T, 101 | signature.as_ptr(), 102 | signature.len() as symcrypt_sys::SIZE_T, 103 | NumberFormat::MSB.to_symcrypt_format(), 104 | hash_algorithm_ptr, 105 | salt_length as symcrypt_sys::SIZE_T, 106 | 0, // flags must be 0 107 | ) { 108 | symcrypt_sys::SYMCRYPT_ERROR_SYMCRYPT_NO_ERROR => Ok(()), 109 | err => Err(err.into()), 110 | } 111 | } 112 | } 113 | } 114 | 115 | #[cfg(test)] 116 | mod test { 117 | use super::*; 118 | use crate::hash::{sha256, HashAlgorithm}; 119 | use crate::rsa::{RsaKey, RsaKeyUsage}; 120 | 121 | #[test] 122 | fn test_pss_sign_and_verify_with_key_pair() { 123 | let key_pair = RsaKey::generate_key_pair(2048, None, RsaKeyUsage::SignAndEncrypt).unwrap(); 124 | 125 | let hashed_message = sha256(b"hello world"); 126 | let hash_algorithm = HashAlgorithm::Sha256; 127 | let salt_length = 32; 128 | 129 | let signature = key_pair 130 | .pss_sign(&hashed_message, hash_algorithm, salt_length) 131 | .unwrap(); 132 | 133 | let verify_result = 134 | key_pair.pss_verify(&hashed_message, &signature, hash_algorithm, salt_length); 135 | 136 | assert!(verify_result.is_ok()); 137 | } 138 | #[test] 139 | fn test_pss_sign_and_verify_with_public_key_bytes() { 140 | let key_pair = RsaKey::generate_key_pair(2048, None, RsaKeyUsage::SignAndEncrypt).unwrap(); 141 | 142 | let public_key_blob = key_pair.export_public_key_blob().unwrap(); 143 | let public_key = RsaKey::set_public_key( 144 | &public_key_blob.modulus, 145 | &public_key_blob.pub_exp, 146 | RsaKeyUsage::SignAndEncrypt, 147 | ) 148 | .unwrap(); 149 | 150 | let hashed_message = sha256(b"hello world"); 151 | let hash_algorithm = HashAlgorithm::Sha256; 152 | let salt_length = 32; 153 | 154 | let signature = key_pair 155 | .pss_sign(&hashed_message, hash_algorithm, salt_length) 156 | .unwrap(); 157 | 158 | let verify_result = 159 | public_key.pss_verify(&hashed_message, &signature, hash_algorithm, salt_length); 160 | 161 | assert!(verify_result.is_ok()); 162 | } 163 | 164 | #[test] 165 | fn test_pss_sign_and_verify_with_different_key() { 166 | let key_pair_1 = 167 | RsaKey::generate_key_pair(2048, None, RsaKeyUsage::SignAndEncrypt).unwrap(); 168 | let key_pair_2 = 169 | RsaKey::generate_key_pair(2048, None, RsaKeyUsage::SignAndEncrypt).unwrap(); 170 | 171 | let hashed_message = sha256(b"hello world"); 172 | let hash_algorithm = HashAlgorithm::Sha256; 173 | let salt_length = 32; 174 | 175 | let signature = key_pair_1 176 | .pss_sign(&hashed_message, hash_algorithm, salt_length) 177 | .unwrap(); 178 | 179 | let verify_result = key_pair_2 180 | .pss_verify(&hashed_message, &signature, hash_algorithm, salt_length) 181 | .unwrap_err(); 182 | 183 | assert!(matches!( 184 | verify_result, 185 | SymCryptError::SignatureVerificationFailure | SymCryptError::InvalidArgument 186 | )); 187 | } 188 | 189 | #[test] 190 | fn test_pss_sign_and_verify_tampered_signature() { 191 | let key_pair = RsaKey::generate_key_pair(2048, None, RsaKeyUsage::SignAndEncrypt).unwrap(); 192 | 193 | let public_key_blob = key_pair.export_public_key_blob().unwrap(); 194 | let public_key = RsaKey::set_public_key( 195 | &public_key_blob.modulus, 196 | &public_key_blob.pub_exp, 197 | RsaKeyUsage::SignAndEncrypt, 198 | ) 199 | .unwrap(); 200 | 201 | let hashed_message = sha256(b"hello world"); 202 | let hash_algorithm = HashAlgorithm::Sha256; 203 | let salt_length = 32; 204 | 205 | let mut signature = key_pair 206 | .pss_sign(&hashed_message, hash_algorithm, salt_length) 207 | .unwrap(); 208 | 209 | // tamper with signature 210 | signature[1] ^= 0xFF; // modifying second element, If we modify the first we will get InvalidArgument error instead. 211 | 212 | let verify_result = public_key 213 | .pss_verify(&hashed_message, &signature, hash_algorithm, salt_length) 214 | .unwrap_err(); 215 | 216 | assert_eq!(verify_result, SymCryptError::SignatureVerificationFailure); 217 | } 218 | 219 | #[test] 220 | fn test_pss_sign_and_verify_salt_too_large() { 221 | let key_pair = RsaKey::generate_key_pair(2048, None, RsaKeyUsage::SignAndEncrypt).unwrap(); 222 | 223 | let hashed_message = sha256(b"hello world"); 224 | let hash_algorithm = HashAlgorithm::Sha256; 225 | 226 | // If the length of the hash + the size of the salt is larger than the size of the modulus, then generation should fail 227 | let salt_length = 1000; 228 | 229 | let signature = key_pair 230 | .pss_sign(&hashed_message, hash_algorithm, salt_length) 231 | .unwrap_err(); 232 | 233 | assert_eq!(signature, SymCryptError::InvalidArgument); 234 | } 235 | 236 | #[test] 237 | fn test_pss_sign_with_wrong_key_usage() { 238 | let key_pair = RsaKey::generate_key_pair(2048, None, RsaKeyUsage::Encrypt).unwrap(); 239 | 240 | let hashed_message = sha256(b"hello world"); 241 | let hash_algorithm = HashAlgorithm::Sha256; 242 | let salt_length = 32; 243 | 244 | let signature = key_pair 245 | .pss_sign(&hashed_message, hash_algorithm, salt_length) 246 | .unwrap_err(); 247 | 248 | assert_eq!(signature, SymCryptError::InvalidArgument); 249 | } 250 | 251 | #[test] 252 | fn test_pss_sign_with_public_key() { 253 | let key_pair = RsaKey::generate_key_pair(2048, None, RsaKeyUsage::SignAndEncrypt).unwrap(); 254 | 255 | let public_key_blob = key_pair.export_public_key_blob().unwrap(); 256 | let public_key = RsaKey::set_public_key( 257 | &public_key_blob.modulus, 258 | &public_key_blob.pub_exp, 259 | RsaKeyUsage::SignAndEncrypt, 260 | ) 261 | .unwrap(); 262 | 263 | let hashed_message = sha256(b"hello world"); 264 | let hash_algorithm = HashAlgorithm::Sha256; 265 | let salt_length = 32; 266 | 267 | let result = public_key 268 | .pss_sign(&hashed_message, hash_algorithm, salt_length) 269 | .unwrap_err(); 270 | assert_eq!(result, SymCryptError::InvalidArgument) 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /scripts/generate-all-bindings.ps1: -------------------------------------------------------------------------------- 1 | # This script generates Rust bindings for the SymCrypt library for all supported target triples. 2 | # Prerequisites: 3 | # - The script must be run on Windows with WSL installed. 4 | # - LLVM and bindgen must be installed on both Windows and WSL. 5 | 6 | # Installation instructions: 7 | # Setting up Windows: 8 | # winget install LLVM.LLVM 9 | # 10 | # Setting up WSL: 11 | # - It's better to install the most recent version of Ubuntu, rustup, etc. 12 | # wsl --install Ubuntu-24.04 13 | # 14 | # - Enter the WSL shell and run the following commands: 15 | # sudo apt update && sudo apt upgrade 16 | # sudo apt install -y clang libclang-dev 17 | # curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # to install rust for WSL 18 | # sudo apt install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu # for cross-compilation 19 | # 20 | # rustup update 21 | # rustup toolchain add stable 22 | # rustup target add x86_64-unknown-linux-gnu 23 | # rustup target add aarch64-unknown-linux-gnu 24 | 25 | param ( 26 | # Sets CARGO_TARGET_DIR environment variable for WSL builds. 27 | # This is necessary because if we try to build from Windows volume, WSL might fail to detect 28 | # changes in the file system. 29 | [string]$wslTempDir = "~/rust-symcrypt/target" 30 | ) 31 | 32 | $ErrorActionPreference = "Stop" 33 | $PSNativeCommandUseErrorActionPreference = $True 34 | 35 | Push-Location "$PSScriptRoot/.." # Move to the root of the project 36 | 37 | git submodule update --init 38 | 39 | python3 "./symcrypt-sys/symcrypt/scripts/version.py" --build-info 40 | mv -Force "./symcrypt-sys/symcrypt/inc/buildInfo.h" "./symcrypt-sys/inc/" 41 | mv -Force "./symcrypt-sys/symcrypt/inc/symcrypt_internal_shared.inc" "./symcrypt-sys/inc/" 42 | 43 | $bindingsDir = "./symcrypt-sys/src/bindings" # is relative to the project root 44 | if (Test-Path $bindingsDir) { 45 | Remove-Item -Recurse -Force "$bindingsDir" 46 | } 47 | 48 | cargo run --locked --bin symcrypt-bindgen "x86_64-pc-windows-msvc" $bindingsDir 49 | cargo run --locked --bin symcrypt-bindgen "aarch64-pc-windows-msvc" $bindingsDir 50 | 51 | Write-Host "Restarting WSL..." && wsl --shutdown # force WSL to reload the environment 52 | wsl exec bash "./scripts/run.sh" "export CARGO_TARGET_DIR=$wslTempDir && cargo build -p symcrypt-bindgen" 53 | wsl exec bash "./scripts/run.sh" "export CARGO_TARGET_DIR=$wslTempDir && cargo run --locked --bin symcrypt-bindgen x86_64-unknown-linux-gnu $bindingsDir" 54 | wsl exec bash "./scripts/run.sh" "export CARGO_TARGET_DIR=$wslTempDir && cargo run --locked --bin symcrypt-bindgen aarch64-unknown-linux-gnu $bindingsDir" 55 | 56 | cargo fmt -p symcrypt-sys 57 | 58 | Pop-Location 59 | -------------------------------------------------------------------------------- /scripts/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Helper script called from generate-all-bindings.ps1, mainly to set the PATH to cargo binaries 3 | 4 | export PATH=$PATH:~/.cargo/bin 5 | echo "Running: $1" 6 | eval "$1" 7 | -------------------------------------------------------------------------------- /symcrypt-bindgen/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "symcrypt-bindgen" 3 | authors = ["Microsoft"] 4 | version = "0.2.0" 5 | description = "Bindings generation for symcrypt-sys" 6 | edition.workspace = true 7 | rust-version.workspace = true 8 | publish = false 9 | 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | 13 | [dependencies] 14 | bindgen = "^0.71.1" 15 | 16 | anyhow = "1.0" 17 | cmd_lib = "1.9" 18 | serde_json = "1.0" 19 | regex = "1.11" 20 | -------------------------------------------------------------------------------- /symcrypt-bindgen/README.md: -------------------------------------------------------------------------------- 1 | # symcrypt-bindgen 2 | 3 | This repository is intended for development purposes only. Users should not generate or use their 4 | own raw bindings. 5 | 6 | ## Updating Bindings 7 | 8 | To create new bindings, run the following command: 9 | 10 | ```powershell 11 | cargo run --locked --bin symcrypt-bindgen 12 | ``` 13 | 14 | For Windows users, there is a script available to update bindings for all four supported platforms: 15 | 16 | ```powershell 17 | ./scripts/generate_all_bindings.ps1 18 | ``` 19 | 20 | Alternatively, you can create a pull request with the `publish_new_bindings` label. In this case, 21 | the bindings will be published as an artifact on GitHub. 22 | -------------------------------------------------------------------------------- /symcrypt-bindgen/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::path::{Path, PathBuf}; 3 | use std::str::FromStr; 4 | 5 | const SUPPORTED_TARGETS: &[&str] = &[ 6 | "x86_64-pc-windows-msvc", 7 | "aarch64-pc-windows-msvc", 8 | "x86_64-unknown-linux-gnu", 9 | "aarch64-unknown-linux-gnu", 10 | ]; 11 | 12 | fn main() { 13 | let args: Vec = env::args().collect(); 14 | if args.len() != 3 { 15 | eprintln!("Wrong arguments: {:?}", args); 16 | eprintln!("Usage: {} ", args[0]); 17 | std::process::exit(1); 18 | } 19 | 20 | let triple = &args[1]; 21 | let out_dir = &args[2]; 22 | 23 | if !SUPPORTED_TARGETS.contains(&triple.as_str()) { 24 | eprintln!( 25 | "Unsupported target: {}. Supported targets: {:?}", 26 | triple, SUPPORTED_TARGETS 27 | ); 28 | std::process::exit(1); 29 | } 30 | 31 | let root_dir = get_parent_n(Path::new(std::file!()), 3); 32 | 33 | println!("root_dir: {}", root_dir.display()); 34 | let symcrypt_sys_crate = root_dir.join("symcrypt-sys"); 35 | let wrapper_header = symcrypt_sys_crate.join("inc/wrapper.h"); 36 | let target_name = triple.replace("-", "_"); 37 | let bindings_file = format!("{}/{}.rs", out_dir, target_name); 38 | let rust_target = get_rust_version_from_cargo_metadata(); 39 | 40 | println!("Rust version: {rust_target}"); 41 | println!("Output file: {bindings_file}"); 42 | 43 | std::fs::create_dir_all(out_dir).expect("Unable to create output directory"); 44 | 45 | let bindings = bindgen::builder() 46 | .header(wrapper_header.display().to_string()) 47 | .rust_target(bindgen::RustTarget::from_str(&rust_target).unwrap()) 48 | 49 | // Clang arguments 50 | .clang_arg("-v") 51 | .clang_args(["-target", triple]) 52 | .clang_arg(format!("-I{}/inc", symcrypt_sys_crate.display())) 53 | .clang_arg(format!("-I{}/symcrypt/inc", symcrypt_sys_crate.display())) 54 | .clang_arg(format!("-I{}/symcrypt/lib", symcrypt_sys_crate.display())) 55 | 56 | // ALLOWLIST 57 | 58 | // INIT FUNCTIONS 59 | .allowlist_function("SymCryptModuleInit") 60 | .allowlist_function("SymCryptInit") 61 | .allowlist_var("^(SYMCRYPT_CODE_VERSION.*)$") 62 | // HASH FUNCTIONS 63 | .allowlist_function("^SymCrypt(?:Sha3_(?:256|384|512)|Sha(?:256|384|512|1)|Md5)(?:Init|Append|Result|StateCopy)?$") 64 | .allowlist_var("^(SYMCRYPT_(SHA3_256|SHA3_384|SHA3_512|SHA256|SHA384|SHA512|SHA1|MD5)_RESULT_SIZE$)") 65 | .allowlist_var("^SymCrypt(?:Sha3_(?:256|384|512)|Sha(?:256|384|512|1)|Md5)Algorithm$") 66 | // HMAC FUNCTIONS 67 | .allowlist_function("^SymCryptHmac(?:Sha(?:256|384|512|1)|Md5)(?:ExpandKey|Init|Append|Result|StateCopy)?$") 68 | .allowlist_var("^(SymCryptHmac(Sha256|Sha384|Sha512|Sha1|Md5)Algorithm)$") 69 | // GCM FUNCTIONS 70 | .allowlist_function("^(SymCryptGcm(?:ValidateParameters|ExpandKey|Encrypt|Decrypt|Init|StateCopy|AuthPart|DecryptPart|EncryptPart|EncryptFinal|DecryptFinal)?)$") 71 | .allowlist_function("SymCryptChaCha20Poly1305(Encrypt|Decrypt)") 72 | .allowlist_function("^SymCryptTlsPrf1_2(?:ExpandKey|Derive)?$") 73 | // CBC FUNCTIONS 74 | .allowlist_function("^SymCryptAesCbc(Encrypt|Decrypt)?$") 75 | // BLOCK CIPHERS 76 | .allowlist_var("SymCryptAesBlockCipher") 77 | .allowlist_function("^SymCryptAesExpandKey$") 78 | .allowlist_var("SYMCRYPT_AES_BLOCK_SIZE") 79 | // HKDF FUNCTIONS 80 | .allowlist_function("^(SymCryptHkdf.*)$") 81 | // ECDH KEY AGREEMENT FUNCTIONS 82 | .allowlist_function("^SymCryptEcurve(Allocate|Free|SizeofFieldElement)$") 83 | .allowlist_var("^SymCryptEcurveParams(NistP256|NistP384|NistP521|Curve25519)$") 84 | .allowlist_function("^(SymCryptEckey(Allocate|Free|SizeofPublicKey|SizeofPrivateKey|GetValue|SetRandom|SetValue|SetRandom|))$") 85 | .allowlist_var("SYMCRYPT_FLAG_ECKEY_ECDH") 86 | .allowlist_var("SYMCRYPT_FLAG_ECKEY_ECDSA") 87 | .allowlist_function("SymCryptEcDhSecretAgreement") 88 | // RSA FUNCTIONS 89 | .allowlist_function("^SymCryptRsa.*") // Must allow ALL SymCryptRsakey* before blocking the functions that are not needed. 90 | .blocklist_function("SymCryptRsakeyCreate") 91 | .blocklist_function("SymCryptRsakeySizeofRsakeyFromParams") 92 | .blocklist_function("SymCryptRsakeyWipe") 93 | .blocklist_function("SymCryptRsaSelftest") 94 | .blocklist_function("^SymCryptRsaRaw.*$") 95 | .allowlist_var("SYMCRYPT_FLAG_RSAKEY_ENCRYPT") 96 | .allowlist_var("SYMCRYPT_FLAG_RSAKEY_SIGN") 97 | // ECDSA functions 98 | .allowlist_function("^(SymCryptEcDsa(Sign|Verify).*)") 99 | // RSA PKCS1 FUNCTIONS 100 | .allowlist_function("^(SymCryptRsaPkcs1(Sign|Verify|Encrypt|Decrypt).*)$") 101 | .allowlist_var("SYMCRYPT_FLAG_RSA_PKCS1_NO_ASN1") 102 | .allowlist_var("SYMCRYPT_FLAG_RSA_PKCS1_OPTIONAL_HASH_OID") 103 | // RSA PSS FUNCTIONS 104 | .allowlist_function("^(SymCryptRsaPss(Sign|Verify).*)$") 105 | // OID LISTS 106 | .allowlist_var("^SymCrypt(Sha(1|256|384|512|3_(256|384|512))|Md5)OidList$") 107 | // UTILITY FUNCTIONS 108 | .allowlist_function("SymCryptWipe") 109 | .allowlist_function("SymCryptRandom") 110 | .allowlist_function("SymCryptCallbackRandom") 111 | .allowlist_function("SymCryptLoadMsbFirstUint64") 112 | .allowlist_function("SymCryptStoreMsbFirstUint64") 113 | 114 | .generate_comments(true) 115 | .derive_default(true) 116 | .generate() 117 | .expect("Unable to generate bindings"); 118 | 119 | bindings 120 | .write_to_file(&bindings_file) 121 | .expect("Couldn't write bindings!"); 122 | 123 | // For dynamic linking, we expose SymCryptModuleInit, for static linking, we expose SymCryptInit. 124 | fix_symcrypt_bindings(&bindings_file); 125 | 126 | // For dynamic linking, we need to add a link attribute to the bindings. 127 | fix_bindings_for_windows(triple, &bindings_file); 128 | } 129 | 130 | fn get_parent_n(path: &Path, n: usize) -> PathBuf { 131 | let mut parent = path; 132 | for _ in 0..n { 133 | parent = parent.parent().unwrap(); 134 | } 135 | parent.to_path_buf() 136 | } 137 | 138 | // Bindings have to be compatible with the Rust version specified for symcrypt-sys crate. 139 | fn get_rust_version_from_cargo_metadata() -> String { 140 | let output: String = cmd_lib::run_fun!(cargo metadata --no-deps --format-version=1) 141 | .expect("failed to execute cargo metadata"); 142 | 143 | let metadata: serde_json::Value = 144 | serde_json::from_slice(output.as_bytes()).expect("Failed to parse cargo metadata output"); 145 | 146 | let packages = metadata["packages"].as_array().unwrap(); 147 | let package = packages 148 | .iter() 149 | .find(|p| p["name"].as_str().unwrap() == "symcrypt-sys") 150 | .expect("symcrypt-sys package not found"); 151 | package["rust_version"] 152 | .as_str() 153 | .map(|s| s.to_string()) 154 | .unwrap() 155 | } 156 | 157 | #[allow(clippy::collapsible_if)] 158 | fn fix_bindings_for_windows(triple: &str, bindings_file: &str) { 159 | if triple.contains("windows") { 160 | println!("Fixing bindings for Windows"); 161 | let link_str = 162 | r#"#[cfg_attr(feature = "dynamic", link(name = "symcrypt", kind = "dylib"))]"#; 163 | let regex_exp1 = regex::Regex::new(r"pub static \w+: \[SYMCRYPT_OID; \d+usize\];").unwrap(); 164 | let regex_exp2 = regex::Regex::new(r"pub static \w+: PCSYMCRYPT_\w+;").unwrap(); 165 | let bindings_content = 166 | std::fs::read_to_string(bindings_file).expect("Unable to read bindings file"); 167 | 168 | let mut out_content = Vec::new(); 169 | let lines: Vec<&str> = bindings_content.lines().collect(); 170 | out_content.push(lines[0]); 171 | 172 | for i in 1..lines.len() { 173 | if lines[i - 1].contains("extern \"C\" {") { 174 | if regex_exp1.is_match(lines[i]) || regex_exp2.is_match(lines[i]) { 175 | out_content.pop(); 176 | out_content.push(link_str); 177 | out_content.push(lines[i - 1]); 178 | } 179 | } 180 | out_content.push(lines[i]); 181 | } 182 | 183 | out_content.push(""); // Add an empty line at the end 184 | std::fs::write(bindings_file, out_content.join("\n")) 185 | .expect("Unable to write bindings file"); 186 | } 187 | } 188 | 189 | #[allow(clippy::collapsible_if)] 190 | fn fix_symcrypt_bindings(bindings_file: &str) { 191 | println!("Fixing bindings to expose SymCryptInit or SymCryptModuleInit and SymCryptRandom"); 192 | 193 | let bindings_content = 194 | std::fs::read_to_string(bindings_file).expect("Unable to read bindings file"); 195 | 196 | let mut out_content = Vec::new(); 197 | let lines: Vec<&str> = bindings_content.lines().collect(); 198 | let mut i = 0; 199 | 200 | // With Dynamic, we want to expose SymCryptModuleInit and SymCryptRandom 201 | // With Static, we want to expose SymCryptInit and SymCryptCallbackRandom 202 | while i < lines.len() { 203 | if lines[i].trim() == "extern \"C\" {" { 204 | if i + 1 < lines.len() { 205 | let next_line = lines[i + 1].trim(); 206 | 207 | let cfg_attr = match next_line { 208 | line if line == "pub fn SymCryptInit();" 209 | || line.starts_with("pub fn SymCryptCallbackRandom(") => 210 | { 211 | "#[cfg(not(feature = \"dynamic\"))]" 212 | } 213 | line if line.starts_with("pub fn SymCryptModuleInit(") 214 | || line.starts_with("pub fn SymCryptRandom(") => 215 | { 216 | "#[cfg(feature = \"dynamic\")]" 217 | } 218 | _ => "", 219 | }; 220 | 221 | if !cfg_attr.is_empty() { 222 | out_content.push(cfg_attr.to_string()); 223 | } 224 | } 225 | } 226 | 227 | out_content.push(lines[i].to_string()); 228 | i += 1; 229 | } 230 | 231 | // Append newline for linux bindings 232 | if !out_content.last().unwrap_or(&String::new()).ends_with('\n') { 233 | out_content.push("".to_string()); 234 | } 235 | 236 | // Write the modified content back 237 | std::fs::write(bindings_file, out_content.join("\n")).expect("Unable to write bindings file"); 238 | } 239 | -------------------------------------------------------------------------------- /symcrypt-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "symcrypt-sys" 3 | authors = ["Microsoft"] 4 | version = "0.4.0" 5 | license = "MIT OR Apache-2.0" 6 | description = "Rust/C Bindings for SymCrypt" 7 | edition.workspace = true 8 | rust-version.workspace = true 9 | build = "build/main.rs" 10 | homepage = "https://github.com/microsoft/SymCrypt" 11 | repository = "https://github.com/microsoft/rust-symcrypt" 12 | readme = "README.md" 13 | exclude = ["symcrypt/*", "inc/*"] # FIXME: update this before merging to main branch 14 | #links = "symcrypt" # FIXME: uncomment this before merging to main branch 15 | 16 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 17 | 18 | [features] 19 | dynamic = [] 20 | 21 | [dependencies] 22 | libc = "0.2.0" 23 | 24 | [build-dependencies] 25 | cc = { version = "1.2.10" } # FIXME: enable parallel feature before merging to main branch 26 | -------------------------------------------------------------------------------- /symcrypt-sys/README.md: -------------------------------------------------------------------------------- 1 | # SymCrypt Rust/C FFI Bindings 2 | symcrypt-sys provides Rust/C FFI bindings for the [SymCrypt](https://github.com/microsoft/SymCrypt) library. 3 | 4 | This crate is supplementary to the `symcrypt` crate. 5 | 6 | The bindings are checked into this crate in order to have better control over the binding generation as well as the exposed APIs from SymCrypt. To speed up the common case build process, the binding generation has been separated to `symcrypt-bindgen` 7 | 8 | ## Changelog 9 | 10 | To view a detailed list of changes please see the [releases page](https://github.com/microsoft/rust-symcrypt/releases/). 11 | 12 | 13 | ## Usage 14 | 15 | Recommended usage is to take advantage of the `symcrypt` crate, which provides safe and rust idiomatic wrappers over the bindings. 16 | 17 | However, If you want to access the bindings directly, you can add `symcrypt-sys` as a dependency in your rust project. 18 | 19 | You must also configure your system to pick up the SymCrypt lib on your machine, please see: [INSTALL.md](https://github.com/microsoft/rust-symcrypt/blob/main/rust-symcrypt/INSTALL.md) for more info. 20 | 21 | In your `Cargo.toml` 22 | ```Rust 23 | symcrypt-sys = "0.4.0" 24 | ``` 25 | Then you can call the underlying SymCrypt code directly via the FFIs. 26 | ```Rust 27 | unsafe { 28 | // SAFETY: FFI calls 29 | symcrypt_sys::SymCryptSha384( 30 | data.as_ptr(), 31 | data.len() as symcrypt_sys::SIZE_T, 32 | result.as_mut_ptr(), 33 | ); 34 | } 35 | ``` -------------------------------------------------------------------------------- /symcrypt-sys/VERSION.md: -------------------------------------------------------------------------------- 1 | 53be637dab201a4c9d95e1cf58040c85d71cf3c2 2 | -------------------------------------------------------------------------------- /symcrypt-sys/build/main.rs: -------------------------------------------------------------------------------- 1 | #[cfg(not(feature = "dynamic"))] 2 | pub mod static_link; 3 | 4 | #[cfg(not(feature = "dynamic"))] 5 | pub mod triple; 6 | 7 | fn main() -> std::io::Result<()> { 8 | #[cfg(feature = "dynamic")] 9 | link_symcrypt_dynamically()?; 10 | 11 | #[cfg(not(feature = "dynamic"))] 12 | static_link::compile_and_link_symcrypt()?; 13 | 14 | Ok(()) 15 | } 16 | 17 | #[cfg(feature = "dynamic")] 18 | fn link_symcrypt_dynamically() -> std::io::Result<()> { 19 | #[cfg(target_os = "windows")] 20 | { 21 | // Look for the .lib file during link time. We are searching the PATH for symcrypt.dll 22 | let lib_path = std::env::var("SYMCRYPT_LIB_PATH") 23 | .unwrap_or_else(|_| panic!("SYMCRYPT_LIB_PATH environment variable not set, for more information please see: https://github.com/microsoft/rust-symcrypt/tree/main/rust-symcrypt#quick-start-guide")); 24 | println!("cargo:rustc-link-search=native={}", lib_path); 25 | 26 | println!("cargo:rustc-link-lib=dylib=symcrypt"); 27 | 28 | // During run time, the OS will handle finding the symcrypt.dll file. The places Windows will look will be: 29 | // 1. The folder from which the application loaded. 30 | // 2. The system folder. Use the GetSystemDirectory function to retrieve the path of this folder. 31 | // 3. The Windows folder. Use the GetWindowsDirectory function to get the path of this folder. 32 | // 4. The current folder. 33 | // 5. The directories that are listed in the PATH environment variable. 34 | 35 | // For more info please see: https://learn.microsoft.com/en-us/windows/win32/dlls/dynamic-link-library-search-order 36 | } 37 | 38 | #[cfg(target_os = "linux")] 39 | { 40 | println!("cargo:rustc-link-lib=dylib=symcrypt"); // the "lib" prefix for libsymcrypt is implied on Linux 41 | 42 | // If you are using AL3, you can get the required symcrypt.so via tdnf 43 | // If you are using Ubuntu, you can get the required symcrypt.so via PMC. Please see the quick start guide for more information. 44 | 45 | // If you are using a different Linux distro, you will need to configure your distro's LD linker to find the required symcrypt.so files. 46 | // As an example, on Ubuntu you can place your symcrypt.so files in your usr/lib/x86_64-linux-gnu/ path. 47 | } 48 | 49 | Ok(()) 50 | } 51 | -------------------------------------------------------------------------------- /symcrypt-sys/build/static_link.rs: -------------------------------------------------------------------------------- 1 | use super::triple::Triple; 2 | use std::collections::HashSet; 3 | 4 | const LIB_NAME: &str = "symcrypt"; 5 | 6 | /// Compiles and links the SymCrypt library statically. 7 | /// This is the entery point for building SymCrypt statically. 8 | /// 9 | /// - Determines the build configuration based on target architecture. 10 | /// - Calls `compile_symcrypt_static()` to actually build SymCrypt. 11 | /// - Outputs necessary metadata for Cargo (`cargo:rustc-link-lib=...`). 12 | /// 13 | /// Based on SymCrypt's `CMakeLists.txt`, but adapted for Rust. 14 | pub fn compile_and_link_symcrypt() -> std::io::Result<()> { 15 | let options = SymCryptOptions::new(); 16 | println!("Build config: {:?}", options); 17 | 18 | // Required Windows bcrypt dependency for BCryptGenRandom 19 | const ADDITIONAL_DEPENDENCIES: &[&str] = &[ 20 | #[cfg(windows)] 21 | "bcrypt", 22 | ]; 23 | println!("cargo:rerun-if-changed=upstream"); 24 | println!("Compiling SymCrypt..."); 25 | 26 | // Compile and Build SymCrypt with provided SymCryptOptions 27 | compile_symcrypt_static(LIB_NAME, &options)?; 28 | println!("cargo:rustc-link-lib=static={LIB_NAME}"); 29 | 30 | // Link additional dependencies 31 | for dep in ADDITIONAL_DEPENDENCIES { 32 | println!("cargo:rustc-link-lib=dylib={dep}"); 33 | } 34 | 35 | Ok(()) 36 | } 37 | 38 | // TODO: update -symcrypt_fips_build comment 39 | 40 | /// Holds configuration options for compiling SymCrypt. 41 | /// 42 | /// - `triple`: The target triple 43 | /// - `symcrypt_use_asm`: Whether to enable assembly optimizations. 44 | /// - `symcrypt_fips_build`: Whether to build in FIPS mode PLACEHOLDER 45 | /// - `preconfiged_cc`: Returns a Pre-configured `cc` object for the target triple. 46 | #[derive(Debug)] 47 | struct SymCryptOptions { 48 | triple: Triple, 49 | symcrypt_use_asm: bool, 50 | //symcrypt_fips_build: bool, // TODO: Determine if we should expose FIPS build option? 51 | } 52 | 53 | impl SymCryptOptions { 54 | fn new() -> Self { 55 | Self { 56 | triple: Triple::get_target_triple(), 57 | symcrypt_use_asm: false, // FIXME: Turn this to true when we get ASM checked in 58 | //symcrypt_fips_build: false, // TODO: Determine if we should expose FIPS build option? 59 | } 60 | } 61 | fn use_asm(&self) -> bool { 62 | self.symcrypt_use_asm 63 | } 64 | fn triple(&self) -> Triple { 65 | self.triple.clone() 66 | } 67 | 68 | // Returns a cc object that has been preconfigured for the target triple 69 | fn preconfigure_cc(&self) -> cc::Build { 70 | let mut cc = cc::Build::new(); 71 | cc.target(self.triple.to_triple()) 72 | .include("inc") 73 | .include("symcrypt/inc") 74 | .include("symcrypt/lib") 75 | .warnings(false); // Ignore noisy warnings from SymCrypt 76 | 77 | if !self.symcrypt_use_asm { 78 | cc.define("SYMCRYPT_IGNORE_PLATFORM", None); // TODO: Fix when we get ASM 79 | } 80 | 81 | // Set specific flags for operating system 82 | 83 | match self.triple { 84 | // Target all Windows targets 85 | Triple::x86_64_pc_windows_msvc | Triple::aarch64_pc_windows_msvc => { 86 | // From SymCrypt-Platforms.cmake 87 | cc.flag("/MP") // Multi-threaded compilation 88 | .flag("/Zp8") // Structure packing alignment 89 | .flag("/WX") // Treat warnings as errors 90 | .flag("/guard:cf") // Control Flow Guard 91 | .flag("/wd5105") // Disable warning caused by Windows SDK headers 92 | .flag("/EHsc"); // Exception handling 93 | // .flag("/dynamicbase"); // Enabling ASLR produces lots of warnings 94 | 95 | // From lib/CmakeLists.txt 96 | // cc.asm_flag("/DSYMCRYPT_MASM"); // TODO: enable for ASM 97 | } 98 | 99 | // Target all Linux targets 100 | Triple::x86_64_unknown_linux_gnu | Triple::aarch64_unknown_linux_gnu => { 101 | // From lib/CmakeLists.txt 102 | // Stack Protection ON by default for linux 103 | cc.flag("-fstack-protector-strong") 104 | .flag("-Wstack-protector") 105 | .flag("--param=ssp-buffer-size=4") 106 | .flag("-fstack-clash-protection") 107 | .flag("-Wno-incompatible-pointer-types"); // Ignore noisy SymCrypt errors 108 | 109 | // From lib/CmakeLists.txt 110 | // cc.flag("-x assembler-with-cpp"); // TODO: enable for ASM 111 | 112 | // From SymCrypt-Platforms.cmake 113 | cc.flag("-Wno-unknown-pragmas") 114 | .flag("-Werror") 115 | .flag("-Wno-deprecated-declarations") 116 | .flag("-Wno-deprecated") 117 | .flag("-g") 118 | .flag("-Wno-multichar") 119 | .flag("-fPIC") // PIC is enabled by default on Linux 120 | .flag("-fno-plt") 121 | .flag("-fno-builtin-bcmp") 122 | .flag("-fno-unroll-loops"); 123 | } 124 | } 125 | 126 | // Set specific flags for each triple 127 | match self.triple { 128 | Triple::x86_64_pc_windows_msvc => { 129 | // From SymCrypt-Platforms.cmake 130 | cc.define("_AMD64_", None).flag("/Gz"); // Set default to __stdcall, only for X86 131 | } 132 | Triple::aarch64_pc_windows_msvc => { 133 | cc.define("_ARM64_", None); 134 | } 135 | Triple::x86_64_unknown_linux_gnu => { 136 | // From SymCrypt-Platforms.cmake 137 | // Only for x86_64_unknown_linux_gnu 138 | cc.flag("-mssse3") 139 | .flag("-mxsave") 140 | .flag("-maes") 141 | .flag("-mpclmul") 142 | .flag("-msha") 143 | .flag("-mrdrnd") 144 | .flag("-mrdseed"); 145 | } 146 | Triple::aarch64_unknown_linux_gnu => { 147 | // From SymCrypt-Platforms.cmake 148 | cc.flag("-march=armv8-a+simd+crypto") // Enable a baseline of features for the compiler to support everywhere. 149 | .flag("-flax-vector-conversions"); // Setting -flax-vector-conversions to build Arm64 intrinsics code with GCC. 150 | } 151 | } 152 | 153 | cc 154 | } 155 | } 156 | 157 | const SOURCE_DIR: &str = "symcrypt/lib"; 158 | const SOURCES_COMMON: &str = " 159 | 3des.c 160 | a_dispatch.c 161 | aes-asm.c 162 | aes-c.c 163 | aes-default-bc.c 164 | aes-default.c 165 | aes-key.c 166 | aes-neon.c 167 | aes-selftest.c 168 | aes-xmm.c 169 | aes-ymm.c 170 | aescmac.c 171 | aesCtrDrbg.c 172 | aeskw.c 173 | AesTables.c 174 | blockciphermodes.c 175 | ccm.c 176 | chacha20_poly1305.c 177 | chacha20.c 178 | cpuid_notry.c 179 | cpuid_um.c 180 | cpuid.c 181 | crt.c 182 | DesTables.c 183 | desx.c 184 | dh.c 185 | dl_internal_groups.c 186 | dlgroup.c 187 | dlkey.c 188 | dsa.c 189 | ec_dh.c 190 | ec_dispatch.c 191 | ec_dsa.c 192 | ec_internal_curves.c 193 | ec_montgomery.c 194 | ec_mul.c 195 | ec_short_weierstrass.c 196 | ec_twisted_edwards.c 197 | eckey.c 198 | ecpoint.c 199 | ecurve.c 200 | equal.c 201 | FatalIntercept.c 202 | fdef_general.c 203 | fdef_int.c 204 | fdef_mod.c 205 | fdef369_mod.c 206 | fips_selftest.c 207 | gcm.c 208 | gen_int.c 209 | ghash.c 210 | hash.c 211 | hkdf_selftest.c 212 | hkdf.c 213 | hmac.c 214 | hmacmd5.c 215 | hmacsha1.c 216 | hmacsha224.c 217 | hmacsha256.c 218 | hmacsha384.c 219 | hmacsha512.c 220 | hmacsha512_224.c 221 | hmacsha512_256.c 222 | hmacsha3_224.c 223 | hmacsha3_256.c 224 | hmacsha3_384.c 225 | hmacsha3_512.c 226 | kmac.c 227 | libmain.c 228 | lms.c 229 | marvin32.c 230 | md2.c 231 | md4.c 232 | md5.c 233 | mldsa.c 234 | mldsa_primitives.c 235 | mlkem.c 236 | mlkem_primitives.c 237 | modexp.c 238 | paddingPkcs7.c 239 | parhash.c 240 | pbkdf2_hmacsha1.c 241 | pbkdf2_hmacsha256.c 242 | pbkdf2.c 243 | poly1305.c 244 | primes.c 245 | rc2.c 246 | rc4.c 247 | rdrand.c 248 | rdseed.c 249 | recoding.c 250 | rsa_enc.c 251 | rsa_padding.c 252 | rsakey.c 253 | ScsTable.c 254 | scsTools.c 255 | selftest.c 256 | service_indicator.c 257 | session.c 258 | sha1.c 259 | sha256.c 260 | sha256Par.c 261 | sha256Par-ymm.c 262 | sha256-xmm.c 263 | sha256-ymm.c 264 | sha512.c 265 | sha512Par.c 266 | sha512Par-ymm.c 267 | sha512-ymm.c 268 | sha3.c 269 | sha3_224.c 270 | sha3_256.c 271 | sha3_384.c 272 | sha3_512.c 273 | shake.c 274 | sp800_108_hmacsha1.c 275 | sp800_108_hmacsha256.c 276 | sp800_108_hmacsha512.c 277 | sp800_108.c 278 | srtp_kdf.c 279 | srtp_kdf_selftest.c 280 | ssh_kdf.c 281 | ssh_kdf_sha256.c 282 | ssh_kdf_sha512.c 283 | sskdf.c 284 | sskdf_selftest.c 285 | tlsCbcVerify.c 286 | tlsprf_selftest.c 287 | tlsprf.c 288 | xmss.c 289 | xtsaes.c 290 | "; 291 | 292 | fn compile_symcrypt_static(lib_name: &str, options: &SymCryptOptions) -> std::io::Result<()> { 293 | // Compile intermediates required this is currently only required for x86_64_unknown_linux_gnu 294 | let (already_compiled_files, intermediates) = compile_symcrypt_intermediates(options); 295 | 296 | // Convert already compiled files to a HashSet for faster lookups 297 | let already_compiled_set: HashSet<&str> = already_compiled_files.iter().cloned().collect(); 298 | 299 | // Prepares list of files to be compiled, excluding already compiled files for x86_64_unknown_linux_gnu 300 | let mut base_files: Vec<&'static str> = SOURCES_COMMON 301 | .lines() 302 | .map(str::trim) // Trim once instead of inside filter 303 | .filter(|line| { 304 | !line.is_empty() && !line.starts_with("#") && !already_compiled_set.contains(line) 305 | }) 306 | .collect(); 307 | 308 | base_files.push("env_generic.c"); // symcrypt_generic 309 | 310 | // Add module-specific files for each target 311 | let mut module_files = vec![]; 312 | 313 | match options.triple() { 314 | Triple::x86_64_pc_windows_msvc | Triple::aarch64_pc_windows_msvc => { 315 | base_files.push("env_windowsUserModeWin8_1.c"); 316 | base_files.push("IEEE802_11SaeCustom.c"); 317 | module_files.push("inc/static_WindowsDefault.c"); 318 | } 319 | Triple::x86_64_unknown_linux_gnu => { 320 | base_files.push("linux/intrinsics.c"); // Only needed for x86_64_unknown_linux_gnu 321 | base_files.push("env_posixUserMode.c"); 322 | module_files.push("inc/static_LinuxDefault.c"); 323 | } 324 | Triple::aarch64_unknown_linux_gnu => { 325 | base_files.push("env_posixUserMode.c"); 326 | module_files.push("inc/static_LinuxDefault.c"); 327 | } 328 | } 329 | 330 | // Add assembly pre generated ASM files to be compiled 331 | // ASM files come from lib/CMakeLists.txt 332 | let asm_files = match options.triple() { 333 | Triple::x86_64_pc_windows_msvc => vec![ 334 | "aesasm-gas.asm", 335 | "fdef_asm-gas.asm", 336 | "fdef369_asm-gas.asm", 337 | "fdef_mulx-gas.asm", 338 | "wipe-gas.asm", 339 | "sha256xmm_asm-gas.asm", 340 | "sha256ymm_asm-gas.asm", 341 | "sha512ymm_asm-gas.asm", 342 | "sha512ymm_avx512vl_asm-gas.asm", 343 | ], 344 | Triple::aarch64_pc_windows_msvc => { 345 | vec!["fdef_asm-gas.asm", "fdef369_asm-gas.asm", "wipe-gas.asm"] 346 | } 347 | Triple::x86_64_unknown_linux_gnu => vec![ 348 | "aesasm-gas.asm", 349 | "fdef_asm-gas.asm", 350 | "fdef369_asm-gas.asm", 351 | "fdef_mulx-gas.asm", 352 | "wipe-gas.asm", 353 | "sha256xmm_asm-gas.asm", 354 | "sha256ymm_asm-gas.asm", 355 | "sha512ymm_asm-gas.asm", 356 | "sha512ymm_avx512vl_asm-gas.asm", 357 | ], 358 | Triple::aarch64_unknown_linux_gnu => { 359 | vec!["fdef_asm-gas.asm", "fdef369_asm-gas.asm", "wipe-gas.asm"] 360 | } 361 | }; 362 | 363 | // Pre-Configure the cc compiler based on the target triple 364 | let mut cc = options.preconfigure_cc(); 365 | 366 | // Add in the intermediates that were previously compiled, will be empty for most targets 367 | cc.objects(intermediates); 368 | 369 | // Add base files to be compiled 370 | for file in base_files { 371 | cc.file(format!("{SOURCE_DIR}/{file}")); 372 | } 373 | 374 | // Add assembly files to be compiled 375 | if options.use_asm() { 376 | for file in asm_files { 377 | cc.file(format!( 378 | "{SOURCE_DIR}/asm/{}/{file}", // TODO: replace with right file path when ASM checked in. 379 | options.triple.to_triple() 380 | )); 381 | } 382 | } 383 | 384 | // Add module-specific files to be compiled 385 | cc.files(module_files); 386 | 387 | println!("Files to compile: {}", cc.get_files().count()); 388 | 389 | // Compiles all files and returns the compiled library 390 | cc.compile(lib_name); 391 | 392 | Ok(()) 393 | } 394 | 395 | // Special compile files for x86_64_unknown_linux_gnu 396 | const X86_64_LINUX_CUSTOM_COMPILE_FILES: &str = r#" 397 | aes-ymm.c "-mavx;-mavx2;-mvaes;-mvpclmulqdq" 398 | sha256Par-ymm.c "-mavx;-mavx2" 399 | sha512Par-ymm.c "-mavx;-mavx2" 400 | sha256-xmm.c "-mssse3" 401 | sha256-ymm.c "-mavx;-mavx2;-mbmi2" 402 | sha512-ymm.c "-mavx;-mavx2;-mbmi2" 403 | "#; 404 | 405 | //set_source_files_properties(sha512-ymm.c PROPERTIES COMPILE_OPTIONS "-mavx;-mavx2;-mbmi2") 406 | 407 | /// Compiles the SymCrypt custom intermediates 408 | /// 409 | /// Currently this is only required for x86_64_unknown_linux_gnu, 410 | /// but can be modified to include other targets as needed. 411 | /// 412 | /// If the target is not `x86_64_unknown_linux_gnu`, it returns empty vectors 413 | fn compile_symcrypt_intermediates( 414 | symcrypt_options: &SymCryptOptions, 415 | ) -> (Vec<&'static str>, Vec) { 416 | let mut files = vec![]; 417 | let mut intermediates = vec![]; 418 | 419 | // Only compile intermediates for x86_64_unknown_linux_gnu. 420 | // Can modify with additional targets as needed. 421 | if symcrypt_options.triple() != Triple::x86_64_unknown_linux_gnu { 422 | return (files, intermediates); // No intermediates to compile 423 | } 424 | 425 | // Fetch preconfigured cc based on the target triple. 426 | let mut cc = symcrypt_options.preconfigure_cc(); 427 | 428 | for line in X86_64_LINUX_CUSTOM_COMPILE_FILES.lines() { 429 | if line.trim().is_empty() || line.trim().starts_with("#") { 430 | continue; 431 | } 432 | 433 | // Example of parts: 434 | // [aes-ymm.c, "-mavx;-mavx2;-mvaes;-mvpclmulqdq"] 435 | let parts: Vec<&str> = line.split_whitespace().collect(); 436 | if parts.len() < 2 { 437 | continue; 438 | } 439 | 440 | let file = parts[0]; 441 | println!("Compiling {file} with custom options: {}", parts[1]); 442 | 443 | // Isolate the compile options 444 | let options = parts[1] 445 | .trim_matches('"') 446 | .split(';') 447 | .filter(|s| !s.is_empty()); 448 | 449 | // Push intermediates to the cc object to be compiled 450 | cc.file(format!("{SOURCE_DIR}/{file}")); 451 | for option in options { 452 | cc.flag(option); 453 | } 454 | 455 | // Add the file to the list of files to be replaced by compiled intermediates. 456 | files.push(file); 457 | } 458 | 459 | // Use cc's compile_intermediates() to batch generate intermediate files without linking 460 | let mut result = cc.compile_intermediates(); 461 | intermediates.append(&mut result); 462 | 463 | // Return files to be replaced by intermediates. 464 | (files, intermediates) 465 | } 466 | -------------------------------------------------------------------------------- /symcrypt-sys/build/triple.rs: -------------------------------------------------------------------------------- 1 | #[allow(non_camel_case_types)] 2 | #[derive(Debug, PartialEq, Eq, Clone)] 3 | 4 | /// The `Triple` enum represents the target architecture and operating system. 5 | pub enum Triple { 6 | x86_64_pc_windows_msvc, 7 | aarch64_pc_windows_msvc, 8 | x86_64_unknown_linux_gnu, 9 | aarch64_unknown_linux_gnu, 10 | } 11 | 12 | impl Triple { 13 | pub fn get_target_triple() -> Self { 14 | // Extract target OS and architecture from environment variables 15 | let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap(); 16 | let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap(); 17 | 18 | match (target_os.as_str(), target_arch.as_str()) { 19 | ("windows", "x86_64") => Triple::x86_64_pc_windows_msvc, 20 | ("windows", "aarch64") => Triple::aarch64_pc_windows_msvc, 21 | ("linux", "x86_64") => Triple::x86_64_unknown_linux_gnu, 22 | ("linux", "aarch64") => Triple::aarch64_unknown_linux_gnu, 23 | _ => panic!("unsupported target. OS: {target_os}, Arch: {target_arch}"), 24 | } 25 | } 26 | pub fn to_triple(&self) -> &'static str { 27 | match self { 28 | Triple::x86_64_pc_windows_msvc => "x86_64-pc-windows-msvc", 29 | Triple::aarch64_pc_windows_msvc => "aarch64-pc-windows-msvc", 30 | Triple::x86_64_unknown_linux_gnu => "x86_64-unknown-linux-gnu", 31 | Triple::aarch64_unknown_linux_gnu => "aarch64-unknown-linux-gnu", 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /symcrypt-sys/inc/buildInfo.h: -------------------------------------------------------------------------------- 1 | #include "symcrypt_internal_shared.inc" 2 | 3 | #define _SYMCRYPT_STRING_INT(a) #a 4 | #define _SYMCRYPT_STRING(a) _SYMCRYPT_STRING_INT(a) 5 | #define SYMCRYPT_BUILD_INFO_BRANCH "" 6 | #define SYMCRYPT_BUILD_INFO_COMMIT "2025-01-28T01:44:15+01:00_53be637" 7 | #define SYMCRYPT_BUILD_INFO_VERSION _SYMCRYPT_STRING(SYMCRYPT_CODE_VERSION_API) "." _SYMCRYPT_STRING(SYMCRYPT_CODE_VERSION_MINOR) "." _SYMCRYPT_STRING(SYMCRYPT_CODE_VERSION_PATCH) 8 | #define SYMCRYPT_BUILD_INFO_TIMESTAMP "2025-02-07T14:10:46" 9 | -------------------------------------------------------------------------------- /symcrypt-sys/inc/static_LinuxDefault.c: -------------------------------------------------------------------------------- 1 | // 2 | // static_LinuxDefault.c 3 | // Default implementation for Linux static shared object. 4 | // 5 | // Copyright (c) Microsoft Corporation. Licensed under the MIT license. 6 | // 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "wrapper.h" 14 | #include 15 | #include 16 | 17 | SYMCRYPT_ENVIRONMENT_POSIX_USERMODE; 18 | 19 | PVOID 20 | SYMCRYPT_CALL 21 | SymCryptCallbackAlloc( SIZE_T nBytes ) 22 | { 23 | // aligned_alloc requires size to be integer multiple of alignment 24 | SIZE_T cbAllocation = (nBytes + (SYMCRYPT_ASYM_ALIGN_VALUE - 1)) & ~(SYMCRYPT_ASYM_ALIGN_VALUE - 1); 25 | 26 | return aligned_alloc(SYMCRYPT_ASYM_ALIGN_VALUE, cbAllocation); 27 | } 28 | 29 | VOID 30 | SYMCRYPT_CALL 31 | SymCryptCallbackFree( VOID * pMem ) 32 | { 33 | free( pMem ); 34 | } 35 | 36 | // From Linux docs on getrandom: 37 | // RETURN VALUE top 38 | // On success, getrandom() returns the number of bytes that were 39 | // copied to the buffer buf. This may be less than the number of 40 | // bytes requested via buflen if either GRND_RANDOM was specified in 41 | // flags and insufficient entropy was present in the random source or 42 | // the system call was interrupted by a signal. 43 | // On error, -1 is returned, and errno is set to indicate the error. 44 | SYMCRYPT_ERROR 45 | SYMCRYPT_CALL 46 | SymCryptCallbackRandom(unsigned char *pbBuffer, size_t cbBuffer) 47 | { 48 | size_t total_received = 0; 49 | ssize_t result; 50 | 51 | while (total_received < cbBuffer) { 52 | result = getrandom(pbBuffer + total_received, cbBuffer - total_received, 0); 53 | if (result < 0) { 54 | if (errno == EINTR) { 55 | // Buffer is not yet full, continue to get more entropy 56 | continue; 57 | } 58 | return SYMCRYPT_EXTERNAL_FAILURE; 59 | } 60 | total_received += (size_t)result; 61 | } 62 | return SYMCRYPT_NO_ERROR; 63 | } 64 | -------------------------------------------------------------------------------- /symcrypt-sys/inc/static_WindowsDefault.c: -------------------------------------------------------------------------------- 1 | // 2 | // static_WindowsDefault.c 3 | // Default implementation for Windows static shared object. 4 | // 5 | // Copyright (c) Microsoft Corporation. Licensed under the MIT license. 6 | // 7 | 8 | #define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) 9 | 10 | // Ensure that windows.h doesn't re-define the status_* symbols 11 | #define WIN32_NO_STATUS 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | SYMCRYPT_ENVIRONMENT_WINDOWS_USERMODE_LATEST; 19 | 20 | PVOID 21 | SYMCRYPT_CALL 22 | SymCryptCallbackAlloc( SIZE_T nBytes ) 23 | { 24 | return _aligned_malloc( nBytes, SYMCRYPT_ASYM_ALIGN_VALUE ); 25 | } 26 | 27 | VOID 28 | SYMCRYPT_CALL 29 | SymCryptCallbackFree(PVOID ptr) 30 | { 31 | _aligned_free( ptr ); 32 | } 33 | 34 | SYMCRYPT_ERROR 35 | SYMCRYPT_CALL 36 | SymCryptCallbackRandom( 37 | _Out_writes_bytes_( cbBuffer ) PBYTE pbBuffer, 38 | SIZE_T cbBuffer ) 39 | { 40 | NTSTATUS status = BCryptGenRandom( BCRYPT_RNG_ALG_HANDLE, pbBuffer, (ULONG) cbBuffer, 0 ); 41 | 42 | return NT_SUCCESS( status ) ? SYMCRYPT_NO_ERROR : SYMCRYPT_EXTERNAL_FAILURE; 43 | } 44 | -------------------------------------------------------------------------------- /symcrypt-sys/inc/symcrypt_internal_shared.inc: -------------------------------------------------------------------------------- 1 | // 2 | // symcrypt_internal_shared.inc 3 | // Copyright (c) Microsoft Corporation. Licensed under the MIT license. 4 | // 5 | // This is the file that contains the SymCrypt version information and defines SYMCRYPT_DEBUG. 6 | // It is included in both C and ASM such that the values are the same on both sides. 7 | // We use the C preprocessor to set ASM constants, as we already need to use the C preprocessor for 8 | // symcryptasm processing (see scripts/symcryptasm_processor.py). 9 | // 10 | // In previous releases we had a numbering system with major/minor version number. 11 | // This worked well with the sequential servicing imposed by SourceDepot. 12 | // With the switch to Git this no longer works due to having multiple branches. 13 | // We move to having the version here only specify the API and minor version number 14 | // These will NOT be changed for every build. The API version only changes when there are 15 | // breaking changes to the API in symcrypt.h. (Note: symcrypt_low_level.h is not stable and can change 16 | // at any time.) The minor version is changed at regular intervals, but not necessarily at 17 | // every build of the library. 18 | // 19 | // Separate from these numbers the build system includes information about the branch, 20 | // last commit, build time, etc. 21 | // 22 | // The API numbering starts at 100 to avoid number conflicts with the old system. 23 | // 24 | 25 | #define SYMCRYPT_CODE_VERSION_API 103 26 | #define SYMCRYPT_CODE_VERSION_MINOR 8 27 | #define SYMCRYPT_CODE_VERSION_PATCH 0 28 | 29 | #if defined(DBG) 30 | #define SYMCRYPT_DEBUG 1 31 | #else 32 | #define SYMCRYPT_DEBUG 0 33 | #endif 34 | -------------------------------------------------------------------------------- /symcrypt-sys/inc/wrapper.h: -------------------------------------------------------------------------------- 1 | #ifdef __linux__ 2 | #include 3 | #endif 4 | 5 | #include "../symcrypt/inc/symcrypt.h" 6 | -------------------------------------------------------------------------------- /symcrypt-sys/src/bindings.rs: -------------------------------------------------------------------------------- 1 | #[cfg(all(target_os = "windows", target_arch = "x86_64"))] 2 | mod x86_64_pc_windows_msvc; 3 | #[cfg(all(target_os = "windows", target_arch = "x86_64"))] 4 | pub use x86_64_pc_windows_msvc::*; 5 | 6 | #[cfg(all(target_os = "windows", target_arch = "aarch64"))] 7 | mod aarch64_pc_windows_msvc; 8 | #[cfg(all(target_os = "windows", target_arch = "aarch64"))] 9 | pub use aarch64_pc_windows_msvc::*; 10 | 11 | #[cfg(all(target_os = "linux", target_arch = "x86_64"))] 12 | mod x86_64_unknown_linux_gnu; 13 | #[cfg(all(target_os = "linux", target_arch = "x86_64"))] 14 | pub use x86_64_unknown_linux_gnu::*; 15 | 16 | #[cfg(all(target_os = "linux", target_arch = "aarch64"))] 17 | mod aarch64_unknown_linux_gnu; 18 | #[cfg(all(target_os = "linux", target_arch = "aarch64"))] 19 | pub use aarch64_unknown_linux_gnu::*; 20 | -------------------------------------------------------------------------------- /symcrypt-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![allow(non_upper_case_globals)] 3 | #![allow(non_camel_case_types)] 4 | #![allow(non_snake_case)] 5 | #![allow(clippy::all)] 6 | 7 | extern crate libc; 8 | 9 | // Include bindings depending on which OS and architecture we are compiling for. 10 | // Current supported are: 11 | 12 | // Windows: 13 | // windows amd64 x86_64-pc-windows-msvc 14 | // windows arm64 aarch64-pc-windows-msvc 15 | 16 | // Linux: 17 | // linux amd64 x86_64-unknown-linux-gnu 18 | // linux arm64 aarch64-unknown-linux-gnu 19 | mod bindings; 20 | 21 | pub use bindings::*; 22 | --------------------------------------------------------------------------------