parsec-client-0.16.0/.cargo/audit.toml000064400000000000000000000021041046102023000156120ustar 00000000000000[advisories] ignore = ["RUSTSEC-2021-0139"] # Remove when spiffe is upgraded informational_warnings = ["unmaintained"] # warn for categories of informational advisories severity_threshold = "low" # CVSS severity ("none", "low", "medium", "high", "critical") # Advisory Database Configuration [database] path = "/tmp/advisory-db" # Path where advisory git repo will be cloned url = "https://github.com/RustSec/advisory-db.git" # URL to git repo fetch = true # Perform a `git fetch` before auditing stale = false # Allow stale advisory DB (i.e. no commits for 90 days) # Output Configuration [output] deny = ["unmaintained"] # exit on error if unmaintained dependencies are found format = "terminal" # "terminal" (human readable report) or "json" quiet = false # Only print information on error show_tree = true # Show inverse dependency trees along with advisories # Target Configuration [target] os = "linux" # Ignore advisories for operating systems other than this one [yanked] enabled = true # Warn for yanked crates in Cargo.lock update_index = true # Auto-update the crates.io index parsec-client-0.16.0/.cargo_vcs_info.json0000644000000001360000000000100136510ustar { "git": { "sha1": "6682fb80254c64d33f18264648286627a6936c7f" }, "path_in_vcs": "" }parsec-client-0.16.0/.github/workflows/ci.yml000064400000000000000000000012451046102023000171560ustar 00000000000000name: Continuous Integration on: [push, pull_request, workflow_dispatch] jobs: build: name: Build and check formatting runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python 3.7 uses: actions/setup-python@v1 with: python-version: 3.7 - name: Download Parsec Mock, install dependencies and execute all tests run: | curl -s -N -L https://github.com/parallaxsecond/parsec-mock/archive/refs/tags/0.1.1.tar.gz | tar xz cd parsec-mock-0.1.1/ python -m pip install --upgrade pip pip install -r requirements.txt cd .. ./tests/ci.sh parsec-client-0.16.0/.github/workflows/nightly.yml000064400000000000000000000031641046102023000202430ustar 00000000000000name: Nightly Checks on: schedule: # Every night at midnight - cron: "0 0 * * *" workflow_dispatch: inputs: rev: description: "Revision hash to run against" required: false default: "" jobs: dependencies: name: Check for unused dependencies runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: ref: "${{ github.event.inputs.rev }}" - name: Install latest Rust uses: actions-rs/toolchain@v1 with: toolchain: nightly - name: Install cargo udeps run: cargo install cargo-udeps --locked - name: Execute cargo udeps run: cargo +nightly udeps audit: name: Check for crates with security vulnerabilities runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: ref: "${{ github.event.inputs.rev }}" - name: Install latest Rust uses: actions-rs/toolchain@v1 with: toolchain: nightly - name: Install cargo audit run: cargo install cargo-audit - name: Execute cargo audit run: cargo audit coverage: name: Calculate coverage from tests runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: ref: "${{ github.event.inputs.rev }}" - name: Execute tarpaulin run: | curl -s -N -L https://github.com/parallaxsecond/parsec-mock/archive/refs/tags/0.1.1.tar.gz | tar xz cd parsec-mock-0.1.1/ python -m pip install --upgrade pip pip install -r requirements.txt cd .. ./tests/coverage.sh parsec-client-0.16.0/.gitignore000064400000000000000000000000501046102023000144240ustar 00000000000000/target *patch Cargo.lock cobertura.xml parsec-client-0.16.0/.travis.yml.disabled000064400000000000000000000001571046102023000163230ustar 00000000000000# Executing our tests on Arm64 with Travis CI dist: focal arch: arm64 language: rust script: - ./tests/ci.sh parsec-client-0.16.0/CHANGELOG.md000064400000000000000000000456021046102023000142610ustar 00000000000000# Changelog ## [0.16.0](https://github.com/parallaxsecond/parsec-client-rust/tree/0.16.0) (2023-10-06) [Full Changelog](https://github.com/parallaxsecond/parsec-client-rust/compare/0.15.0...0.16.0) **Merged pull requests:** - Bump parsec-interface to 0.29.0 [\#115](https://github.com/parallaxsecond/parsec-client-rust/pull/115) ([tgonzalezorlandoarm](https://github.com/tgonzalezorlandoarm)) - ci/nightly: use actions/checkoutv2 for coverage builds [\#114](https://github.com/parallaxsecond/parsec-client-rust/pull/114) ([tgonzalezorlandoarm](https://github.com/tgonzalezorlandoarm)) - ci/nightly: Run parsec-mock for coverage tests [\#113](https://github.com/parallaxsecond/parsec-client-rust/pull/113) ([tgonzalezorlandoarm](https://github.com/tgonzalezorlandoarm)) - .cargo/audit: Ignore unmaintained ansi\_term util spiffe is upgraded [\#112](https://github.com/parallaxsecond/parsec-client-rust/pull/112) ([tgonzalezorlandoarm](https://github.com/tgonzalezorlandoarm)) - .cargo/audit: Remove deprecated packages field [\#111](https://github.com/parallaxsecond/parsec-client-rust/pull/111) ([tgonzalezorlandoarm](https://github.com/tgonzalezorlandoarm)) - Remove unmaintained 'users' crate [\#110](https://github.com/parallaxsecond/parsec-client-rust/pull/110) ([tgonzalezorlandoarm](https://github.com/tgonzalezorlandoarm)) ## [0.15.0](https://github.com/parallaxsecond/parsec-client-rust/tree/0.15.0) (2023-01-04) [Full Changelog](https://github.com/parallaxsecond/parsec-client-rust/compare/0.14.1...0.15.0) **Merged pull requests:** - Update to remove const\_err [\#107](https://github.com/parallaxsecond/parsec-client-rust/pull/107) ([marcsvll](https://github.com/marcsvll)) ## [0.14.1](https://github.com/parallaxsecond/parsec-client-rust/tree/0.14.1) (2022-09-12) [Full Changelog](https://github.com/parallaxsecond/parsec-client-rust/compare/0.14.0...0.14.1) **Merged pull requests:** - Bump Crates' versions [\#105](https://github.com/parallaxsecond/parsec-client-rust/pull/105) ([mohamedasaker-arm](https://github.com/mohamedasaker-arm)) - Build parse-interface with deprecated primitive feature changes [\#104](https://github.com/parallaxsecond/parsec-client-rust/pull/104) ([mohamedasaker-arm](https://github.com/mohamedasaker-arm)) ## [0.14.0](https://github.com/parallaxsecond/parsec-client-rust/tree/0.14.0) (2022-02-15) [Full Changelog](https://github.com/parallaxsecond/parsec-client-rust/compare/0.13.0...0.14.0) **Implemented enhancements:** - Add activate credential attestation methods [\#100](https://github.com/parallaxsecond/parsec-client-rust/pull/100) ([ionut-arm](https://github.com/ionut-arm)) **Merged pull requests:** - Prepare for new version of client [\#102](https://github.com/parallaxsecond/parsec-client-rust/pull/102) ([ionut-arm](https://github.com/ionut-arm)) - Add PsaCipherEncrypt and PsaCipherDecrypt operations [\#101](https://github.com/parallaxsecond/parsec-client-rust/pull/101) ([akazimierskigl](https://github.com/akazimierskigl)) - Merge can-do-crypto branch into main [\#98](https://github.com/parallaxsecond/parsec-client-rust/pull/98) ([anta5010](https://github.com/anta5010)) - Use the main branch of Parsec-interface [\#97](https://github.com/parallaxsecond/parsec-client-rust/pull/97) ([anta5010](https://github.com/anta5010)) - Merge origin/main into can-do-crypto [\#96](https://github.com/parallaxsecond/parsec-client-rust/pull/96) ([anta5010](https://github.com/anta5010)) - Use Parsec Mock for testing [\#95](https://github.com/parallaxsecond/parsec-client-rust/pull/95) ([hug-dev](https://github.com/hug-dev)) - Bump prost and spiffe for cargo-audit [\#94](https://github.com/parallaxsecond/parsec-client-rust/pull/94) ([hug-dev](https://github.com/hug-dev)) - Added CanDoCrypto to basic client. [\#93](https://github.com/parallaxsecond/parsec-client-rust/pull/93) ([Kakemone](https://github.com/Kakemone)) - Update CHANGELOG [\#92](https://github.com/parallaxsecond/parsec-client-rust/pull/92) ([hug-dev](https://github.com/hug-dev)) ## [0.13.0](https://github.com/parallaxsecond/parsec-client-rust/tree/0.13.0) (2021-08-04) [Full Changelog](https://github.com/parallaxsecond/parsec-client-rust/compare/0.12.0...0.13.0) **Implemented enhancements:** - Use &str for key names instead of String [\#81](https://github.com/parallaxsecond/parsec-client-rust/issues/81) - Investigate a SignClient for just-in-time key creation [\#70](https://github.com/parallaxsecond/parsec-client-rust/issues/70) - Modify interface to take parameters as reference [\#31](https://github.com/parallaxsecond/parsec-client-rust/issues/31) - Add SPIFFE authentication via the new crate [\#85](https://github.com/parallaxsecond/parsec-client-rust/pull/85) ([hug-dev](https://github.com/hug-dev)) **Fixed bugs:** - Change Codecov badge to main branch [\#89](https://github.com/parallaxsecond/parsec-client-rust/pull/89) ([ionut-arm](https://github.com/ionut-arm)) **Merged pull requests:** - Prepare for the release [\#90](https://github.com/parallaxsecond/parsec-client-rust/pull/90) ([hug-dev](https://github.com/hug-dev)) - Add cargo-audit config file. [\#88](https://github.com/parallaxsecond/parsec-client-rust/pull/88) ([ionut-arm](https://github.com/ionut-arm)) - Changed to use &str for key\_name parameters. [\#86](https://github.com/parallaxsecond/parsec-client-rust/pull/86) ([MattDavis00](https://github.com/MattDavis00)) - Update psa-crypto [\#84](https://github.com/parallaxsecond/parsec-client-rust/pull/84) ([hug-dev](https://github.com/hug-dev)) - Support for ps\_sign\_ and ps\_verify\_message Parsec operations. [\#83](https://github.com/parallaxsecond/parsec-client-rust/pull/83) ([RobertDrazkowskiGL](https://github.com/RobertDrazkowskiGL)) - Add dependency on the newest \(git only at the moment\) parsec-interface. [\#82](https://github.com/parallaxsecond/parsec-client-rust/pull/82) ([RobertDrazkowskiGL](https://github.com/RobertDrazkowskiGL)) - Add the CHANGELOG [\#80](https://github.com/parallaxsecond/parsec-client-rust/pull/80) ([hug-dev](https://github.com/hug-dev)) ## [0.12.0](https://github.com/parallaxsecond/parsec-client-rust/tree/0.12.0) (2021-03-18) [Full Changelog](https://github.com/parallaxsecond/parsec-client-rust/compare/0.11.0...0.12.0) **Implemented enhancements:** - Implement component bootstrapping [\#52](https://github.com/parallaxsecond/parsec-client-rust/issues/52) - Resolve service endpoint from a URI environment variable [\#37](https://github.com/parallaxsecond/parsec-client-rust/issues/37) - Add code coverage checking to the nightly run [\#77](https://github.com/parallaxsecond/parsec-client-rust/pull/77) ([ionut-arm](https://github.com/ionut-arm)) - Bootstrap socket location from env variable [\#73](https://github.com/parallaxsecond/parsec-client-rust/pull/73) ([ionut-arm](https://github.com/ionut-arm)) - Increase the default timeout to 60 seconds [\#72](https://github.com/parallaxsecond/parsec-client-rust/pull/72) ([hug-dev](https://github.com/hug-dev)) - Add changelog file [\#60](https://github.com/parallaxsecond/parsec-client-rust/pull/60) ([ionut-arm](https://github.com/ionut-arm)) **Closed issues:** - Add support for ListClients and DeleteClient [\#66](https://github.com/parallaxsecond/parsec-client-rust/issues/66) - Add a JWT-SVID authentication data [\#55](https://github.com/parallaxsecond/parsec-client-rust/issues/55) **Merged pull requests:** - Prepare for version 0.12.0 [\#79](https://github.com/parallaxsecond/parsec-client-rust/pull/79) ([hug-dev](https://github.com/hug-dev)) - Update the interface to latest master [\#78](https://github.com/parallaxsecond/parsec-client-rust/pull/78) ([hug-dev](https://github.com/hug-dev)) - Update hash to latest interface [\#76](https://github.com/parallaxsecond/parsec-client-rust/pull/76) ([hug-dev](https://github.com/hug-dev)) - Move a log message from info to debug [\#75](https://github.com/parallaxsecond/parsec-client-rust/pull/75) ([hug-dev](https://github.com/hug-dev)) - Add documentation about the endpoint env. var. [\#74](https://github.com/parallaxsecond/parsec-client-rust/pull/74) ([hug-dev](https://github.com/hug-dev)) - Update interface dependency [\#71](https://github.com/parallaxsecond/parsec-client-rust/pull/71) ([ionut-arm](https://github.com/ionut-arm)) - Remove spiffe-based feature from master [\#68](https://github.com/parallaxsecond/parsec-client-rust/pull/68) ([hug-dev](https://github.com/hug-dev)) - Add ListClients and DeleteClient operations [\#67](https://github.com/parallaxsecond/parsec-client-rust/pull/67) ([hug-dev](https://github.com/hug-dev)) - Disable Travis CI build [\#65](https://github.com/parallaxsecond/parsec-client-rust/pull/65) ([ionut-arm](https://github.com/ionut-arm)) - Consume parsec-interface at 0.22.0 and bump crate to 0.12.0 [\#63](https://github.com/parallaxsecond/parsec-client-rust/pull/63) ([paulhowardarm](https://github.com/paulhowardarm)) - Add a note about JWT-SVID Workload Endpoint [\#62](https://github.com/parallaxsecond/parsec-client-rust/pull/62) ([hug-dev](https://github.com/hug-dev)) - Add a JWT-SVID authentication method [\#61](https://github.com/parallaxsecond/parsec-client-rust/pull/61) ([hug-dev](https://github.com/hug-dev)) ## [0.11.0](https://github.com/parallaxsecond/parsec-client-rust/tree/0.11.0) (2020-10-20) [Full Changelog](https://github.com/parallaxsecond/parsec-client-rust/compare/0.10.0...0.11.0) **Implemented enhancements:** - Implement provider and auth bootstrapping [\#58](https://github.com/parallaxsecond/parsec-client-rust/pull/58) ([ionut-arm](https://github.com/ionut-arm)) - Add Unix Peer Credential auth support [\#57](https://github.com/parallaxsecond/parsec-client-rust/pull/57) ([ionut-arm](https://github.com/ionut-arm)) - Remove filesystem checks [\#56](https://github.com/parallaxsecond/parsec-client-rust/pull/56) ([ionut-arm](https://github.com/ionut-arm)) **Fixed bugs:** - Socket path security checks can fail when the client is in a container [\#51](https://github.com/parallaxsecond/parsec-client-rust/issues/51) **Closed issues:** - Implement new authenticator support [\#41](https://github.com/parallaxsecond/parsec-client-rust/issues/41) **Merged pull requests:** - Add a new construction for naked client [\#59](https://github.com/parallaxsecond/parsec-client-rust/pull/59) ([hug-dev](https://github.com/hug-dev)) - Quickfix: replace parsec-interface patch with a direct dependency [\#54](https://github.com/parallaxsecond/parsec-client-rust/pull/54) ([joechrisellis](https://github.com/joechrisellis)) - Add ListKeys support [\#53](https://github.com/parallaxsecond/parsec-client-rust/pull/53) ([joechrisellis](https://github.com/joechrisellis)) ## [0.10.0](https://github.com/parallaxsecond/parsec-client-rust/tree/0.10.0) (2020-10-02) [Full Changelog](https://github.com/parallaxsecond/parsec-client-rust/compare/0.9.0...0.10.0) ## [0.9.0](https://github.com/parallaxsecond/parsec-client-rust/tree/0.9.0) (2020-09-07) [Full Changelog](https://github.com/parallaxsecond/parsec-client-rust/compare/0.8.0...0.9.0) **Implemented enhancements:** - Manage data safely within the client [\#9](https://github.com/parallaxsecond/parsec-client-rust/issues/9) - Upgrade dependencies versions [\#48](https://github.com/parallaxsecond/parsec-client-rust/pull/48) ([hug-dev](https://github.com/hug-dev)) ## [0.8.0](https://github.com/parallaxsecond/parsec-client-rust/tree/0.8.0) (2020-08-18) [Full Changelog](https://github.com/parallaxsecond/parsec-client-rust/compare/0.7.1...0.8.0) **Implemented enhancements:** - Added has compute and compare [\#45](https://github.com/parallaxsecond/parsec-client-rust/pull/45) ([sbailey-arm](https://github.com/sbailey-arm)) **Merged pull requests:** - Add test for psa\_generate\_random [\#47](https://github.com/parallaxsecond/parsec-client-rust/pull/47) ([joechrisellis](https://github.com/joechrisellis)) - Added raw key agreement and test [\#46](https://github.com/parallaxsecond/parsec-client-rust/pull/46) ([sbailey-arm](https://github.com/sbailey-arm)) - Added aead encrypt and decrypt [\#44](https://github.com/parallaxsecond/parsec-client-rust/pull/44) ([sbailey-arm](https://github.com/sbailey-arm)) - Add support for ListAuthenticators operation [\#43](https://github.com/parallaxsecond/parsec-client-rust/pull/43) ([joechrisellis](https://github.com/joechrisellis)) - Add Rust client support for `psa_generate_random` operation [\#42](https://github.com/parallaxsecond/parsec-client-rust/pull/42) ([joechrisellis](https://github.com/joechrisellis)) ## [0.7.1](https://github.com/parallaxsecond/parsec-client-rust/tree/0.7.1) (2020-07-22) [Full Changelog](https://github.com/parallaxsecond/parsec-client-rust/compare/0.7.0...0.7.1) **Implemented enhancements:** - Publish a new version [\#40](https://github.com/parallaxsecond/parsec-client-rust/pull/40) ([hug-dev](https://github.com/hug-dev)) - Implement `Error` and `Display` traits for `parsec_client::error::Error` [\#39](https://github.com/parallaxsecond/parsec-client-rust/pull/39) ([joechrisellis](https://github.com/joechrisellis)) ## [0.7.0](https://github.com/parallaxsecond/parsec-client-rust/tree/0.7.0) (2020-07-15) [Full Changelog](https://github.com/parallaxsecond/parsec-client-rust/compare/0.6.0...0.7.0) **Implemented enhancements:** - Added PsaExportKey [\#38](https://github.com/parallaxsecond/parsec-client-rust/pull/38) ([sbailey-arm](https://github.com/sbailey-arm)) ## [0.6.0](https://github.com/parallaxsecond/parsec-client-rust/tree/0.6.0) (2020-07-07) [Full Changelog](https://github.com/parallaxsecond/parsec-client-rust/compare/0.5.0...0.6.0) **Merged pull requests:** - Added asymmetric encrypt and decrypt [\#36](https://github.com/parallaxsecond/parsec-client-rust/pull/36) ([sbailey-arm](https://github.com/sbailey-arm)) ## [0.5.0](https://github.com/parallaxsecond/parsec-client-rust/tree/0.5.0) (2020-07-02) [Full Changelog](https://github.com/parallaxsecond/parsec-client-rust/compare/0.4.0...0.5.0) **Implemented enhancements:** - Add memory wiping functionality [\#32](https://github.com/parallaxsecond/parsec-client-rust/pull/32) ([ionut-arm](https://github.com/ionut-arm)) **Fixed bugs:** - Fix the fs check on the socket folder feature [\#35](https://github.com/parallaxsecond/parsec-client-rust/pull/35) ([hug-dev](https://github.com/hug-dev)) - Import the newer interface [\#34](https://github.com/parallaxsecond/parsec-client-rust/pull/34) ([hug-dev](https://github.com/hug-dev)) **Merged pull requests:** - Change socket location and add checks [\#33](https://github.com/parallaxsecond/parsec-client-rust/pull/33) ([hug-dev](https://github.com/hug-dev)) ## [0.4.0](https://github.com/parallaxsecond/parsec-client-rust/tree/0.4.0) (2020-06-05) [Full Changelog](https://github.com/parallaxsecond/parsec-client-rust/compare/0.3.0...0.4.0) **Implemented enhancements:** - Import the newest interface and increase version [\#30](https://github.com/parallaxsecond/parsec-client-rust/pull/30) ([hug-dev](https://github.com/hug-dev)) - Import the new interface [\#29](https://github.com/parallaxsecond/parsec-client-rust/pull/29) ([hug-dev](https://github.com/hug-dev)) ## [0.3.0](https://github.com/parallaxsecond/parsec-client-rust/tree/0.3.0) (2020-05-06) [Full Changelog](https://github.com/parallaxsecond/parsec-client-rust/compare/0.2.0...0.3.0) **Implemented enhancements:** - Expose the interface through the client, even for testing [\#25](https://github.com/parallaxsecond/parsec-client-rust/issues/25) - Add Send and Sync to trait objects [\#27](https://github.com/parallaxsecond/parsec-client-rust/pull/27) ([hug-dev](https://github.com/hug-dev)) ## [0.2.0](https://github.com/parallaxsecond/parsec-client-rust/tree/0.2.0) (2020-04-24) [Full Changelog](https://github.com/parallaxsecond/parsec-client-rust/compare/0.1.0...0.2.0) **Implemented enhancements:** - Resurface full interface as part of core [\#26](https://github.com/parallaxsecond/parsec-client-rust/pull/26) ([ionut-arm](https://github.com/ionut-arm)) ## [0.1.0](https://github.com/parallaxsecond/parsec-client-rust/tree/0.1.0) (2020-04-22) [Full Changelog](https://github.com/parallaxsecond/parsec-client-rust/compare/a574ae6083652a7dd57e5e99fbadd05a423143fc...0.1.0) **Implemented enhancements:** - Make the CoreClient really dumb [\#19](https://github.com/parallaxsecond/parsec-client-rust/issues/19) - Extract UnixSocket-specific functionality out of RequestHandler [\#13](https://github.com/parallaxsecond/parsec-client-rust/issues/13) - Create test framework [\#7](https://github.com/parallaxsecond/parsec-client-rust/issues/7) - Implement client-specific error structures [\#5](https://github.com/parallaxsecond/parsec-client-rust/issues/5) - Implement configuration [\#3](https://github.com/parallaxsecond/parsec-client-rust/issues/3) - Add methods for modifying timeout of IPC handlers [\#24](https://github.com/parallaxsecond/parsec-client-rust/pull/24) ([ionut-arm](https://github.com/ionut-arm)) - Make `implicit_provider` optional [\#23](https://github.com/parallaxsecond/parsec-client-rust/pull/23) ([ionut-arm](https://github.com/ionut-arm)) - Add getters for auth\_data and implicit\_provider [\#22](https://github.com/parallaxsecond/parsec-client-rust/pull/22) ([ionut-arm](https://github.com/ionut-arm)) - Add contributing guidelines link [\#21](https://github.com/parallaxsecond/parsec-client-rust/pull/21) ([hug-dev](https://github.com/hug-dev)) - Refactor low level clients [\#20](https://github.com/parallaxsecond/parsec-client-rust/pull/20) ([ionut-arm](https://github.com/ionut-arm)) - Add failing IPC test [\#15](https://github.com/parallaxsecond/parsec-client-rust/pull/15) ([ionut-arm](https://github.com/ionut-arm)) - Factor IPC handling out of the request handler [\#14](https://github.com/parallaxsecond/parsec-client-rust/pull/14) ([ionut-arm](https://github.com/ionut-arm)) - Add testing framework and unit tests [\#12](https://github.com/parallaxsecond/parsec-client-rust/pull/12) ([ionut-arm](https://github.com/ionut-arm)) - Make inner attributes configurable [\#10](https://github.com/parallaxsecond/parsec-client-rust/pull/10) ([ionut-arm](https://github.com/ionut-arm)) - Add client-specific error types [\#8](https://github.com/parallaxsecond/parsec-client-rust/pull/8) ([ionut-arm](https://github.com/ionut-arm)) - Seed initial client [\#1](https://github.com/parallaxsecond/parsec-client-rust/pull/1) ([ionut-arm](https://github.com/ionut-arm)) **Closed issues:** - Rename methods to contain `psa_` prefix [\#17](https://github.com/parallaxsecond/parsec-client-rust/issues/17) - Improve documentation [\#2](https://github.com/parallaxsecond/parsec-client-rust/issues/2) **Merged pull requests:** - Add "psa\_" prefix to method names. [\#18](https://github.com/parallaxsecond/parsec-client-rust/pull/18) ([ionut-arm](https://github.com/ionut-arm)) - Add documentation [\#16](https://github.com/parallaxsecond/parsec-client-rust/pull/16) ([ionut-arm](https://github.com/ionut-arm)) - Update the way copyrights are displayed [\#11](https://github.com/parallaxsecond/parsec-client-rust/pull/11) ([ionut-arm](https://github.com/ionut-arm)) \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* parsec-client-0.16.0/Cargo.toml0000644000000025170000000000100116540ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "parsec-client" version = "0.16.0" authors = ["Contributors to the Parsec project"] description = "Parsec Client library for the Rust ecosystem" documentation = "https://docs.rs/crate/parsec-client" readme = "README.md" keywords = ["parsec"] categories = ["development-tools"] license = "Apache-2.0" repository = "https://github.com/parallaxsecond/parsec-client-rust" [dependencies.derivative] version = "2.1.1" [dependencies.libc] version = "0.2.147" [dependencies.log] version = "0.4.17" [dependencies.num] version = "0.4.0" [dependencies.parsec-interface] version = "0.29.0" [dependencies.spiffe] version = "0.2.0" optional = true [dependencies.url] version = "2.2.0" [dependencies.zeroize] version = "1.1.0" [dev-dependencies.mockstream] version = "0.0.3" [features] default = [] spiffe-auth = ["spiffe"] testing = ["parsec-interface/testing"] parsec-client-0.16.0/Cargo.toml.orig000064400000000000000000000013231046102023000153270ustar 00000000000000[package] name = "parsec-client" version = "0.16.0" authors = ["Contributors to the Parsec project"] description = "Parsec Client library for the Rust ecosystem" license = "Apache-2.0" repository = "https://github.com/parallaxsecond/parsec-client-rust" readme = "README.md" keywords = ["parsec"] categories = ["development-tools"] edition = "2018" documentation = "https://docs.rs/crate/parsec-client" [dependencies] parsec-interface = "0.29.0" num = "0.4.0" log = "0.4.17" derivative = "2.1.1" zeroize = "1.1.0" url = "2.2.0" spiffe = { version = "0.2.0", optional = true } libc = "0.2.147" [dev-dependencies] mockstream = "0.0.3" [features] default = [] spiffe-auth = ["spiffe"] testing = ["parsec-interface/testing"] parsec-client-0.16.0/LICENSE000064400000000000000000000261361046102023000134560ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. parsec-client-0.16.0/MAINTAINERS.toml000064400000000000000000000026151046102023000151140ustar 00000000000000# parsec-client-rust maintainers file # # This file lists the maintainers of the parallaxsecond/parsec-client-rust # project. # # Its structure is inspired from the maintainers files in the Docker Github # repositories. Please see the MAINTAINERS file in docker/opensource for more # information. [maintainers] # Core maintainers of the project. [maintainers.core] people = [ "adamparco", "anta5010", "heavypackets", "hug-dev", "ionut-arm", "justincormack", "paulhowardarm", ] [people] # A reference list of all people associated with the project. [people.adamparco] Name = "Adam Parco" Email = "adam.parco@docker.com" GitHub = "adamparco" [people.anta5010] Name = "Anton Antonov" Email = "anton.antonov@arm.com" GitHub = "anta5010" [people.heavypackets] Name = "Sabree Blackmon" Email = "sabree.blackmon@docker.com" GitHub = "heavypackets" [people.hug-dev] Name = "Hugues de Valon" Email = "hugues.devalon@arm.com" GitHub = "hug-dev" [people.ionut-arm] Name = "Ionut Mihalcea" Email = "ionut.mihalcea@docker.com" GitHub = "ionut-arm" [people.justincormack] Name = "Justin Cormack" Email = "justin.cormack@docker.com" GitHub = "justincormack" [people.paulhowardarm] Name = "Paul Howard" Email = "paul.howard@arm.com" GitHub = "paulhowardarm" parsec-client-0.16.0/README.md000064400000000000000000000035261046102023000137260ustar 00000000000000# Parsec Rust Client

Crates.io Code documentation

This repository contains a Rust client for consuming the API provided by the [Parsec service](https://github.com/parallaxsecond/parsec). The low-level functionality that this library uses for IPC is implemented in the [interface crate](https://github.com/parallaxsecond/parsec-interface-rs). When using the JWT-SVID authentication method, the client will expect the `SPIFFE_ENDPOINT_SOCKET` environment variable to contain the path of the Workload API endpoint. You will need to compile the crate with the `spiffe-auth` feature activated. See the [SPIFFE Workload Endpoint](https://github.com/spiffe/spiffe/blob/master/standards/SPIFFE_Workload_Endpoint.md#4-locating-the-endpoint) for more information. ## Locating the Parsec endpoint The Rust client follows the [service discovery](https://parallaxsecond.github.io/parsec-book/parsec_client/api_overview.html#service-discovery) policy to find the Parsec endpoint. For example if the socket is at `/tmp/parsec.sock`: ``` export PARSEC_SERVICE_ENDPOINT="unix:/tmp/parsec.sock" ``` ## License The software is provided under Apache-2.0. Contributions to this project are accepted under the same license. ## Contributing Please check the [**Contribution Guidelines**](https://parallaxsecond.github.io/parsec-book/contributing/index.html) to know more about the contribution process. *Copyright 2020 Contributors to the Parsec project.* parsec-client-0.16.0/src/auth.rs000064400000000000000000000066441046102023000145510ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Client app authentication data use crate::error::{Error, Result}; use parsec_interface::requests::{request::RequestAuth, AuthType}; use std::convert::TryFrom; /// Authentication data used in Parsec requests #[derive(Clone, Debug)] pub enum Authentication { /// Used in cases where no authentication is desired or required None, /// Data used for direct, identity-based authentication /// /// Warning: Systems using direct authentication require extra measures /// to be as secure as deployments with other authentication mechanisms. /// Please check the /// [Parsec Threat Model](https://parallaxsecond.github.io/parsec-book/parsec_security/parsec_threat_model/threat_model.html) /// for more information. Direct(String), /// Used for authentication via Peer Credentials provided by Unix /// operating systems for Domain Socket connections. UnixPeerCredentials, /// Authentication using JWT SVID tokens. The will fetch its JWT-SVID and pass it in the /// Authentication field. The socket endpoint is found through the SPIFFE_ENDPOINT_SOCKET /// environment variable. #[cfg(feature = "spiffe-auth")] JwtSvid, } impl Authentication { /// Get the Parsec authentication type based on the data type pub fn auth_type(&self) -> AuthType { match self { Authentication::None => AuthType::NoAuth, Authentication::Direct(_) => AuthType::Direct, Authentication::UnixPeerCredentials => AuthType::UnixPeerCredentials, #[cfg(feature = "spiffe-auth")] Authentication::JwtSvid => AuthType::JwtSvid, } } } impl TryFrom<&Authentication> for RequestAuth { type Error = Error; fn try_from(data: &Authentication) -> Result { match data { Authentication::None => Ok(RequestAuth::new(Vec::new())), Authentication::Direct(name) => Ok(RequestAuth::new(name.bytes().collect())), Authentication::UnixPeerCredentials => { let current_uid: libc::uid_t = unsafe { libc::getuid() }; Ok(RequestAuth::new(current_uid.to_le_bytes().to_vec())) } #[cfg(feature = "spiffe-auth")] Authentication::JwtSvid => { use crate::error::ClientErrorKind; use log::error; use spiffe::workload_api::client::WorkloadApiClient; let client = WorkloadApiClient::default().unwrap(); let token = client.fetch_jwt_token(&["parsec"], None).map_err(|e| { error!("Error while fetching the JWT-SVID ({}).", e); Error::Client(ClientErrorKind::Spiffe(e)) })?; Ok(RequestAuth::new(token.as_bytes().into())) } } } } impl PartialEq for Authentication { fn eq(&self, other: &Self) -> bool { match (self, other) { (Authentication::None, Authentication::None) => true, (Authentication::UnixPeerCredentials, Authentication::UnixPeerCredentials) => true, (Authentication::Direct(app_name), Authentication::Direct(other_app_name)) => { app_name == other_app_name } #[cfg(feature = "spiffe-auth")] (Authentication::JwtSvid, Authentication::JwtSvid) => true, _ => false, } } } parsec-client-0.16.0/src/core/basic_client.rs000064400000000000000000001604701046102023000171550ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Basic client for Parsec integration use super::operation_client::OperationClient; use crate::auth::Authentication; use crate::error::{ClientErrorKind, Error, Result}; use log::{debug, warn}; use parsec_interface::operations::attest_key::{Operation as AttestKey, Result as AttestKeyResult}; use parsec_interface::operations::can_do_crypto::{CheckType, Operation as CanDoCrypto}; use parsec_interface::operations::delete_client::Operation as DeleteClient; use parsec_interface::operations::list_authenticators::{ AuthenticatorInfo, Operation as ListAuthenticators, }; use parsec_interface::operations::list_clients::Operation as ListClients; use parsec_interface::operations::list_keys::{KeyInfo, Operation as ListKeys}; use parsec_interface::operations::list_opcodes::Operation as ListOpcodes; use parsec_interface::operations::list_providers::{Operation as ListProviders, ProviderInfo}; use parsec_interface::operations::ping::Operation as Ping; use parsec_interface::operations::prepare_key_attestation::{ Operation as PrepareKeyAttestation, Result as PrepareKeyAttestationResult, }; use parsec_interface::operations::psa_aead_decrypt::Operation as PsaAeadDecrypt; use parsec_interface::operations::psa_aead_encrypt::Operation as PsaAeadEncrypt; use parsec_interface::operations::psa_algorithm::{ Aead, AsymmetricEncryption, AsymmetricSignature, Cipher, Hash, RawKeyAgreement, }; use parsec_interface::operations::psa_asymmetric_decrypt::Operation as PsaAsymDecrypt; use parsec_interface::operations::psa_asymmetric_encrypt::Operation as PsaAsymEncrypt; use parsec_interface::operations::psa_cipher_decrypt::Operation as PsaCipherDecrypt; use parsec_interface::operations::psa_cipher_encrypt::Operation as PsaCipherEncrypt; use parsec_interface::operations::psa_destroy_key::Operation as PsaDestroyKey; use parsec_interface::operations::psa_export_key::Operation as PsaExportKey; use parsec_interface::operations::psa_export_public_key::Operation as PsaExportPublicKey; use parsec_interface::operations::psa_generate_key::Operation as PsaGenerateKey; use parsec_interface::operations::psa_generate_random::Operation as PsaGenerateRandom; use parsec_interface::operations::psa_hash_compare::Operation as PsaHashCompare; use parsec_interface::operations::psa_hash_compute::Operation as PsaHashCompute; use parsec_interface::operations::psa_import_key::Operation as PsaImportKey; use parsec_interface::operations::psa_key_attributes::Attributes; use parsec_interface::operations::psa_raw_key_agreement::Operation as PsaRawKeyAgreement; use parsec_interface::operations::psa_sign_hash::Operation as PsaSignHash; use parsec_interface::operations::psa_sign_message::Operation as PsaSignMessage; use parsec_interface::operations::psa_verify_hash::Operation as PsaVerifyHash; use parsec_interface::operations::psa_verify_message::Operation as PsaVerifyMessage; use parsec_interface::operations::{NativeOperation, NativeResult}; use parsec_interface::requests::AuthType; use parsec_interface::requests::{Opcode, ProviderId}; use parsec_interface::secrecy::{ExposeSecret, Secret}; use std::collections::HashSet; use zeroize::Zeroizing; /// Core client for the Parsec service /// /// The client exposes low-level functionality for using the Parsec service. /// Below you can see code examples for a few of the operations supported. /// /// Providers are abstracted representations of the secure elements that /// Parsec offers abstraction over. Providers are the ones to execute the /// cryptographic operations requested by the user. /// /// For all cryptographic operations an implicit provider is used which can be /// changed between operations. The client starts with the default provider, the first /// one returned by the ListProviders operation. /// /// For crypto operations, if the implicit client provider is `ProviderId::Core`, a client error /// of `InvalidProvider` type is returned. /// See the operation-specific response codes returned by the service in the operation's page /// [here](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/index.html). #[derive(Debug)] pub struct BasicClient { pub(crate) op_client: OperationClient, pub(crate) auth_data: Authentication, pub(crate) implicit_provider: ProviderId, } /// Main client functionality. impl BasicClient { /// Create a new Parsec client. /// /// The client will be initialised with default values obtained from the service for the /// implicit provider and for application identity. /// /// * `app_name` is the application name to be used if direct authentication is the default /// authentication choice /// /// This client will use the default configuration. That includes using a Protobuf converter /// for message bodies and a Unix Domain Socket IPC handler. The default timeout length is 60 /// seconds. /// /// # Example /// ///```no_run ///# use std::error::Error; ///# ///# fn main() -> Result<(), Box> { ///use parsec_client::BasicClient; /// ///let client: BasicClient = BasicClient::new(None)?; ///# Ok(())} ///``` pub fn new(app_name: Option) -> Result { let mut client = BasicClient { op_client: OperationClient::new()?, auth_data: Authentication::None, implicit_provider: ProviderId::Core, }; client.set_default_provider()?; client.set_default_auth(app_name)?; debug!("Parsec BasicClient created with implicit provider \"{}\" and authentication data \"{:?}\"", client.implicit_provider(), client.auth_data()); Ok(client) } /// Create a client that can initially only be used with Core operations not necessitating /// authentication (eg ping). /// /// Setting an authentication method and an implicit provider is needed before calling crypto /// operations. /// /// # Example /// /// ```no_run ///# use std::error::Error; ///# ///# fn main() -> Result<(), Box> { ///use parsec_client::BasicClient; ///let client = BasicClient::new_naked()?; ///let (major, minor) = client.ping()?; ///# Ok(())} /// ``` pub fn new_naked() -> Result { Ok(BasicClient { op_client: OperationClient::new()?, auth_data: Authentication::None, implicit_provider: ProviderId::Core, }) } /// Query the service for the list of authenticators provided and use the first one as default /// /// * `app_name` is to be used if direct authentication is the default choice /// /// # Errors /// /// If no authenticator is reported by the service, a `NoAuthenticator` error kind is returned. /// /// If the default authenticator is `DirectAuthenticator` and `app_name` was set to `None`, /// an error of type `MissingParam` is returned. /// /// If none of the authenticators returned by the service is supported, `NoAuthenticator` is /// returned. /// /// # Example /// /// ```no_run ///# use std::error::Error; ///# ///# fn main() -> Result<(), Box> { ///use parsec_client::BasicClient; ///use parsec_client::core::interface::requests::ProviderId; ///let mut client = BasicClient::new_naked()?; ///// Set the default authenticator but choose a specific provider. ///client.set_implicit_provider(ProviderId::Pkcs11); ///client.set_default_auth(Some("main_client".to_string()))?; ///# Ok(())} /// ``` pub fn set_default_auth(&mut self, app_name: Option) -> Result<()> { let authenticators = self.list_authenticators()?; if authenticators.is_empty() { return Err(Error::Client(ClientErrorKind::NoAuthenticator)); } for authenticator in authenticators { match authenticator.id { AuthType::Direct => { self.auth_data = Authentication::Direct( app_name.ok_or(Error::Client(ClientErrorKind::MissingParam))?, ) } AuthType::UnixPeerCredentials => { self.auth_data = Authentication::UnixPeerCredentials } #[cfg(feature = "spiffe-auth")] AuthType::JwtSvid => self.auth_data = Authentication::JwtSvid, auth => { warn!( "Authenticator of type \"{:?}\" not supported by this client library", auth ); continue; } } return Ok(()); } Err(Error::Client(ClientErrorKind::NoAuthenticator)) } /// Update the authentication data of the client. /// /// This is useful if you want to use a different authentication method than /// the default one. /// /// # Example /// /// See [`set_default_provider`]. pub fn set_auth_data(&mut self, auth_data: Authentication) { self.auth_data = auth_data; } /// Retrieve authentication data of the client. /// /// # Example /// /// ```no_run ///# use std::error::Error; ///# ///# fn main() -> Result<(), Box> { ///use parsec_client::BasicClient; ///use parsec_client::auth::Authentication; ///let mut client = BasicClient::new_naked()?; ///client.set_auth_data(Authentication::UnixPeerCredentials); ///assert_eq!(Authentication::UnixPeerCredentials, client.auth_data()); ///# Ok(())} /// ``` pub fn auth_data(&self) -> Authentication { self.auth_data.clone() } /// Query for the service provider list and set the default one as implicit /// /// # Errors /// /// If no provider is returned by the service, an client error of `NoProvider` /// type is returned. /// /// # Example /// /// ```no_run ///# use std::error::Error; ///# ///# fn main() -> Result<(), Box> { ///use parsec_client::BasicClient; ///use parsec_client::auth::Authentication; ///let mut client = BasicClient::new_naked()?; ///// Use the default provider but use a specific authentication. ///client.set_default_provider()?; ///client.set_auth_data(Authentication::UnixPeerCredentials); ///# Ok(())} /// ``` pub fn set_default_provider(&mut self) -> Result<()> { let providers = self.list_providers()?; if providers.is_empty() { return Err(Error::Client(ClientErrorKind::NoProvider)); } self.implicit_provider = providers[0].id; Ok(()) } /// Set the provider that the client will be implicitly working with. /// /// # Example /// /// See [`set_default_auth`]. pub fn set_implicit_provider(&mut self, provider: ProviderId) { self.implicit_provider = provider; } /// Retrieve client's implicit provider. /// /// # Example /// /// ```no_run ///# use std::error::Error; ///# ///# fn main() -> Result<(), Box> { ///use parsec_client::BasicClient; ///use parsec_client::core::interface::requests::ProviderId; ///let mut client = BasicClient::new_naked()?; ///client.set_implicit_provider(ProviderId::Pkcs11); ///assert_eq!(ProviderId::Pkcs11, client.implicit_provider()); ///# Ok(())} /// ``` pub fn implicit_provider(&self) -> ProviderId { self.implicit_provider } /// **[Core Operation]** List the opcodes supported by the specified provider. /// /// # Example /// ///```no_run ///# use std::error::Error; ///# ///# fn main() -> Result<(), Box> { ///# use std::error::Error; ///# ///# fn main() -> Result<(), Box> { ///use parsec_client::BasicClient; ///use parsec_client::core::interface::requests::{Opcode, ProviderId}; /// ///let client: BasicClient = BasicClient::new(None)?; ///let opcodes = client.list_opcodes(ProviderId::Pkcs11)?; ///if opcodes.contains(&Opcode::PsaGenerateRandom) { /// let random_bytes = client.psa_generate_random(10)?; ///} ///# Ok(())} ///# Ok(())} ///``` pub fn list_opcodes(&self, provider: ProviderId) -> Result> { let res = self.op_client.process_operation( NativeOperation::ListOpcodes(ListOpcodes { provider_id: provider, }), ProviderId::Core, &self.auth_data, )?; if let NativeResult::ListOpcodes(res) = res { Ok(res.opcodes) } else { // Should really not be reached given the checks we do, but it's not impossible if some // changes happen in the interface Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) } } /// **[Core Operation]** List the providers that are supported by the service. /// /// # Example /// ///```no_run ///# use std::error::Error; ///# ///# fn main() -> Result<(), Box> { ///use parsec_client::BasicClient; /// ///let mut client: BasicClient = BasicClient::new_naked()?; ///let providers = client.list_providers()?; ///// Set the second most prioritary provider ///client.set_implicit_provider(providers[1].id); ///# Ok(())} ///``` pub fn list_providers(&self) -> Result> { let res = self.op_client.process_operation( NativeOperation::ListProviders(ListProviders {}), ProviderId::Core, &self.auth_data, )?; if let NativeResult::ListProviders(res) = res { Ok(res.providers) } else { // Should really not be reached given the checks we do, but it's not impossible if some // changes happen in the interface Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) } } /// **[Core Operation]** List the authenticators that are supported by the service. /// /// # Example /// ///```no_run ///# use std::error::Error; ///# ///# fn main() -> Result<(), Box> { ///use parsec_client::BasicClient; /// ///let client: BasicClient = BasicClient::new(None)?; ///let opcodes = client.list_authenticators()?; ///# Ok(())} ///``` pub fn list_authenticators(&self) -> Result> { let res = self.op_client.process_operation( NativeOperation::ListAuthenticators(ListAuthenticators {}), ProviderId::Core, &self.auth_data, )?; if let NativeResult::ListAuthenticators(res) = res { Ok(res.authenticators) } else { // Should really not be reached given the checks we do, but it's not impossible if some // changes happen in the interface Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) } } /// **[Core Operation]** List all keys belonging to the application. /// /// # Example /// ///```no_run ///# use std::error::Error; ///# ///# fn main() -> Result<(), Box> { ///use parsec_client::BasicClient; /// ///let client: BasicClient = BasicClient::new(None)?; ///let keys = client.list_keys()?; ///# Ok(())} ///``` pub fn list_keys(&self) -> Result> { let res = self.op_client.process_operation( NativeOperation::ListKeys(ListKeys {}), ProviderId::Core, &self.auth_data, )?; if let NativeResult::ListKeys(res) = res { Ok(res.keys) } else { // Should really not be reached given the checks we do, but it's not impossible if some // changes happen in the interface Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) } } /// Get the key attributes. /// /// This is a convenience method that uses `list_keys` underneath. /// /// # Errors /// /// Returns `NotFound` if a key with this name does not exist. /// /// # Example /// ///```no_run ///# use std::error::Error; ///# ///# fn main() -> Result<(), Box> { ///use parsec_client::BasicClient; /// ///let client: BasicClient = BasicClient::new(None)?; ///let attributes = client.key_attributes("my_key")?; ///# Ok(())} ///``` pub fn key_attributes(&self, key_name: &str) -> Result { Ok(self .list_keys()? .into_iter() .find(|key_info| key_info.name == key_name) .ok_or(crate::error::Error::Client(ClientErrorKind::NotFound))? .attributes) } /// **[Core Operation, Admin Operation]** Lists all clients currently having /// data in the service. /// /// # Example /// ///```no_run ///# use std::error::Error; ///# ///# fn main() -> Result<(), Box> { ///use parsec_client::BasicClient; /// ///let client: BasicClient = BasicClient::new(None)?; ///let clients = client.list_clients()?; ///# Ok(())} ///``` pub fn list_clients(&self) -> Result> { let res = self.op_client.process_operation( NativeOperation::ListClients(ListClients {}), ProviderId::Core, &self.auth_data, )?; if let NativeResult::ListClients(res) = res { Ok(res.clients) } else { // Should really not be reached given the checks we do, but it's not impossible if some // changes happen in the interface Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) } } /// **[Core Operation, Admin Operation]** Delete all data a client has in the service. /// /// # Example /// ///```no_run ///# use std::error::Error; ///# ///# fn main() -> Result<(), Box> { ///use parsec_client::BasicClient; /// ///let client: BasicClient = BasicClient::new(None)?; ///client.delete_client("main_client")?; ///# Ok(())} ///``` pub fn delete_client(&self, client: &str) -> Result<()> { let res = self.op_client.process_operation( NativeOperation::DeleteClient(DeleteClient { client: client.to_string(), }), ProviderId::Core, &self.auth_data, )?; if let NativeResult::DeleteClient(_) = res { Ok(()) } else { // Should really not be reached given the checks we do, but it's not impossible if some // changes happen in the interface Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) } } /// **[Core Operation]** Send a ping request to the service. /// /// This operation is intended for testing connectivity to the /// service and for retrieving the maximum wire protocol version /// it supports. /// /// # Example /// /// See [`new_naked`]. pub fn ping(&self) -> Result<(u8, u8)> { let res = self.op_client.process_operation( NativeOperation::Ping(Ping {}), ProviderId::Core, &Authentication::None, )?; if let NativeResult::Ping(res) = res { Ok((res.wire_protocol_version_maj, res.wire_protocol_version_min)) } else { // Should really not be reached given the checks we do, but it's not impossible if some // changes happen in the interface Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) } } /// **[Cryptographic Operation]** Generate a key. /// /// Creates a new key with the given name within the namespace of the /// implicit client provider. Any UTF-8 string is considered a valid key name, /// however names must be unique per provider. /// /// Persistence of keys is implemented at provider level, and currently all /// providers persist all the keys users create. /// /// If this method returns an error, no key will have been generated and /// the name used will still be available for another key. /// /// # Example /// ///```no_run ///# use std::error::Error; ///# ///# fn main() -> Result<(), Box> { ///use parsec_client::BasicClient; ///use parsec_client::core::interface::operations::psa_key_attributes::{Attributes, Lifetime, Policy, Type, UsageFlags}; ///use parsec_client::core::interface::operations::psa_algorithm::{AsymmetricSignature, Hash}; /// ///let client: BasicClient = BasicClient::new(None)?; ///let key_attrs = Attributes { /// lifetime: Lifetime::Persistent, /// key_type: Type::RsaKeyPair, /// bits: 2048, /// policy: Policy { /// usage_flags: UsageFlags::default(), /// permitted_algorithms: AsymmetricSignature::RsaPkcs1v15Sign { /// hash_alg: Hash::Sha256.into(), /// }.into(), /// }, ///}; ///client.psa_generate_key("my_key", key_attrs)?; ///# Ok(())} ///``` pub fn psa_generate_key(&self, key_name: &str, key_attributes: Attributes) -> Result<()> { let crypto_provider = self.can_provide_crypto()?; let op = PsaGenerateKey { key_name: String::from(key_name), attributes: key_attributes, }; let _ = self.op_client.process_operation( NativeOperation::PsaGenerateKey(op), crypto_provider, &self.auth_data, )?; Ok(()) } /// **[Cryptographic Operation]** Destroy a key. /// /// Given that keys are namespaced at a provider level, it is /// important to call `psa_destroy_key` on the correct combination of /// implicit client provider and `key_name`. /// /// # Example /// ///```no_run ///# use std::error::Error; ///# ///# fn main() -> Result<(), Box> { ///use parsec_client::BasicClient; /// ///let client: BasicClient = BasicClient::new(None)?; ///client.psa_destroy_key("my_key")?; ///# Ok(())} ///``` pub fn psa_destroy_key(&self, key_name: &str) -> Result<()> { let crypto_provider = self.can_provide_crypto()?; let op = PsaDestroyKey { key_name: String::from(key_name), }; let _ = self.op_client.process_operation( NativeOperation::PsaDestroyKey(op), crypto_provider, &self.auth_data, )?; Ok(()) } /// **[Cryptographic Operation]** Import a key. /// /// Creates a new key with the given name within the namespace of the /// implicit client provider using the user-provided data. Any UTF-8 string is /// considered a valid key name, however names must be unique per provider. /// /// The key material should follow the appropriate binary format expressed /// [here](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_export_public_key.html). /// Several crates (e.g. [`picky-asn1`](https://crates.io/crates/picky-asn1)) /// can greatly help in dealing with binary encodings. /// /// If this method returns an error, no key will have been imported and the /// name used will still be available for another key. /// /// # Example /// ///```no_run ///# use std::error::Error; ///# ///# fn main() -> Result<(), Box> { ///use parsec_client::BasicClient; ///use parsec_client::core::interface::operations::psa_key_attributes::{Attributes, Lifetime, Policy, Type, UsageFlags, EccFamily}; ///use parsec_client::core::interface::operations::psa_algorithm::{AsymmetricSignature, Hash}; /// ///let client: BasicClient = BasicClient::new(None)?; ///let ecc_private_key = vec![ /// 0x26, 0xc8, 0x82, 0x9e, 0x22, 0xe3, 0x0c, 0xa6, 0x3d, 0x29, 0xf5, 0xf7, 0x27, 0x39, 0x58, 0x47, /// 0x41, 0x81, 0xf6, 0x57, 0x4f, 0xdb, 0xcb, 0x4d, 0xbb, 0xdd, 0x52, 0xff, 0x3a, 0xc0, 0xf6, 0x0d, ///]; ///let key_attrs = Attributes { /// lifetime: Lifetime::Persistent, /// key_type: Type::EccKeyPair { /// curve_family: EccFamily::SecpR1, /// }, /// bits: 256, /// policy: Policy { /// usage_flags: UsageFlags::default(), /// permitted_algorithms: AsymmetricSignature::RsaPkcs1v15Sign { /// hash_alg: Hash::Sha256.into(), /// }.into(), /// }, ///}; ///client.psa_import_key("my_key", &ecc_private_key, key_attrs)?; ///# Ok(())} ///``` pub fn psa_import_key( &self, key_name: &str, key_material: &[u8], key_attributes: Attributes, ) -> Result<()> { let key_material = Secret::new(key_material.to_vec()); let crypto_provider = self.can_provide_crypto()?; let op = PsaImportKey { key_name: String::from(key_name), attributes: key_attributes, data: key_material, }; let _ = self.op_client.process_operation( NativeOperation::PsaImportKey(op), crypto_provider, &self.auth_data, )?; Ok(()) } /// **[Cryptographic Operation]** Export a public key or the public part of a key pair. /// /// The returned key material will follow the appropriate binary format expressed /// [here](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_export_public_key.html). /// Several crates (e.g. [`picky-asn1`](https://crates.io/crates/picky-asn1)) /// can greatly help in dealing with binary encodings. /// /// # Example /// ///```no_run ///# use std::error::Error; ///# ///# fn main() -> Result<(), Box> { ///use parsec_client::BasicClient; /// ///let client: BasicClient = BasicClient::new(None)?; ///let public_key_data = client.psa_export_public_key("my_key"); ///# Ok(())} ///``` pub fn psa_export_public_key(&self, key_name: &str) -> Result> { let crypto_provider = self.can_provide_crypto()?; let op = PsaExportPublicKey { key_name: String::from(key_name), }; let res = self.op_client.process_operation( NativeOperation::PsaExportPublicKey(op), crypto_provider, &self.auth_data, )?; if let NativeResult::PsaExportPublicKey(res) = res { Ok(res.data.to_vec()) } else { // Should really not be reached given the checks we do, but it's not impossible if some // changes happen in the interface Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) } } /// **[Cryptographic Operation]** Export a key. /// /// The returned key material will follow the appropriate binary format expressed /// [here](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_export_key.html). /// Several crates (e.g. [`picky-asn1`](https://crates.io/crates/picky-asn1)) /// can greatly help in dealing with binary encodings. /// /// # Example /// ///```no_run ///# use std::error::Error; ///# ///# fn main() -> Result<(), Box> { ///use parsec_client::BasicClient; /// ///let client: BasicClient = BasicClient::new(None)?; ///let key_data = client.psa_export_key("my_key"); ///# Ok(())} ///``` pub fn psa_export_key(&self, key_name: &str) -> Result> { let crypto_provider = self.can_provide_crypto()?; let op = PsaExportKey { key_name: String::from(key_name), }; let res = self.op_client.process_operation( NativeOperation::PsaExportKey(op), crypto_provider, &self.auth_data, )?; if let NativeResult::PsaExportKey(res) = res { Ok(res.data.expose_secret().to_vec()) } else { // Should really not be reached given the checks we do, but it's not impossible if some // changes happen in the interface Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) } } /// **[Cryptographic Operation]** Create an asymmetric signature on a pre-computed message digest. /// /// The key intended for signing **must** have its `sign_hash` flag set /// to `true` in its [key policy](https://docs.rs/parsec-interface/*/parsec_interface/operations/psa_key_attributes/struct.Policy.html). /// /// The signature will be created with the algorithm defined in /// `sign_algorithm`, but only after checking that the key policy /// and type conform with it. /// /// `hash` must be a hash pre-computed over the message of interest /// with the algorithm specified within `sign_algorithm`. /// /// # Example /// ///```no_run ///# use std::error::Error; ///# ///# fn main() -> Result<(), Box> { ///use parsec_client::BasicClient; ///use parsec_client::core::interface::operations::psa_key_attributes::{Attributes, Lifetime, Policy, Type, UsageFlags}; ///use parsec_client::core::interface::operations::psa_algorithm::{AsymmetricSignature, Hash}; /// ///let client: BasicClient = BasicClient::new(None)?; ///// Hash of a message pre-calculated with SHA-256. ///let hash = vec![ /// 0x69, 0x3E, 0xDB, 0x1B, 0x22, 0x79, 0x03, 0xF4, 0xC0, 0xBF, 0xD6, 0x91, 0x76, 0x37, 0x84, 0xA2, /// 0x94, 0x8E, 0x92, 0x50, 0x35, 0xC2, 0x8C, 0x5C, 0x3C, 0xCA, 0xFE, 0x18, 0xE8, 0x81, 0x37, 0x78, ///]; ///let signature = client.psa_sign_hash("my_key", &hash, AsymmetricSignature::RsaPkcs1v15Sign { ///hash_alg: Hash::Sha256.into(), ///})?; ///# Ok(())} ///``` pub fn psa_sign_hash( &self, key_name: &str, hash: &[u8], sign_algorithm: AsymmetricSignature, ) -> Result> { let hash = Zeroizing::new(hash.to_vec()); let crypto_provider = self.can_provide_crypto()?; let op = PsaSignHash { key_name: String::from(key_name), alg: sign_algorithm, hash, }; let res = self.op_client.process_operation( NativeOperation::PsaSignHash(op), crypto_provider, &self.auth_data, )?; if let NativeResult::PsaSignHash(res) = res { Ok(res.signature.to_vec()) } else { // Should really not be reached given the checks we do, but it's not impossible if some // changes happen in the interface Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) } } /// **[Cryptographic Operation]** Verify an existing asymmetric signature over a pre-computed message digest. /// /// The key intended for signing **must** have its `verify_hash` flag set /// to `true` in its [key policy](https://docs.rs/parsec-interface/*/parsec_interface/operations/psa_key_attributes/struct.Policy.html). /// /// The signature will be verifyied with the algorithm defined in /// `sign_algorithm`, but only after checking that the key policy /// and type conform with it. /// /// `hash` must be a hash pre-computed over the message of interest /// with the algorithm specified within `sign_algorithm`. /// /// # Example /// ///```no_run ///# use std::error::Error; ///# ///# fn main() -> Result<(), Box> { ///use parsec_client::BasicClient; ///use parsec_client::core::interface::operations::psa_key_attributes::{Attributes, Lifetime, Policy, Type, UsageFlags}; ///use parsec_client::core::interface::operations::psa_algorithm::{AsymmetricSignature, Hash}; /// ///let client: BasicClient = BasicClient::new(None)?; ///// Hash of a message pre-calculated with SHA-256. ///let hash = vec![ /// 0x69, 0x3E, 0xDB, 0x1B, 0x22, 0x79, 0x03, 0xF4, 0xC0, 0xBF, 0xD6, 0x91, 0x76, 0x37, 0x84, 0xA2, /// 0x94, 0x8E, 0x92, 0x50, 0x35, 0xC2, 0x8C, 0x5C, 0x3C, 0xCA, 0xFE, 0x18, 0xE8, 0x81, 0x37, 0x78, ///]; ///let alg = AsymmetricSignature::RsaPkcs1v15Sign { /// hash_alg: Hash::Sha256.into(), ///}; ///let signature = client.psa_sign_hash("my_key", &hash, alg)?; ///client.psa_verify_hash("my_key", &hash, alg, &signature)?; ///# Ok(())} ///``` pub fn psa_verify_hash( &self, key_name: &str, hash: &[u8], sign_algorithm: AsymmetricSignature, signature: &[u8], ) -> Result<()> { let hash = Zeroizing::new(hash.to_vec()); let signature = Zeroizing::new(signature.to_vec()); let crypto_provider = self.can_provide_crypto()?; let op = PsaVerifyHash { key_name: String::from(key_name), alg: sign_algorithm, hash, signature, }; let _ = self.op_client.process_operation( NativeOperation::PsaVerifyHash(op), crypto_provider, &self.auth_data, )?; Ok(()) } /// **[Cryptographic Operation]** Create an asymmetric signature on a message. /// /// The key intended for signing **must** have its `sign_message` flag set /// to `true` in its [key policy](https://docs.rs/parsec-interface/*/parsec_interface/operations/psa_key_attributes/struct.Policy.html). /// /// The signature will be created with the algorithm defined in /// `sign_algorithm`, but only after checking that the key policy /// and type conform with it. /// /// # Example /// ///```no_run ///# use std::error::Error; ///# ///# fn main() -> Result<(), Box> { ///use parsec_client::BasicClient; ///use parsec_client::core::interface::operations::psa_key_attributes::{Attributes, Lifetime, Policy, Type, UsageFlags}; ///use parsec_client::core::interface::operations::psa_algorithm::{AsymmetricSignature, Hash}; /// ///let client: BasicClient = BasicClient::new(None)?; ///let message = "This is the message to sign which can be of any size!".as_bytes(); ///let signature = client.psa_sign_message( /// "my_key", /// message, /// AsymmetricSignature::RsaPkcs1v15Sign { /// hash_alg: Hash::Sha256.into(), /// } ///)?; ///# Ok(())} ///``` pub fn psa_sign_message( &self, key_name: &str, msg: &[u8], sign_algorithm: AsymmetricSignature, ) -> Result> { let message = Zeroizing::new(msg.to_vec()); let crypto_provider = self.can_provide_crypto()?; let op = PsaSignMessage { key_name: String::from(key_name), alg: sign_algorithm, message, }; let res = self.op_client.process_operation( NativeOperation::PsaSignMessage(op), crypto_provider, &self.auth_data, )?; if let NativeResult::PsaSignMessage(res) = res { Ok(res.signature.to_vec()) } else { // Should really not be reached given the checks we do, but it's not impossible if some // changes happen in the interface Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) } } /// **[Cryptographic Operation]** Verify an existing asymmetric signature over a message. /// /// The key intended for signing **must** have its `verify_message` flag set /// to `true` in its [key policy](https://docs.rs/parsec-interface/*/parsec_interface/operations/psa_key_attributes/struct.Policy.html). /// /// The signature will be verifyied with the algorithm defined in /// `sign_algorithm`, but only after checking that the key policy /// and type conform with it. /// /// # Example /// ///```no_run ///# use std::error::Error; ///# ///# fn main() -> Result<(), Box> { ///use parsec_client::BasicClient; ///use parsec_client::core::interface::operations::psa_key_attributes::{Attributes, Lifetime, Policy, Type, UsageFlags}; ///use parsec_client::core::interface::operations::psa_algorithm::{AsymmetricSignature, Hash}; /// ///let client: BasicClient = BasicClient::new(None)?; ///let message = "This is the message to sign which can be of any size!".as_bytes(); ///let alg = AsymmetricSignature::RsaPkcs1v15Sign { /// hash_alg: Hash::Sha256.into(), ///}; ///let signature = client.psa_sign_message("my_key", message, alg)?; ///client.psa_verify_message("my_key", message, alg, &signature)?; ///# Ok(())} ///``` pub fn psa_verify_message( &self, key_name: &str, msg: &[u8], sign_algorithm: AsymmetricSignature, signature: &[u8], ) -> Result<()> { let message = Zeroizing::new(msg.to_vec()); let signature = Zeroizing::new(signature.to_vec()); let crypto_provider = self.can_provide_crypto()?; let op = PsaVerifyMessage { key_name: String::from(key_name), alg: sign_algorithm, message, signature, }; let _ = self.op_client.process_operation( NativeOperation::PsaVerifyMessage(op), crypto_provider, &self.auth_data, )?; Ok(()) } /// **[Cryptographic Operation]** Encrypt a short message. /// /// The key intended for encrypting **must** have its `encrypt` flag set /// to `true` in its [key policy](https://docs.rs/parsec-interface/*/parsec_interface/operations/psa_key_attributes/struct.Policy.html). /// /// The encryption will be performed with the algorithm defined in `alg`, /// but only after checking that the key policy and type conform with it. /// /// `salt` can be provided if supported by the algorithm. If the algorithm does not support salt, pass /// an empty vector. If the algorithm supports optional salt, pass an empty vector to indicate no /// salt. For RSA PKCS#1 v1.5 encryption, no salt is supported. pub fn psa_asymmetric_encrypt( &self, key_name: &str, encrypt_alg: AsymmetricEncryption, plaintext: &[u8], salt: Option<&[u8]>, ) -> Result> { let salt = salt.map(|salt_ref| salt_ref.to_vec().into()); let crypto_provider = self.can_provide_crypto()?; let op = PsaAsymEncrypt { key_name: String::from(key_name), alg: encrypt_alg, plaintext: plaintext.to_vec().into(), salt, }; let encrypt_res = self.op_client.process_operation( NativeOperation::PsaAsymmetricEncrypt(op), crypto_provider, &self.auth_data, )?; if let NativeResult::PsaAsymmetricEncrypt(res) = encrypt_res { Ok(res.ciphertext.to_vec()) } else { // Should really not be reached given the checks we do, but it's not impossible if some // changes happen in the interface Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) } } /// **[Cryptographic Operation]** Decrypt a short message. /// /// The key intended for decrypting **must** have its `decrypt` flag set /// to `true` in its [key policy](https://docs.rs/parsec-interface/*/parsec_interface/operations/psa_key_attributes/struct.Policy.html). /// /// `salt` can be provided if supported by the algorithm. If the algorithm does not support salt, pass /// an empty vector. If the algorithm supports optional salt, pass an empty vector to indicate no /// salt. For RSA PKCS#1 v1.5 encryption, no salt is supported. /// /// /// The decryption will be performed with the algorithm defined in `alg`, /// but only after checking that the key policy and type conform with it. pub fn psa_asymmetric_decrypt( &self, key_name: &str, encrypt_alg: AsymmetricEncryption, ciphertext: &[u8], salt: Option<&[u8]>, ) -> Result> { let salt = salt.map(|salt| Zeroizing::new(salt.to_vec())); let crypto_provider = self.can_provide_crypto()?; let op = PsaAsymDecrypt { key_name: String::from(key_name), alg: encrypt_alg, ciphertext: Zeroizing::new(ciphertext.to_vec()), salt, }; let decrypt_res = self.op_client.process_operation( NativeOperation::PsaAsymmetricDecrypt(op), crypto_provider, &self.auth_data, )?; if let NativeResult::PsaAsymmetricDecrypt(res) = decrypt_res { Ok(res.plaintext.to_vec()) } else { // Should really not be reached given the checks we do, but it's not impossible if some // changes happen in the interface Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) } } /// **[Cryptographic Operation]** Compute hash of a message. /// /// The hash computation will be performed with the algorithm defined in `alg`. pub fn psa_hash_compute(&self, alg: Hash, input: &[u8]) -> Result> { let crypto_provider = self.can_provide_crypto()?; let op = PsaHashCompute { alg, input: input.to_vec().into(), }; let hash_compute_res = self.op_client.process_operation( NativeOperation::PsaHashCompute(op), crypto_provider, &self.auth_data, )?; if let NativeResult::PsaHashCompute(res) = hash_compute_res { Ok(res.hash.to_vec()) } else { // Should really not be reached given the checks we do, but it's not impossible if some // changes happen in the interface Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) } } /// **[Cryptographic Operation]** Compute hash of a message and compare it with a reference value. /// /// The hash computation will be performed with the algorithm defined in `alg`. /// /// If this operation returns no error, the hash was computed successfully and it matches the reference value. pub fn psa_hash_compare(&self, alg: Hash, input: &[u8], hash: &[u8]) -> Result<()> { let crypto_provider = self.can_provide_crypto()?; let op = PsaHashCompare { alg, input: input.to_vec().into(), hash: hash.to_vec().into(), }; let _ = self.op_client.process_operation( NativeOperation::PsaHashCompare(op), crypto_provider, &self.auth_data, )?; Ok(()) } /// **[Cryptographic Operation]** Authenticate and encrypt a short message. /// /// The key intended for decrypting **must** have its `encrypt` flag set /// to `true` in its [key policy](https://docs.rs/parsec-interface/*/parsec_interface/operations/psa_key_attributes/struct.Policy.html). /// /// The encryption will be performed with the algorithm defined in `alg`, /// but only after checking that the key policy and type conform with it. /// /// `nonce` must be appropriate for the selected `alg`. /// /// For algorithms where the encrypted data and the authentication tag are defined as separate outputs, /// the returned buffer will contain the encrypted data followed by the authentication data. pub fn psa_aead_encrypt( &self, key_name: &str, encrypt_alg: Aead, nonce: &[u8], additional_data: &[u8], plaintext: &[u8], ) -> Result> { let crypto_provider = self.can_provide_crypto()?; let op = PsaAeadEncrypt { key_name: String::from(key_name), alg: encrypt_alg, nonce: nonce.to_vec().into(), additional_data: additional_data.to_vec().into(), plaintext: plaintext.to_vec().into(), }; let encrypt_res = self.op_client.process_operation( NativeOperation::PsaAeadEncrypt(op), crypto_provider, &self.auth_data, )?; if let NativeResult::PsaAeadEncrypt(res) = encrypt_res { Ok(res.ciphertext.to_vec()) } else { // Should really not be reached given the checks we do, but it's not impossible if some // changes happen in the interface Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) } } /// **[Cryptographic Operation]** Decrypt and authenticate a short message. /// /// The key intended for decrypting **must** have its `decrypt` flag set /// to `true` in its [key policy](https://docs.rs/parsec-interface/*/parsec_interface/operations/psa_key_attributes/struct.Policy.html). /// /// The decryption will be performed with the algorithm defined in `alg`, /// but only after checking that the key policy and type conform with it. /// /// `nonce` must be appropriate for the selected `alg`. /// /// For algorithms where the encrypted data and the authentication tag are defined as separate inputs, /// `ciphertext` must contain the encrypted data followed by the authentication data. pub fn psa_aead_decrypt( &self, key_name: &str, encrypt_alg: Aead, nonce: &[u8], additional_data: &[u8], ciphertext: &[u8], ) -> Result> { let crypto_provider = self.can_provide_crypto()?; let op = PsaAeadDecrypt { key_name: String::from(key_name), alg: encrypt_alg, nonce: nonce.to_vec().into(), additional_data: additional_data.to_vec().into(), ciphertext: ciphertext.to_vec().into(), }; let decrypt_res = self.op_client.process_operation( NativeOperation::PsaAeadDecrypt(op), crypto_provider, &self.auth_data, )?; if let NativeResult::PsaAeadDecrypt(res) = decrypt_res { Ok(res.plaintext.to_vec()) } else { // Should really not be reached given the checks we do, but it's not impossible if some // changes happen in the interface Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) } } /// **[Cryptographic Operation]** Encrypt a short message with a symmetric cipher. /// /// The key intended for encrypting **must** have its `encrypt` flag set /// to `true` in its [key policy](https://docs.rs/parsec-interface/*/parsec_interface/operations/psa_key_attributes/struct.Policy.html). /// /// This function will encrypt a short message with a random initialisation vector (IV). pub fn psa_cipher_encrypt( &self, key_name: String, alg: Cipher, plaintext: &[u8], ) -> Result> { let crypto_provider = self.can_provide_crypto()?; let op = PsaCipherEncrypt { key_name, alg, plaintext: plaintext.to_vec().into(), }; let res = self.op_client.process_operation( NativeOperation::PsaCipherEncrypt(op), crypto_provider, &self.auth_data, )?; if let NativeResult::PsaCipherEncrypt(res) = res { Ok(res.ciphertext.to_vec()) } else { // Should really not be reached given the checks we do, but it's not impossible if some // changes happen in the interface Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) } } /// **[Cryptographic Operation]** Decrypt a short message with a symmetric cipher. /// /// The key intended for decrypting **must** have its `decrypt` flag set /// to `true` in its [key policy](https://docs.rs/parsec-interface/*/parsec_interface/operations/psa_key_attributes/struct.Policy.html). /// /// `ciphertext` must be the IV followed by the ciphertext. /// /// This function will decrypt a short message using the provided initialisation vector (IV). pub fn psa_cipher_decrypt( &self, key_name: String, alg: Cipher, ciphertext: &[u8], ) -> Result> { let crypto_provider = self.can_provide_crypto()?; let op = PsaCipherDecrypt { key_name, alg, ciphertext: ciphertext.to_vec().into(), }; let res = self.op_client.process_operation( NativeOperation::PsaCipherDecrypt(op), crypto_provider, &self.auth_data, )?; if let NativeResult::PsaCipherDecrypt(res) = res { Ok(res.plaintext.to_vec()) } else { // Should really not be reached given the checks we do, but it's not impossible if some // changes happen in the interface Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) } } /// **[Cryptographic Operation]** Perform a raw key agreement. /// /// The provided private key **must** have its `derive` flag set /// to `true` in its [key policy](https://docs.rs/parsec-interface/*/parsec_interface/operations/psa_key_attributes/struct.Policy.html). /// /// The raw_key_agreement will be performed with the algorithm defined in `alg`, /// but only after checking that the key policy and type conform with it. /// /// `peer_key` must be the peer public key to use in the raw key derivation. It must /// be in a format supported by [`PsaImportKey`](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_import_key.html). pub fn psa_raw_key_agreement( &self, alg: RawKeyAgreement, private_key_name: &str, peer_key: &[u8], ) -> Result> { let op = PsaRawKeyAgreement { alg, private_key_name: String::from(private_key_name), peer_key: Zeroizing::new(peer_key.to_vec()), }; let crypto_provider = self.can_provide_crypto()?; let raw_key_agreement_res = self.op_client.process_operation( NativeOperation::PsaRawKeyAgreement(op), crypto_provider, &self.auth_data, )?; if let NativeResult::PsaRawKeyAgreement(res) = raw_key_agreement_res { Ok(res.shared_secret.expose_secret().to_vec()) } else { // Should really not be reached given the checks we do, but it's not impossible if some // changes happen in the interface Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) } } /// **[Cryptographic Operation]** Generate some random bytes. /// /// Generates a sequence of random bytes and returns them to the user. /// /// If this method returns an error, no bytes will have been generated. /// /// # Example /// /// See [`list_opcodes`]. pub fn psa_generate_random(&self, nbytes: usize) -> Result> { let crypto_provider = self.can_provide_crypto()?; let op = PsaGenerateRandom { size: nbytes }; let res = self.op_client.process_operation( NativeOperation::PsaGenerateRandom(op), crypto_provider, &self.auth_data, )?; if let NativeResult::PsaGenerateRandom(res) = res { Ok(res.random_bytes.to_vec()) } else { // Should really not be reached given the checks we do, but it's not impossible if some // changes happen in the interface Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) } } /// **[Capability Discovery Operation]** Check if attributes are supported. /// /// Checks if the given attributes are supported for the given type of operation. /// /// #Errors /// /// This operation will either return Ok(()) or Err(PsaErrorNotSupported) indicating whether the attributes are supported. /// /// See the operation-specific response codes returned by the service /// [here](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/can_do_crypto.html#specific-response-status-codes). pub fn can_do_crypto(&self, check_type: CheckType, attributes: Attributes) -> Result<()> { let crypto_provider = self.can_provide_crypto()?; let op = CanDoCrypto { check_type, attributes, }; let _ = self.op_client.process_operation( NativeOperation::CanDoCrypto(op), crypto_provider, &self.auth_data, )?; Ok(()) } /// **[Cryptographic Operation]** Get data required to prepare an /// ActivateCredential key attestation. /// /// Retrieve the binary blobs required by a third party to perform a /// MakeCredential operation, in preparation for a key attestation using /// ActivateCredential. /// /// **This key attestation method is TPM-specific** pub fn prepare_activate_credential( &self, attested_key_name: String, attesting_key_name: Option, ) -> Result { self.can_use_provider(ProviderId::Tpm)?; let op = PrepareKeyAttestation::ActivateCredential { attested_key_name, attesting_key_name, }; let res = self.op_client.process_operation( NativeOperation::PrepareKeyAttestation(op), ProviderId::Tpm, &self.auth_data, )?; if let NativeResult::PrepareKeyAttestation( PrepareKeyAttestationResult::ActivateCredential { name, public, attesting_key_pub, }, ) = res { Ok(PrepareActivateCredential { name: name.to_vec(), public: public.to_vec(), attesting_key_pub: attesting_key_pub.to_vec(), }) } else { // Should really not be reached given the checks we do, but it's not impossible if some // changes happen in the interface Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) } } /// **[Cryptographic Operation]** Perform a key attestation operation via /// ActivateCredential /// /// **This key attestation method is TPM-specific** /// /// You can see more details on the inner-workings, and on the requirements /// for this operation [here](https://parallaxsecond.github.io/parsec-book/parsec_client/operations/attest_key.html). /// /// Before performing an ActivateCredential attestation you must compute /// the `credential_blob` and `secret` parameters using the outputs from /// the `prepare_activate_credential` method. pub fn activate_credential_attestation( &self, attested_key_name: String, attesting_key_name: Option, credential_blob: Vec, secret: Vec, ) -> Result> { self.can_use_provider(ProviderId::Tpm)?; let op = AttestKey::ActivateCredential { attested_key_name, attesting_key_name, credential_blob: credential_blob.into(), secret: secret.into(), }; let res = self.op_client.process_operation( NativeOperation::AttestKey(op), ProviderId::Tpm, &self.auth_data, )?; if let NativeResult::AttestKey(AttestKeyResult::ActivateCredential { credential }) = res { Ok(credential.to_vec()) } else { // Should really not be reached given the checks we do, but it's not impossible if some // changes happen in the interface Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)) } } fn can_provide_crypto(&self) -> Result { match self.implicit_provider { ProviderId::Core => Err(Error::Client(ClientErrorKind::InvalidProvider)), crypto_provider => Ok(crypto_provider), } } fn can_use_provider(&self, provider: ProviderId) -> Result<()> { let providers = self.list_providers()?; if providers.iter().any(|prov| prov.id == provider) { Ok(()) } else { Err(Error::Client(ClientErrorKind::NoProvider)) } } } impl Default for BasicClient { fn default() -> Self { BasicClient { op_client: Default::default(), auth_data: Authentication::None, implicit_provider: ProviderId::Core, } } } /// Wrapper for the data needed to prepare for an /// ActivateCredential attestation. #[derive(Debug)] pub struct PrepareActivateCredential { /// TPM name of key to be attested pub name: Vec, /// Bytes representing the serialized version of the key public parameters pub public: Vec, /// The public part of the attesting key pub attesting_key_pub: Vec, } parsec-client-0.16.0/src/core/ipc_handler/mod.rs000064400000000000000000000027401046102023000175600ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Types implementing an abstraction over IPC channels use crate::error::{ClientErrorKind, Error, Result}; use std::io::{Read, Write}; use std::time::Duration; use url::Url; pub mod unix_socket; /// Default timeout for client IPC requests. pub const DEFAULT_TIMEOUT: Duration = Duration::from_secs(60); /// This trait is created to allow the iterator returned by incoming to iterate over a trait object /// that implements both Read and Write. pub trait ReadWrite: Read + Write {} // Automatically implements ReadWrite for all types that implement Read and Write. impl ReadWrite for T {} /// Trait that must be implemented by any IPC client /// /// The trait is used by the request handler for obtaining a stream to the service. pub trait Connect { /// Connect to underlying IPC and return a readable and writeable stream fn connect(&self) -> Result>; /// Set timeout for all produced streams. fn set_timeout(&mut self, timeout: Option); } /// Create an implementation of `Connect` from the socket URL pub fn connector_from_url(socket_url: Url) -> Result> { match socket_url.scheme() { "unix" => Ok(Box::from(unix_socket::Handler::new( socket_url.path().into(), Some(DEFAULT_TIMEOUT), )?)), _ => Err(Error::Client(ClientErrorKind::InvalidSocketUrl)), } } parsec-client-0.16.0/src/core/ipc_handler/unix_socket.rs000064400000000000000000000035751046102023000213430ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Handler for Unix domain sockets use super::{Connect, ReadWrite}; use crate::error::{ClientErrorKind, Error, Result}; use std::os::unix::fs::FileTypeExt; use std::os::unix::net::UnixStream; use std::path::PathBuf; use std::time::Duration; /// Default socket path used by the service. pub const DEFAULT_SOCKET_PATH: &str = "/run/parsec/parsec.sock"; /// IPC handler for Unix domain sockets #[derive(Debug, Clone)] pub struct Handler { /// Path at which the socket can be found path: PathBuf, /// Timeout for reads and writes on the streams timeout: Option, } impl Connect for Handler { fn connect(&self) -> Result> { let stream = UnixStream::connect(self.path.clone()).map_err(ClientErrorKind::Ipc)?; stream .set_read_timeout(self.timeout) .map_err(ClientErrorKind::Ipc)?; stream .set_write_timeout(self.timeout) .map_err(ClientErrorKind::Ipc)?; Ok(Box::from(stream)) } fn set_timeout(&mut self, timeout: Option) { self.timeout = timeout; } } impl Handler { /// Create new client using given socket path and timeout duration pub fn new(path: PathBuf, timeout: Option) -> Result { if path.exists() && std::fs::metadata(&path) .map_err(|_| Error::Client(ClientErrorKind::InvalidSocketAddress))? .file_type() .is_socket() { Ok(Handler { path, timeout }) } else { Err(Error::Client(ClientErrorKind::InvalidSocketAddress)) } } } impl Default for Handler { fn default() -> Self { Handler { path: DEFAULT_SOCKET_PATH.into(), timeout: Some(super::DEFAULT_TIMEOUT), } } } parsec-client-0.16.0/src/core/mod.rs000064400000000000000000000014441046102023000153100ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Core helpers for integration with the Parsec service //! //! The `interface` module is a version of the [`parsec-interface`](https://crates.io/crates/parsec-interface) //! crate and is meant to be used in conjuction with the [basic](basic_client/struct.BasicClient.html), //! [operation](operation_client/struct.OperationClient.html) and [request](request_client/struct.RequestClient.html) //! structures. pub mod basic_client; pub mod ipc_handler; pub mod operation_client; pub mod request_client; mod testing; /// Resurfacing of the Secrecy library used by the client. pub use interface::secrecy; /// Resurfacing of the Parsec interface library used by the client. pub use parsec_interface as interface; parsec-client-0.16.0/src/core/operation_client.rs000064400000000000000000000120111046102023000200570ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Operation-level client #![allow(dead_code)] use super::request_client::RequestClient; use crate::auth::Authentication; use crate::error::{ClientErrorKind, Error, Result}; use derivative::Derivative; use parsec_interface::operations::{Convert, NativeOperation, NativeResult}; use parsec_interface::operations_protobuf::ProtobufConverter; use parsec_interface::requests::{ request::RequestHeader, Opcode, ProviderId, Request, Response, ResponseStatus, }; use std::convert::TryInto; /// Low-level client optimised for communicating with the Parsec service at an operation level. /// /// Usage is recommended when fine control over how operations are wrapped and processed is needed. #[derive(Derivative)] #[derivative(Debug)] pub struct OperationClient { /// Converter that manages request body conversions /// /// Defaults to a Protobuf converter #[derivative(Debug = "ignore")] pub content_converter: Box, /// Converter that manages response body conversions /// /// Defaults to a Protobuf converter #[derivative(Debug = "ignore")] pub accept_converter: Box, /// Client for request and response objects pub request_client: RequestClient, } #[allow(clippy::new_without_default)] impl OperationClient { /// Creates an OperationClient instance. The request client uses a timeout of 5 /// seconds on reads and writes on the socket. It uses the version 1.0 wire protocol /// to form requests, the direct authentication method and protobuf format as /// content type. pub fn new() -> Result { Ok(OperationClient { request_client: RequestClient::new()?, ..Default::default() }) } fn operation_to_request( &self, operation: NativeOperation, provider: ProviderId, auth: &Authentication, ) -> Result { let opcode = operation.opcode(); let body = self .content_converter .operation_to_body(operation) .map_err(ClientErrorKind::Interface)?; let header = RequestHeader { provider, session: 0, // no provisioning of sessions yet content_type: self.content_converter.body_type(), accept_type: self.accept_converter.body_type(), auth_type: auth.auth_type(), opcode, }; Ok(Request { header, body, auth: auth.try_into()?, }) } fn response_to_result( &self, response: Response, expected_opcode: Opcode, ) -> Result { let status = response.header.status; if status != ResponseStatus::Success { return Err(Error::Service(status)); } let opcode = response.header.opcode; if opcode != expected_opcode { return Err(Error::Client(ClientErrorKind::InvalidServiceResponseType)); } Ok(self .accept_converter .body_to_result(response.body, opcode) .map_err(ClientErrorKind::Interface)?) } /// Send an operation to a specific provider and get a result. /// /// # Errors /// /// If the conversions between operation to request or between response to result fail, returns /// a serializing or deserializing error. Returns an error if the operation itself failed. If the /// opcode is different between request and response, `InvalidServiceResponseType` is returned. pub fn process_operation( &self, operation: NativeOperation, provider: ProviderId, auth: &Authentication, ) -> Result { let req_opcode = operation.opcode(); let request = self.operation_to_request(operation, provider, auth)?; let response = self.request_client.process_request(request)?; self.response_to_result(response, req_opcode) } } impl Default for OperationClient { fn default() -> Self { OperationClient { content_converter: Box::from(ProtobufConverter {}), accept_converter: Box::from(ProtobufConverter {}), request_client: Default::default(), } } } /// Configuration methods for controlling communication with the service. impl crate::BasicClient { /// Set the converter used for request bodies handled by this client. /// /// By default Protobuf will be used for this. pub fn set_request_body_converter( &mut self, content_converter: Box, ) { self.op_client.content_converter = content_converter; } /// Set the converter used for response bodies handled by this client. /// /// By default Protobuf will be used for this. pub fn set_response_body_converter( &mut self, accept_converter: Box, ) { self.op_client.accept_converter = accept_converter; } } parsec-client-0.16.0/src/core/request_client.rs000064400000000000000000000061261046102023000175610ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Request-level client use super::ipc_handler::{self, unix_socket, Connect}; use crate::error::{ClientErrorKind, Result}; use derivative::Derivative; use parsec_interface::requests::{Request, Response}; use std::env; use std::time::Duration; const DEFAULT_MAX_BODY_SIZE: usize = usize::max_value(); /// Low level client structure optimised for communicating with the service /// at a request level of abstraction. /// /// Usage is recommended when fine control over the request header and IPC handler /// is needed. #[derive(Derivative)] #[derivative(Debug)] pub struct RequestClient { /// Max size for response bodies /// /// Defaults to the max value of `usize` on the current platform pub max_body_size: usize, /// Handler for IPC-related functionality /// /// Defaults to using Unix domain sockets #[derivative(Debug = "ignore")] pub ipc_handler: Box, } impl RequestClient { /// Creates a new `RequestClient`, bootstrapping the socket /// location. pub fn new() -> Result { Ok(RequestClient { ipc_handler: ipc_handler::connector_from_url(url::Url::parse( &env::var("PARSEC_SERVICE_ENDPOINT") .unwrap_or(format!("unix:{}", unix_socket::DEFAULT_SOCKET_PATH)), )?)?, max_body_size: DEFAULT_MAX_BODY_SIZE, }) } /// Send a request and get a response. pub fn process_request(&self, request: Request) -> Result { // Try to connect once, wait for a timeout until trying again. let mut stream = self.ipc_handler.connect()?; request .write_to_stream(&mut stream) .map_err(ClientErrorKind::Interface)?; Ok(Response::read_from_stream(&mut stream, self.max_body_size) .map_err(ClientErrorKind::Interface)?) } } impl Default for RequestClient { fn default() -> Self { RequestClient { max_body_size: DEFAULT_MAX_BODY_SIZE, ipc_handler: Box::from(unix_socket::Handler::default()), } } } /// Configuration methods for controlling IPC-level options. impl crate::BasicClient { /// Set the maximum body size allowed for requests. /// /// Defaults to the maximum value of `usize`. pub fn set_max_body_size(&mut self, max_body_size: usize) { self.op_client.request_client.max_body_size = max_body_size; } /// Set the IPC handler used for communication with the service. /// /// By default the [Unix domain socket client](../ipc_handler/unix_socket/struct.Client.html) is used. pub fn set_ipc_handler(&mut self, ipc_handler: Box) { self.op_client.request_client.ipc_handler = ipc_handler; } /// Set the timeout for operations on the IPC stream. /// /// The value defaults to 1 second. pub fn set_timeout(&mut self, timeout: Option) { self.op_client .request_client .ipc_handler .set_timeout(timeout); } } parsec-client-0.16.0/src/core/testing/core_tests.rs000064400000000000000000000754601046102023000203710ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::{FailingMockIpc, TestBasicClient, DEFAULT_APP_NAME}; use crate::auth::Authentication; use crate::error::{ClientErrorKind, Error}; use mockstream::{FailingMockStream, MockStream}; use parsec_interface::operations; use parsec_interface::operations::list_authenticators::AuthenticatorInfo; use parsec_interface::operations::list_keys::KeyInfo; use parsec_interface::operations::list_providers::{ProviderInfo, Uuid}; use parsec_interface::operations::psa_algorithm::*; use parsec_interface::operations::psa_key_attributes::*; use parsec_interface::operations::Convert; use parsec_interface::operations::{NativeOperation, NativeResult}; use parsec_interface::operations_protobuf::ProtobufConverter; use parsec_interface::requests::ProviderId; use parsec_interface::requests::Response; use parsec_interface::requests::ResponseStatus; use parsec_interface::requests::{request::RequestHeader, Request}; use parsec_interface::requests::{AuthType, BodyType, Opcode}; use parsec_interface::secrecy::{ExposeSecret, Secret}; use std::collections::HashSet; use std::io::ErrorKind; use zeroize::Zeroizing; const PROTOBUF_CONVERTER: ProtobufConverter = ProtobufConverter {}; const REQ_HEADER: RequestHeader = RequestHeader { provider: ProviderId::Core, session: 0, content_type: BodyType::Protobuf, accept_type: BodyType::Protobuf, auth_type: AuthType::NoAuth, opcode: Opcode::Ping, }; fn get_response_bytes_from_result(result: NativeResult) -> Vec { let mut stream = MockStream::new(); let mut req_hdr = REQ_HEADER; req_hdr.opcode = result.opcode(); let mut resp = Response::from_request_header(req_hdr, ResponseStatus::Success); resp.body = PROTOBUF_CONVERTER.result_to_body(result).unwrap(); resp.write_to_stream(&mut stream).unwrap(); stream.pop_bytes_written() } fn get_req_from_bytes(bytes: Vec) -> Request { let mut stream = MockStream::new(); stream.push_bytes_to_read(&bytes); Request::read_from_stream(&mut stream, usize::max_value()).unwrap() } fn get_operation_from_req_bytes(bytes: Vec) -> NativeOperation { let req = get_req_from_bytes(bytes); PROTOBUF_CONVERTER .body_to_operation(req.body, req.header.opcode) .unwrap() } #[test] fn list_provider_test() { let mut client: TestBasicClient = Default::default(); let provider_info = vec![ProviderInfo { uuid: Uuid::nil(), description: String::from("Some empty provider"), vendor: String::from("Arm Ltd."), version_maj: 1, version_min: 0, version_rev: 0, id: ProviderId::Core, }]; client.set_mock_read(&get_response_bytes_from_result( NativeResult::ListProviders(operations::list_providers::Result { providers: provider_info, }), )); let providers = client.list_providers().expect("Failed to list providers"); // Check request: // ListProviders request is empty so no checking to be done // Check response: assert_eq!(providers.len(), 1); assert_eq!(providers[0].uuid, Uuid::nil()); } #[test] fn list_opcodes_test() { let mut client: TestBasicClient = Default::default(); let mut opcodes = HashSet::new(); let _ = opcodes.insert(Opcode::PsaDestroyKey); let _ = opcodes.insert(Opcode::PsaGenerateKey); client.set_mock_read(&get_response_bytes_from_result(NativeResult::ListOpcodes( operations::list_opcodes::Result { opcodes }, ))); let opcodes = client .list_opcodes(ProviderId::MbedCrypto) .expect("Failed to retrieve opcodes"); // Check request: // ListOpcodes request is empty so no checking to be done // Check response: assert_eq!(opcodes.len(), 2); assert!(opcodes.contains(&Opcode::PsaGenerateKey) && opcodes.contains(&Opcode::PsaDestroyKey)); } #[test] fn list_clients_test() { let mut client: TestBasicClient = Default::default(); let clients = vec!["toto".to_string(), "tata".to_string()]; client.set_mock_read(&get_response_bytes_from_result(NativeResult::ListClients( operations::list_clients::Result { clients }, ))); let clients = client.list_clients().expect("Failed to retrieve opcodes"); // Check response: assert_eq!(clients.len(), 2); assert!(clients.contains(&"toto".to_string()) && clients.contains(&"tata".to_string())); } #[test] fn delete_client_test() { let mut client: TestBasicClient = Default::default(); client.set_mock_read(&get_response_bytes_from_result(NativeResult::DeleteClient( operations::delete_client::Result {}, ))); let client_name = String::from("toto"); client .delete_client(&client_name) .expect("Failed to call destroy key"); // Check request: let op = get_operation_from_req_bytes(client.get_mock_write()); if let NativeOperation::DeleteClient(op) = op { assert_eq!(op.client, client_name); } else { panic!("Got wrong operation type: {:?}", op); } } #[test] fn list_keys_test() { use parsec_interface::operations::psa_key_attributes::{ Attributes, Lifetime, Policy, Type, UsageFlags, }; let mut client: TestBasicClient = Default::default(); let mut usage_flags = UsageFlags::default(); let _ = usage_flags .set_decrypt() .set_export() .set_copy() .set_cache() .set_encrypt() .set_decrypt() .set_sign_message() .set_verify_message() .set_sign_hash() .set_verify_hash() .set_derive(); let key_info = vec![KeyInfo { provider_id: ProviderId::MbedCrypto, name: String::from("Foo"), attributes: Attributes { lifetime: Lifetime::Persistent, key_type: Type::RsaKeyPair, bits: 1024, policy: Policy { usage_flags, permitted_algorithms: Algorithm::AsymmetricSignature( AsymmetricSignature::RsaPkcs1v15Sign { hash_alg: Hash::Sha256.into(), }, ), }, }, }]; client.set_mock_read(&get_response_bytes_from_result(NativeResult::ListKeys( operations::list_keys::Result { keys: key_info }, ))); let keys = client.list_keys().expect("Failed to list keys"); // Check request: // ListKeys request is empty so no checking to be done // Check response: assert_eq!(keys.len(), 1); assert_eq!(keys[0].name, "Foo"); assert_eq!(keys[0].provider_id, ProviderId::MbedCrypto); } #[test] fn core_provider_for_crypto_test() { let mut client: TestBasicClient = Default::default(); client.set_implicit_provider(ProviderId::Core); let res = client .psa_destroy_key("random key") .expect_err("Expected a failure!!"); assert!(matches!( res, Error::Client(ClientErrorKind::InvalidProvider) )); } #[test] fn psa_generate_key_test() { let mut client: TestBasicClient = Default::default(); client.set_mock_read(&get_response_bytes_from_result( NativeResult::PsaGenerateKey(operations::psa_generate_key::Result {}), )); let key_name = String::from("key-name"); let mut usage_flags = UsageFlags::default(); let _ = usage_flags .set_decrypt() .set_export() .set_copy() .set_cache() .set_decrypt(); let key_attrs = Attributes { lifetime: Lifetime::Persistent, key_type: Type::Aes, bits: 192, policy: Policy { usage_flags, permitted_algorithms: Algorithm::Cipher(Cipher::Ctr), }, }; client .psa_generate_key(&key_name, key_attrs) .expect("failed to generate key"); // Check request: let op = get_operation_from_req_bytes(client.get_mock_write()); if let NativeOperation::PsaGenerateKey(op) = op { assert_eq!(op.attributes, key_attrs); assert_eq!(op.key_name, key_name); } else { panic!("Got wrong operation type: {:?}", op); } // Check response: // GenerateKey response is empty so no checking to be done } #[test] fn psa_destroy_key_test() { let mut client: TestBasicClient = Default::default(); client.set_mock_read(&get_response_bytes_from_result( NativeResult::PsaDestroyKey(operations::psa_destroy_key::Result {}), )); let key_name = String::from("key-name"); client .psa_destroy_key(&key_name) .expect("Failed to call destroy key"); // Check request: let op = get_operation_from_req_bytes(client.get_mock_write()); if let NativeOperation::PsaDestroyKey(op) = op { assert_eq!(op.key_name, key_name); } else { panic!("Got wrong operation type: {:?}", op); } // Check response: // DestroyKey response is empty so no checking to be done } #[test] fn psa_import_key_test() { let mut client: TestBasicClient = Default::default(); client.set_mock_read(&get_response_bytes_from_result(NativeResult::PsaImportKey( operations::psa_import_key::Result {}, ))); let key_name = String::from("key-name"); let mut usage_flags = UsageFlags::default(); let _ = usage_flags .set_decrypt() .set_export() .set_copy() .set_cache() .set_decrypt(); let key_attrs = Attributes { lifetime: Lifetime::Persistent, key_type: Type::Aes, bits: 192, policy: Policy { usage_flags, permitted_algorithms: Algorithm::Cipher(Cipher::Ctr), }, }; let key_data = vec![0xff_u8; 128]; client .psa_import_key(&key_name, &key_data, key_attrs) .unwrap(); // Check request: let op = get_operation_from_req_bytes(client.get_mock_write()); if let NativeOperation::PsaImportKey(op) = op { assert_eq!(op.attributes, key_attrs); assert_eq!(op.key_name, key_name); assert_eq!(op.data.expose_secret(), &key_data); } else { panic!("Got wrong operation type: {:?}", op); } // Check response: // ImportKey response is empty so no checking to be done } #[test] fn psa_export_public_key_test() { let mut client: TestBasicClient = Default::default(); let key_data = vec![0xa5; 128]; client.set_mock_read(&get_response_bytes_from_result( NativeResult::PsaExportPublicKey(operations::psa_export_public_key::Result { data: key_data.clone().into(), }), )); let key_name = String::from("key-name"); // Check response: assert_eq!( client .psa_export_public_key(&key_name) .expect("Failed to export public key"), key_data ); // Check request: let op = get_operation_from_req_bytes(client.get_mock_write()); if let NativeOperation::PsaExportPublicKey(op) = op { assert_eq!(op.key_name, key_name); } else { panic!("Got wrong operation type: {:?}", op); } } #[test] fn psa_export_key_test() { let mut client: TestBasicClient = Default::default(); let key_data = vec![0xa5; 128]; client.set_mock_read(&get_response_bytes_from_result(NativeResult::PsaExportKey( operations::psa_export_key::Result { data: Secret::new(key_data.clone()), }, ))); let key_name = String::from("key-name"); // Check response: assert_eq!( client .psa_export_key(&key_name) .expect("Failed to export key"), key_data ); // Check request: let op = get_operation_from_req_bytes(client.get_mock_write()); if let NativeOperation::PsaExportKey(op) = op { assert_eq!(op.key_name, key_name); } else { panic!("Got wrong operation type: {:?}", op); } } #[test] fn psa_sign_hash_test() { let mut client: TestBasicClient = Default::default(); let hash = vec![0x77_u8; 32]; let key_name = String::from("key_name"); let sign_algorithm = AsymmetricSignature::Ecdsa { hash_alg: Hash::Sha256.into(), }; let signature = vec![0x33_u8; 128]; client.set_mock_read(&get_response_bytes_from_result(NativeResult::PsaSignHash( operations::psa_sign_hash::Result { signature: signature.clone().into(), }, ))); // Check response: assert_eq!( client .psa_sign_hash(&key_name, &hash, sign_algorithm) .expect("Failed to sign hash"), signature ); // Check request: let op = get_operation_from_req_bytes(client.get_mock_write()); if let NativeOperation::PsaSignHash(op) = op { assert_eq!(op.key_name, key_name); assert_eq!(op.hash.to_vec(), hash); assert_eq!(op.alg, sign_algorithm); } else { panic!("Got wrong operation type: {:?}", op); } } #[test] fn psa_generate_random_test() { let mut client: TestBasicClient = Default::default(); let mock_result = vec![0xDE, 0xAD, 0xBE, 0xEF]; client.set_mock_read(&get_response_bytes_from_result( NativeResult::PsaGenerateRandom(operations::psa_generate_random::Result { random_bytes: Zeroizing::from(mock_result.clone()), }), )); // Check response: assert_eq!( client .psa_generate_random(4) .expect("failed to generate random"), mock_result ); // Check request: let op = get_operation_from_req_bytes(client.get_mock_write()); if let NativeOperation::PsaGenerateRandom(op) = op { assert_eq!(op.size, 4); } else { panic!("Got wrong operation type: {:?}", op); } } #[test] fn verify_hash_test() { let mut client: TestBasicClient = Default::default(); let hash = vec![0x77_u8; 32]; let key_name = String::from("key_name"); let sign_algorithm = AsymmetricSignature::Ecdsa { hash_alg: Hash::Sha256.into(), }; let signature = vec![0x33_u8; 128]; client.set_mock_read(&get_response_bytes_from_result( NativeResult::PsaVerifyHash(operations::psa_verify_hash::Result {}), )); client .psa_verify_hash(&key_name, &hash, sign_algorithm, &signature) .expect("Failed to sign hash"); // Check request: let op = get_operation_from_req_bytes(client.get_mock_write()); if let NativeOperation::PsaVerifyHash(op) = op { assert_eq!(op.key_name, key_name); assert_eq!(op.hash.to_vec(), hash); assert_eq!(op.alg, sign_algorithm); assert_eq!(op.signature.to_vec(), signature); } else { panic!("Got wrong operation type: {:?}", op); } // Check response: // VerifyHash response is empty so no checking to be done } #[test] fn psa_sign_message_test() { let mut client: TestBasicClient = Default::default(); let msg = vec![0x77_u8; 100]; let key_name = String::from("key_name"); let sign_algorithm = AsymmetricSignature::Ecdsa { hash_alg: Hash::Sha256.into(), }; let signature = vec![0x33_u8; 128]; client.set_mock_read(&get_response_bytes_from_result( NativeResult::PsaSignMessage(operations::psa_sign_message::Result { signature: signature.clone().into(), }), )); // Check response: assert_eq!( client .psa_sign_message(&key_name, &msg, sign_algorithm) .expect("Failed to sign message"), signature ); // Check request: let op = get_operation_from_req_bytes(client.get_mock_write()); if let NativeOperation::PsaSignMessage(op) = op { assert_eq!(op.key_name, key_name); assert_eq!(op.message.to_vec(), msg); assert_eq!(op.alg, sign_algorithm); } else { panic!("Got wrong operation type: {:?}", op); } } #[test] fn verify_message_test() { let mut client: TestBasicClient = Default::default(); let msg = vec![0x77_u8; 100]; let key_name = String::from("key_name"); let sign_algorithm = AsymmetricSignature::Ecdsa { hash_alg: Hash::Sha256.into(), }; let signature = vec![0x33_u8; 128]; client.set_mock_read(&get_response_bytes_from_result( NativeResult::PsaVerifyMessage(operations::psa_verify_message::Result {}), )); client .psa_verify_message(&key_name, &msg, sign_algorithm, &signature) .expect("Failed to sign hash"); // Check request: let op = get_operation_from_req_bytes(client.get_mock_write()); if let NativeOperation::PsaVerifyMessage(op) = op { assert_eq!(op.key_name, key_name); assert_eq!(op.message.to_vec(), msg); assert_eq!(op.alg, sign_algorithm); assert_eq!(op.signature.to_vec(), signature); } else { panic!("Got wrong operation type: {:?}", op); } // Check response: // VerifyMessage response is empty so no checking to be done } #[test] fn asymmetric_encrypt_test() { let mut client: TestBasicClient = Default::default(); let plaintext = vec![0x77_u8; 32]; let key_name = String::from("key_name"); let encrypt_algorithm = AsymmetricEncryption::RsaPkcs1v15Crypt; let ciphertext = vec![0x33_u8; 128]; client.set_mock_read(&get_response_bytes_from_result( NativeResult::PsaAsymmetricEncrypt(operations::psa_asymmetric_encrypt::Result { ciphertext: ciphertext.clone().into(), }), )); // Check response: assert_eq!( client .psa_asymmetric_encrypt(&key_name, encrypt_algorithm, &plaintext, None) .expect("Failed to encrypt message"), ciphertext ); // Check request: let op = get_operation_from_req_bytes(client.get_mock_write()); if let NativeOperation::PsaAsymmetricEncrypt(op) = op { assert_eq!(op.key_name, key_name); assert_eq!(op.alg, encrypt_algorithm); assert_eq!(*op.plaintext, plaintext); assert_eq!(op.salt, None); } else { panic!("Got wrong operation type: {:?}", op); } } #[test] fn asymmetric_decrypt_test() { let mut client: TestBasicClient = Default::default(); let plaintext = vec![0x77_u8; 32]; let key_name = String::from("key_name"); let encrypt_algorithm = AsymmetricEncryption::RsaPkcs1v15Crypt; let ciphertext = vec![0x33_u8; 128]; client.set_mock_read(&get_response_bytes_from_result( NativeResult::PsaAsymmetricDecrypt(operations::psa_asymmetric_decrypt::Result { plaintext: plaintext.clone().into(), }), )); // Check response assert_eq!( client .psa_asymmetric_decrypt(&key_name, encrypt_algorithm, &ciphertext, None) .expect("Failed to decrypt message"), plaintext ); // Check request: let op = get_operation_from_req_bytes(client.get_mock_write()); if let NativeOperation::PsaAsymmetricDecrypt(op) = op { assert_eq!(op.key_name, key_name); assert_eq!(op.alg, encrypt_algorithm); assert_eq!(*op.ciphertext, ciphertext); assert_eq!(op.salt, None); } else { panic!("Got wrong operation type: {:?}", op); } } #[test] fn aead_encrypt_test() { let mut client: TestBasicClient = Default::default(); let plaintext = vec![0x77_u8; 32]; let nonce = vec![0x0_u8; 32]; let key_name = String::from("key_name"); let encrypt_algorithm = Aead::AeadWithDefaultLengthTag(AeadWithDefaultLengthTag::Ccm); let ciphertext = vec![0x33_u8; 128]; let additional_data = vec![0x55_u8; 128]; client.set_mock_read(&get_response_bytes_from_result( NativeResult::PsaAeadEncrypt(operations::psa_aead_encrypt::Result { ciphertext: ciphertext.clone().into(), }), )); // Check response assert_eq!( client .psa_aead_encrypt( &key_name, encrypt_algorithm, &nonce, &additional_data, &plaintext ) .expect("Failed to encrypt message"), ciphertext ); // Check request: let op = get_operation_from_req_bytes(client.get_mock_write()); if let NativeOperation::PsaAeadEncrypt(op) = op { assert_eq!(op.key_name, key_name); assert_eq!(op.alg, encrypt_algorithm); assert_eq!(*op.plaintext, plaintext); assert_eq!(*op.nonce, nonce); assert_eq!(*op.additional_data, additional_data); } else { panic!("Got wrong operation type: {:?}", op); } } #[test] fn aead_decrypt_test() { let mut client: TestBasicClient = Default::default(); let plaintext = vec![0x77_u8; 32]; let nonce = vec![0x0_u8; 32]; let key_name = String::from("key_name"); let encrypt_algorithm = Aead::AeadWithDefaultLengthTag(AeadWithDefaultLengthTag::Ccm); let ciphertext = vec![0x33_u8; 128]; let additional_data = vec![0x55_u8; 128]; client.set_mock_read(&get_response_bytes_from_result( NativeResult::PsaAeadDecrypt(operations::psa_aead_decrypt::Result { plaintext: plaintext.clone().into(), }), )); // Check response assert_eq!( client .psa_aead_decrypt( &key_name, encrypt_algorithm, &nonce, &additional_data, &ciphertext ) .expect("Failed to decrypt message"), plaintext ); // Check request: let op = get_operation_from_req_bytes(client.get_mock_write()); if let NativeOperation::PsaAeadDecrypt(op) = op { assert_eq!(op.key_name, key_name); assert_eq!(op.alg, encrypt_algorithm); assert_eq!(*op.ciphertext, ciphertext); assert_eq!(*op.nonce, nonce); assert_eq!(*op.additional_data, additional_data); } else { panic!("Got wrong operation type: {:?}", op); } } #[test] fn hash_compute_test() { let mut client: TestBasicClient = Default::default(); let message = vec![0x77_u8; 32]; let hash_algorithm = Hash::Sha256; let hash = vec![0x33_u8; 128]; client.set_mock_read(&get_response_bytes_from_result( NativeResult::PsaHashCompute(operations::psa_hash_compute::Result { hash: hash.clone().into(), }), )); // Check response assert_eq!( client .psa_hash_compute(hash_algorithm, &message,) .expect("Failed to decrypt message"), hash ); // Check request: let op = get_operation_from_req_bytes(client.get_mock_write()); if let NativeOperation::PsaHashCompute(op) = op { assert_eq!(op.alg, hash_algorithm); assert_eq!(*op.input, message); } else { panic!("Got wrong operation type: {:?}", op); } } #[test] fn hash_compare_test() { let mut client: TestBasicClient = Default::default(); let message = vec![0x77_u8; 32]; let hash_algorithm = Hash::Sha256; let hash = vec![0x33_u8; 128]; client.set_mock_read(&get_response_bytes_from_result( NativeResult::PsaHashCompare(operations::psa_hash_compare::Result {}), )); // Check response client .psa_hash_compare(hash_algorithm, &message, &hash) .expect("Failed to decrypt message"); // Check request: let op = get_operation_from_req_bytes(client.get_mock_write()); if let NativeOperation::PsaHashCompare(op) = op { assert_eq!(op.alg, hash_algorithm); assert_eq!(*op.input, message); assert_eq!(*op.hash, hash); } else { panic!("Got wrong operation type: {:?}", op); } } #[test] fn raw_key_agreement_test() { let mut client: TestBasicClient = Default::default(); let key_name = String::from("key_name"); let agreement_alg = RawKeyAgreement::Ecdh; let peer_key = vec![0x33_u8; 128]; let shared_secret = vec![0xff_u8, 64]; client.set_mock_read(&get_response_bytes_from_result( NativeResult::PsaRawKeyAgreement(operations::psa_raw_key_agreement::Result { shared_secret: Secret::new(shared_secret.clone()), }), )); // Check response assert_eq!( client .psa_raw_key_agreement(agreement_alg, &key_name, &peer_key) .expect("Failed key agreement"), shared_secret ); // Check request: let op = get_operation_from_req_bytes(client.get_mock_write()); if let NativeOperation::PsaRawKeyAgreement(op) = op { assert_eq!(op.private_key_name, key_name); assert_eq!(op.alg, agreement_alg); assert_eq!(*op.peer_key, peer_key); } else { panic!("Got wrong operation type: {:?}", op); } } #[test] fn different_response_type_test() { let mut client: TestBasicClient = Default::default(); client.set_mock_read(&get_response_bytes_from_result( NativeResult::PsaVerifyHash(operations::psa_verify_hash::Result {}), )); let key_name = String::from("key-name"); let err = client .psa_destroy_key(&key_name) .expect_err("Error was expected"); assert!(matches!( err, Error::Client(ClientErrorKind::InvalidServiceResponseType) )); } #[test] fn response_status_test() { let mut client: TestBasicClient = Default::default(); let mut stream = MockStream::new(); let status = ResponseStatus::PsaErrorDataCorrupt; let mut resp = Response::from_request_header(REQ_HEADER, ResponseStatus::Success); resp.header.status = status; resp.write_to_stream(&mut stream).unwrap(); client.set_mock_read(&stream.pop_bytes_written()); let err = client.ping().expect_err("Error was expected"); assert!(matches!( err, Error::Service(ResponseStatus::PsaErrorDataCorrupt) )); } #[test] fn malformed_response_test() { let mut client: TestBasicClient = Default::default(); client.set_mock_read(&[0xcb_u8; 130]); let err = client.ping().expect_err("Error was expected"); assert!(matches!( err, Error::Client(ClientErrorKind::Interface(ResponseStatus::InvalidHeader)) )); } #[test] fn request_fields_test() { let mut client: TestBasicClient = Default::default(); client.set_mock_read(&get_response_bytes_from_result(NativeResult::Ping( operations::ping::Result { wire_protocol_version_maj: 1, wire_protocol_version_min: 0, }, ))); let _ = client.ping().expect("Ping failed"); let req = get_req_from_bytes(client.get_mock_write()); assert_eq!(req.header, REQ_HEADER); } #[test] fn auth_value_test() { let mut client: TestBasicClient = Default::default(); client.set_mock_read(&get_response_bytes_from_result( NativeResult::PsaDestroyKey(operations::psa_destroy_key::Result {}), )); let key_name = String::from("key-name"); client .psa_destroy_key(&key_name) .expect("Failed to call destroy key"); let req = get_req_from_bytes(client.get_mock_write()); assert_eq!( String::from_utf8(req.auth.buffer.expose_secret().to_owned()).unwrap(), String::from(DEFAULT_APP_NAME) ); } #[test] fn peer_credential_auth_test() { let mut client: TestBasicClient = Default::default(); client.set_auth_data(Authentication::UnixPeerCredentials); client.set_mock_read(&get_response_bytes_from_result( NativeResult::PsaDestroyKey(operations::psa_destroy_key::Result {}), )); let key_name = String::from("key-name"); client .psa_destroy_key(&key_name) .expect("Failed to call destroy key"); let req = get_req_from_bytes(client.get_mock_write()); let current_uid: libc::uid_t = unsafe { libc::getuid() }; assert_eq!( ¤t_uid.to_le_bytes().to_vec(), req.auth.buffer.expose_secret() ); } #[test] fn failing_ipc_test() { let mut client: TestBasicClient = Default::default(); client.set_ipc_handler(Box::from(FailingMockIpc(FailingMockStream::new( ErrorKind::ConnectionRefused, "connection was refused, so rude", 1, )))); let err = client.ping().expect_err("Expected to fail"); assert!(matches!( err, Error::Client(ClientErrorKind::Interface(ResponseStatus::ConnectionError)) )); } #[test] fn set_default_auth_one_entry() { let mut client: TestBasicClient = Default::default(); client.set_auth_data(Authentication::UnixPeerCredentials); client.set_mock_read(&get_response_bytes_from_result( NativeResult::ListAuthenticators(operations::list_authenticators::Result { authenticators: vec![AuthenticatorInfo { description: String::new(), version_maj: 1, version_min: 0, version_rev: 0, id: AuthType::UnixPeerCredentials, }], }), )); client.set_default_auth(None).unwrap(); assert_eq!(client.auth_data(), Authentication::UnixPeerCredentials); } #[test] fn set_default_auth_three_entries() { let mut client: TestBasicClient = Default::default(); client.set_auth_data(Authentication::UnixPeerCredentials); client.set_mock_read(&get_response_bytes_from_result( NativeResult::ListAuthenticators(operations::list_authenticators::Result { authenticators: vec![ AuthenticatorInfo { description: String::new(), version_maj: 1, version_min: 0, version_rev: 0, id: AuthType::Jwt, }, AuthenticatorInfo { description: String::new(), version_maj: 1, version_min: 0, version_rev: 0, id: AuthType::NoAuth, }, AuthenticatorInfo { description: String::new(), version_maj: 1, version_min: 0, version_rev: 0, id: AuthType::UnixPeerCredentials, }, ], }), )); client.set_default_auth(None).unwrap(); assert_eq!(client.auth_data(), Authentication::UnixPeerCredentials); } #[test] fn set_default_auth_direct() { let mut client: TestBasicClient = Default::default(); client.set_auth_data(Authentication::UnixPeerCredentials); client.set_mock_read(&get_response_bytes_from_result( NativeResult::ListAuthenticators(operations::list_authenticators::Result { authenticators: vec![AuthenticatorInfo { description: String::new(), version_maj: 1, version_min: 0, version_rev: 0, id: AuthType::Direct, }], }), )); assert!(matches!( client.set_default_auth(None).unwrap_err(), Error::Client(ClientErrorKind::MissingParam) )); client.set_mock_read(&get_response_bytes_from_result( NativeResult::ListAuthenticators(operations::list_authenticators::Result { authenticators: vec![AuthenticatorInfo { description: String::new(), version_maj: 1, version_min: 0, version_rev: 0, id: AuthType::Direct, }], }), )); let app_name = String::from("some_app_name"); client.set_default_auth(Some(app_name.clone())).unwrap(); assert_eq!(client.auth_data(), Authentication::Direct(app_name)); } parsec-client-0.16.0/src/core/testing/mod.rs000064400000000000000000000040541046102023000167650ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 #![cfg(test)] use super::basic_client::BasicClient; use super::ipc_handler::{Connect, ReadWrite}; use crate::auth::Authentication; use crate::error::Result; use mockstream::{FailingMockStream, SyncMockStream}; use parsec_interface::requests::ProviderId; use std::ops::{Deref, DerefMut}; use std::time::Duration; mod core_tests; const DEFAULT_APP_NAME: &str = "default-test-app-name"; struct MockIpc(SyncMockStream); impl Connect for MockIpc { fn connect(&self) -> Result> { Ok(Box::from(self.0.clone())) } fn set_timeout(&mut self, _timeout: Option) {} } struct FailingMockIpc(FailingMockStream); impl Connect for FailingMockIpc { fn connect(&self) -> Result> { Ok(Box::from(self.0.clone())) } fn set_timeout(&mut self, _timeout: Option) {} } struct TestBasicClient { core_client: BasicClient, mock_stream: SyncMockStream, } impl TestBasicClient { pub fn set_mock_read(&mut self, bytes: &[u8]) { self.mock_stream.push_bytes_to_read(bytes); } pub fn get_mock_write(&mut self) -> Vec { self.mock_stream.pop_bytes_written() } } impl Deref for TestBasicClient { type Target = BasicClient; fn deref(&self) -> &Self::Target { &self.core_client } } impl DerefMut for TestBasicClient { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.core_client } } impl Default for TestBasicClient { fn default() -> Self { let core_client = BasicClient { op_client: Default::default(), auth_data: Authentication::Direct(String::from(DEFAULT_APP_NAME)), implicit_provider: ProviderId::Pkcs11, }; let mut client = TestBasicClient { core_client, mock_stream: SyncMockStream::new(), }; client .core_client .set_ipc_handler(Box::from(MockIpc(client.mock_stream.clone()))); client } } parsec-client-0.16.0/src/error.rs000064400000000000000000000071011046102023000147260ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Error types specific to the Parsec client use parsec_interface::requests::ResponseStatus; use std::error; use std::fmt; /// Enum used to denote errors returned to the library user #[derive(Debug)] pub enum Error { /// Errors originating in the service Service(ResponseStatus), /// Errors originating in the client Client(ClientErrorKind), } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Error::Service(response_status) => response_status.fmt(f), Error::Client(client_error_kind) => client_error_kind.fmt(f), } } } impl error::Error for Error {} /// Types of errors local to the client library #[derive(Debug)] pub enum ClientErrorKind { /// Errors generated by the Parsec interface library Interface(ResponseStatus), /// Errors generated by interacting with the underlying IPC mechanism Ipc(::std::io::Error), /// The opcode of the response does not match the opcode of the request InvalidServiceResponseType, /// The operation is not supported by the selected provider InvalidProvider, /// Client is missing an implicit provider NoProvider, /// Service is missing authenticator or none of the authenticators is supported /// by the client NoAuthenticator, /// Required parameter was not provided MissingParam, /// The requested resource was not found. NotFound, /// The socket address provided is not valid InvalidSocketAddress, /// The socket URL is invalid InvalidSocketUrl, /// Error while using the SPIFFE Workload API #[cfg(feature = "spiffe-auth")] Spiffe(spiffe::workload_api::client::ClientError), } impl From for Error { fn from(client_error: ClientErrorKind) -> Self { Error::Client(client_error) } } impl From for Error { fn from(_: url::ParseError) -> Self { Error::Client(ClientErrorKind::InvalidSocketUrl) } } impl fmt::Display for ClientErrorKind { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { ClientErrorKind::Interface(response_status) => response_status.fmt(f), ClientErrorKind::Ipc(error) => error.fmt(f), ClientErrorKind::InvalidServiceResponseType => write!( f, "the opcode of the response does not match the opcode of the request" ), ClientErrorKind::InvalidProvider => { write!(f, "operation not supported by selected provider") } ClientErrorKind::NoProvider => write!(f, "client is missing an implicit provider"), ClientErrorKind::NoAuthenticator => write!(f, "service is not reporting any authenticators or none of the reported ones are supported by the client"), ClientErrorKind::MissingParam => write!(f, "one of the `Option` parameters was required but was not provided"), ClientErrorKind::NotFound => write!(f, "one of the resources required in the operation was not found"), ClientErrorKind::InvalidSocketAddress => write!(f, "the socket address provided in the URL is not valid"), ClientErrorKind::InvalidSocketUrl => write!(f, "the socket URL is invalid"), #[cfg(feature = "spiffe-auth")] ClientErrorKind::Spiffe(error) => error.fmt(f), } } } /// Result type used for the internals and interface of the Parsec client pub type Result = ::std::result::Result; parsec-client-0.16.0/src/lib.rs000064400000000000000000000024361046102023000143510ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Client library for integration with the Parsec service // #![doc(html_logo_url = "https://www.rust-lang.org/logos/rust-logo-128x128-blk-v2.png")] TODO: Set to Parsec logo #![deny( nonstandard_style, dead_code, improper_ctypes, non_shorthand_field_patterns, no_mangle_generic_items, overflowing_literals, path_statements, patterns_in_fns_without_body, private_in_public, unconditional_recursion, unused, unused_allocation, unused_comparisons, unused_parens, while_true, missing_debug_implementations, missing_docs, trivial_casts, trivial_numeric_casts, unused_extern_crates, unused_import_braces, unused_qualifications, unused_results, missing_copy_implementations )] // This one is hard to avoid. #![allow(clippy::multiple_crate_versions)] //! Currently this crate allows interaction with the PARSEC service through //! [`BasicClient`](core/basic_client/struct.BasicClient.html), a low-level client that allows //! all supported operations to be performed, requiring all operation parameters //! to be provided explicitly. pub mod auth; pub mod core; pub mod error; pub use crate::core::basic_client::BasicClient; parsec-client-0.16.0/tests/ci.sh000075500000000000000000000021511046102023000145340ustar 00000000000000#!/usr/bin/env bash # Copyright 2020 Contributors to the Parsec project. # SPDX-License-Identifier: Apache-2.0 # Continuous Integration test script, executed by GitHub Actions on x86 and # Travis CI on Arm64. set -euf -o pipefail ################ # Build client # ################ RUST_BACKTRACE=1 cargo build RUST_BACKTRACE=1 cargo build --features testing RUST_BACKTRACE=1 cargo build --features spiffe-auth RUST_BACKTRACE=1 cargo build --no-default-features ################# # Static checks # ################# # On native target clippy or fmt might not be available. if cargo fmt -h; then cargo fmt --all -- --check fi if cargo clippy -h; then cargo clippy --all-targets -- -D clippy::all -D clippy::cargo fi ###################### # Start Mock Service # ###################### CURRENT_PATH=$(pwd) cd parsec-mock-0.1.1 python parsec_mock/parsec_mock.py --parsec-socket $CURRENT_PATH/parsec_mock.sock & while [[ ! -S $CURRENT_PATH/parsec_mock.sock ]]; do sleep 5 done cd .. export PARSEC_SERVICE_ENDPOINT="unix://$CURRENT_PATH/parsec_mock.sock" ############# # Run tests # ############# RUST_BACKTRACE=1 cargo test parsec-client-0.16.0/tests/coverage.sh000075500000000000000000000012541046102023000157370ustar 00000000000000#!/usr/bin/env bash # Copyright 2021 Contributors to the Parsec project. # SPDX-License-Identifier: Apache-2.0 set -euf -o pipefail cargo install cargo-tarpaulin ###################### # Start Mock Service # ###################### CURRENT_PATH=$(pwd) cd parsec-mock-0.1.1 python parsec_mock/parsec_mock.py --parsec-socket $CURRENT_PATH/parsec_mock.sock & while [[ ! -S $CURRENT_PATH/parsec_mock.sock ]]; do sleep 5 done cd .. export PARSEC_SERVICE_ENDPOINT="unix://$CURRENT_PATH/parsec_mock.sock" ###################### # Run tests # ###################### cargo tarpaulin --tests --out Xml --exclude-files="src/core/testing/*" bash <(curl -s https://codecov.io/bash) parsec-client-0.16.0/tests/mock_service.rs000064400000000000000000000004501046102023000166210ustar 00000000000000// Copyright 2021 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use parsec_client::BasicClient; #[test] fn ping_noauth() { let client = BasicClient::new_naked().unwrap(); // ping_noauth request assert_eq!(client.ping().expect("Ping failed"), (1, 0)); }