parsec-service-1.3.0/.cargo/audit.toml000064400000000000000000000020051046102023000157110ustar 00000000000000[advisories] 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-service-1.3.0/.cargo/config.toml000064400000000000000000000003101046102023000160450ustar 00000000000000# Cargo seems to expect the -gcc variant instead of -ld [target.armv7-unknown-linux-gnueabihf] linker = "arm-linux-gnueabihf-gcc" [target.aarch64-unknown-linux-gnu] linker = "aarch64-linux-gnu-gcc" parsec-service-1.3.0/.cargo_vcs_info.json0000644000000001360000000000100137500ustar { "git": { "sha1": "798a3d9ce760a60532ead33e9f37139500c8a48b" }, "path_in_vcs": "" }parsec-service-1.3.0/.circleci/config.yml000064400000000000000000000005021046102023000163600ustar 00000000000000version: 2.1 jobs: build: working_directory: ~/repo docker: - image: ghcr.io/parallaxsecond/parsec-service-test-all resource_class: arm.medium steps: - checkout - run: | git submodule update --init --recursive cargo build --features=all-providers,all-authenticators parsec-service-1.3.0/.dockerignore000064400000000000000000000003201046102023000152070ustar 00000000000000quickstart/quickstart.Dockerfile quickstart/package.sh quickstart/*.tar.gz .idea/ # Copied from .gitignore /target *.psa_its *.swp tags *DS_Store *vscode *.patch mappings/ kim-mappings/ NVChip .devcontainer parsec-service-1.3.0/.github/CODEOWNERS000064400000000000000000000016501046102023000154750ustar 00000000000000# The content of the config.rs file should not change in a breaking way. # See https://github.com/parallaxsecond/parsec/issues/393 for details. src/utils/config.rs @parallaxsecond/admin # The content of the cli.rs file should not change in a breaking way. # See https://github.com/parallaxsecond/parsec/issues/392 for details. src/utils/cli.rs @parallaxsecond/admin # The Docker container is also used to check that there are no breaking # changes in buildtime dependencies. # See https://github.com/parallaxsecond/parsec/issues/397 # See https://github.com/parallaxsecond/parsec/issues/408 e2e_tests/docker_image/ @parallaxsecond/admin # The way tests are executed should be only modified carefully to not remove # regression or breaking changes detection. ci.sh @parallaxsecond/admin # The main function file contains interactions with the operating system which must # stay stable. src/bin/main.rs @parallaxsecond/admin parsec-service-1.3.0/.github/actions/load_docker/action.yml000064400000000000000000000011671046102023000220130ustar 00000000000000name: "Load docker image" description: "Load docker image" inputs: image-name: required: true description: "Docker image name" image-path: required: true description: "Path to save the docker image" # ...name, description and inputs as above runs: using: "composite" steps: - uses: actions/checkout@v3 - name: Download artifact uses: actions/download-artifact@v3 with: name: ${{ inputs.image-name }} path: ${{ inputs.image-path }} - name: Load image run: docker load --input ${{ inputs.image-path }}/${{ inputs.image-name }}.tar shell: bash parsec-service-1.3.0/.github/workflows/ci.yml000064400000000000000000000203271046102023000172570ustar 00000000000000name: Continuous Integration on: [push, pull_request, workflow_dispatch] env: # TEST_ALL_DOCKER_IMAGE: 'parsec-service-test-all' TEST_ALL_DOCKER_IMAGE: 'ghcr.io/parallaxsecond/parsec-service-test-all' jobs: build-and-export-test-all-docker: runs-on: ubuntu-latest # If TEST_ALL_DOCKER_IMAGE is 'parsec-service-test-all' or any local image, # the following condition must evaluate true to execute this job # Else it must evaluate false to NOT execute this job # Unfortunately, env.TEST_ALL_DOCKER_IMAGE cannot be used here as the `env` context is not recognized at this level. if: ${{ false }} # env.TEST_ALL_DOCKER_IMAGE == 'parsec-service-test-all' steps: - uses: actions/checkout@v3 - name: Build the docker container run: pushd e2e_tests/docker_image && docker build -t parsec-service-test-all -f parsec-service-test-all.Dockerfile . && popd - name: Export the docker container run: docker save parsec-service-test-all > /tmp/parsec-service-test-all.tar - name: Upload artifact uses: actions/upload-artifact@v3 with: name: parsec-service-test-all path: /tmp/parsec-service-test-all.tar all-providers: name: Various tests targeting a Parsec image with all providers included runs-on: ubuntu-latest if: ${{ always() }} needs: [build-and-export-test-all-docker] steps: - uses: actions/checkout@v2 - name: Load Docker uses: ./.github/actions/load_docker if: ${{ env.TEST_ALL_DOCKER_IMAGE == 'parsec-service-test-all' }} with: image-name: "${{ env.TEST_ALL_DOCKER_IMAGE }}" image-path: "/tmp" - name: Run the container to execute the test script run: docker run -v $(pwd):/tmp/parsec -w /tmp/parsec -t ${{ env.TEST_ALL_DOCKER_IMAGE }} /tmp/parsec/ci.sh all build-all-providers: name: Cargo check all-providers (current Rust stable & old compiler) runs-on: ubuntu-latest if: ${{ always() }} needs: [build-and-export-test-all-docker] steps: - uses: actions/checkout@v2 - name: Load Docker uses: ./.github/actions/load_docker if: ${{ env.TEST_ALL_DOCKER_IMAGE == 'parsec-service-test-all' }} with: image-name: "${{ env.TEST_ALL_DOCKER_IMAGE }}" image-path: "/tmp" - name: Run the container to execute the test script run: docker run -v $(pwd):/tmp/parsec -w /tmp/parsec -t ${{ env.TEST_ALL_DOCKER_IMAGE }} /tmp/parsec/ci.sh cargo-check mbed-crypto-provider: name: Integration tests using Mbed Crypto provider runs-on: ubuntu-latest if: ${{ always() }} needs: [build-and-export-test-all-docker] steps: - uses: actions/checkout@v2 - name: Load Docker uses: ./.github/actions/load_docker if: ${{ env.TEST_ALL_DOCKER_IMAGE == 'parsec-service-test-all' }} with: image-name: "${{ env.TEST_ALL_DOCKER_IMAGE }}" image-path: "/tmp" - name: Run the container to execute the test script run: docker run -v $(pwd):/tmp/parsec -w /tmp/parsec -t ${{ env.TEST_ALL_DOCKER_IMAGE }} /tmp/parsec/ci.sh mbed-crypto pkcs11-provider: name: Integration tests using PKCS 11 provider runs-on: ubuntu-latest if: ${{ always() }} needs: [build-and-export-test-all-docker] steps: - uses: actions/checkout@v2 - name: Load Docker uses: ./.github/actions/load_docker if: ${{ env.TEST_ALL_DOCKER_IMAGE == 'parsec-service-test-all' }} with: image-name: "${{ env.TEST_ALL_DOCKER_IMAGE }}" image-path: "/tmp" - name: Run the container to execute the test script run: docker run -v $(pwd):/tmp/parsec -w /tmp/parsec -t ${{ env.TEST_ALL_DOCKER_IMAGE }} /tmp/parsec/ci.sh pkcs11 --no-stress-test tpm-provider: name: Integration tests using TPM provider runs-on: ubuntu-latest if: ${{ always() }} needs: [build-and-export-test-all-docker] steps: - uses: actions/checkout@v2 - name: Load Docker uses: ./.github/actions/load_docker if: ${{ env.TEST_ALL_DOCKER_IMAGE == 'parsec-service-test-all' }} with: image-name: "${{ env.TEST_ALL_DOCKER_IMAGE }}" image-path: "/tmp" - name: Run the container to execute the test script run: docker run -v $(pwd):/tmp/parsec -w /tmp/parsec -t ${{ env.TEST_ALL_DOCKER_IMAGE }} /tmp/parsec/ci.sh tpm trusted-service-provider: name: Integration tests using Crypto Trusted Service provider runs-on: ubuntu-latest if: ${{ always() }} needs: [build-and-export-test-all-docker] steps: - uses: actions/checkout@v2 - name: Load Docker uses: ./.github/actions/load_docker if: ${{ env.TEST_ALL_DOCKER_IMAGE == 'parsec-service-test-all' }} with: image-name: "${{ env.TEST_ALL_DOCKER_IMAGE }}" image-path: "/tmp" - name: Run the container to execute the test script run: docker run -v $(pwd):/tmp/parsec -w /tmp/parsec -t ${{ env.TEST_ALL_DOCKER_IMAGE }} /tmp/parsec/ci.sh trusted-service cryptoauthlib-provider: name: Integration tests using CryptoAuthentication Library provider runs-on: ubuntu-latest if: ${{ always() }} needs: [build-and-export-test-all-docker] steps: - uses: actions/checkout@v2 - name: Load Docker uses: ./.github/actions/load_docker if: ${{ env.TEST_ALL_DOCKER_IMAGE == 'parsec-service-test-all' }} with: image-name: "${{ env.TEST_ALL_DOCKER_IMAGE }}" image-path: "/tmp" - name: Run the container to execute the test script run: docker run -v $(pwd):/tmp/parsec -w /tmp/parsec -t ${{ env.TEST_ALL_DOCKER_IMAGE }} /tmp/parsec/ci.sh cryptoauthlib --no-stress-test fuzz-test-checker: name: Check that the fuzz testing framework is still working runs-on: ubuntu-latest if: ${{ always() }} needs: [build-and-export-test-all-docker] steps: - uses: actions/checkout@v2 - name: Load Docker uses: ./.github/actions/load_docker if: ${{ env.TEST_ALL_DOCKER_IMAGE == 'parsec-service-test-all' }} with: image-name: "${{ env.TEST_ALL_DOCKER_IMAGE }}" image-path: "/tmp" # Not running stress tests because rust-cryptoauthlib test-interface does not support required calls - name: Run the fuzz test script From Container # When running the container built on the CI if: ${{ env.TEST_ALL_DOCKER_IMAGE == 'parsec-service-test-all' }} run: CONTAINER_TAG=parsec-service-test-all ./fuzz.sh test - name: Run the fuzz test script if: ${{ env.TEST_ALL_DOCKER_IMAGE != 'parsec-service-test-all' }} run: ./fuzz.sh test on-disk-kim: name: OnDiskKIM E2E tests on all providers runs-on: ubuntu-latest if: ${{ always() }} needs: [build-and-export-test-all-docker] steps: - uses: actions/checkout@v2 - name: Load Docker uses: ./.github/actions/load_docker if: ${{ env.TEST_ALL_DOCKER_IMAGE == 'parsec-service-test-all' }} with: image-name: "${{ env.TEST_ALL_DOCKER_IMAGE }}" image-path: "/tmp" - name: Run the container to execute the test script run: docker run -v $(pwd):/tmp/parsec -w /tmp/parsec -t ${{ env.TEST_ALL_DOCKER_IMAGE }} /tmp/parsec/ci.sh on-disk-kim cross-compilation: # Currently only the Mbed Crypto, PKCS 11, and TPM providers are tested as the other ones need to cross-compile other libraries. name: Cross-compile Parsec to various targets runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Run the container to execute the test script run: docker run -v $(pwd):/tmp/parsec -w /tmp/parsec ghcr.io/parallaxsecond/parsec-service-test-cross-compile /tmp/parsec/test/cross-compile.sh # When running the container built on the CI # run: docker run -v $(pwd):/tmp/parsec -w /tmp/parsec -t parsec-service-test-cross-compile /tmp/parsec/test/cross-compile.sh links: name: Check links runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Link Checker uses: peter-evans/link-checker@v1 with: args: -v -r *.md - name: Fail if there were link errors run: exit ${{ steps.lc.outputs.exit_code }} parsec-service-1.3.0/.github/workflows/nightly.yml000064400000000000000000000032021046102023000203330ustar 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: Gather coverage data and upload to Codecov runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: ref: "${{ github.event.inputs.rev }}" - name: Run the container to execute the coverage script run: docker run -v $(pwd):/tmp/parsec -w /tmp/parsec --security-opt seccomp=unconfined ghcr.io/parallaxsecond/parsec-service-test-all /tmp/parsec/ci.sh coverage - name: Collect coverage results run: bash <(curl -s https://codecov.io/bash) parsec-service-1.3.0/.gitignore000064400000000000000000000006021046102023000145260ustar 00000000000000# Cargo build directory /target # Mbed Crypto key files *.psa_its # Editor swap files *.swp tags # MacOS folder attributes file *DS_Store # VS Code config folder *vscode # Git patch files *.patch # Parsec key info mappings directories mappings/ kim-mappings/ # TPM simulator state file NVChip .devcontainer # Quickstart tarball quickstart/*.tar.gz # IDE settings files .idea parsec-service-1.3.0/.gitmodules000064400000000000000000000002331046102023000147130ustar 00000000000000[submodule "trusted-services-vendor"] path = trusted-services-vendor url = https://git.trustedfirmware.org/TS/trusted-services.git branch = integration parsec-service-1.3.0/.travis.yml.disabled000064400000000000000000000023321046102023000164170ustar 00000000000000# Executing our tests on Arm64 with Travis CI # The TPM provider Dockerfile does not build on Arm so the all-providers and tpm-provider tests # are not executed on Aarch64. if: type = cron arch: arm64 services: - docker jobs: include: - name: "Integration tests using Mbed Crypto provider" env: DOCKER_IMAGE_NAME=mbed-crypto-provider DOCKER_IMAGE_PATH=e2e_tests/provider_cfg/mbed-crypto SCRIPT="ci.sh mbed-crypto" - name: "Integration tests using PKCS 11 provider" env: DOCKER_IMAGE_NAME=pkcs11-provider DOCKER_IMAGE_PATH=e2e_tests/provider_cfg/pkcs11 SCRIPT="ci.sh pkcs11" # Re-enabling these for now, we'll need to fix the issues, otherwise the build is just useless # PKCS11 tests are failing because of unidentified issues. # See https://github.com/parallaxsecond/parsec/issues/116 # allow_failures: # - env: DOCKER_IMAGE_NAME=mbed-crypto-provider DOCKER_IMAGE_PATH=e2e_tests/provider_cfg/mbed-crypto SCRIPT="ci.sh mbed-crypto" # - env: DOCKER_IMAGE_NAME=pkcs11-provider DOCKER_IMAGE_PATH=e2e_tests/provider_cfg/pkcs11 SCRIPT="ci.sh pkcs11" script: - docker build -t $DOCKER_IMAGE_NAME $DOCKER_IMAGE_PATH - docker run -v $(pwd):/tmp/parsec -w /tmp/parsec $DOCKER_IMAGE_NAME /tmp/parsec/$SCRIPT parsec-service-1.3.0/CHANGELOG.md000064400000000000000000002251651046102023000143640ustar 00000000000000# Changelog ## [1.3.0](https://github.com/parallaxsecond/parsec/tree/1.3.0) (2023-10-25) [Full Changelog](https://github.com/parallaxsecond/parsec/compare/1.3.0-rc2...1.3.0) ## [1.3.0-rc2](https://github.com/parallaxsecond/parsec/tree/1.3.0-rc2) (2023-10-19) [Full Changelog](https://github.com/parallaxsecond/parsec/compare/1.3.0-rc1...1.3.0-rc2) **Merged pull requests:** - e2e\_tests/wrong\_permitted\_algorithm: Change used sha for hw compatibi… [\#723](https://github.com/parallaxsecond/parsec/pull/723) ([tgonzalezorlandoarm](https://github.com/tgonzalezorlandoarm)) ## [1.3.0-rc1](https://github.com/parallaxsecond/parsec/tree/1.3.0-rc1) (2023-10-17) [Full Changelog](https://github.com/parallaxsecond/parsec/compare/1.2.0...1.3.0-rc1) **Closed issues:** - Update cryptoki version to `0.4.1` [\#668](https://github.com/parallaxsecond/parsec/issues/668) - `ansi_term` is unmaintained [\#629](https://github.com/parallaxsecond/parsec/issues/629) **Merged pull requests:** - Bump psa-crypto and interface crates [\#718](https://github.com/parallaxsecond/parsec/pull/718) ([gowthamsk-arm](https://github.com/gowthamsk-arm)) - Update toml, env\_logger and bindgen crates [\#716](https://github.com/parallaxsecond/parsec/pull/716) ([tgonzalezorlandoarm](https://github.com/tgonzalezorlandoarm)) - Bump various crates [\#714](https://github.com/parallaxsecond/parsec/pull/714) ([tgonzalezorlandoarm](https://github.com/tgonzalezorlandoarm)) - Update picky crates [\#711](https://github.com/parallaxsecond/parsec/pull/711) ([tgonzalezorlandoarm](https://github.com/tgonzalezorlandoarm)) - Cargo.toml: Remove uuid crate [\#710](https://github.com/parallaxsecond/parsec/pull/710) ([tgonzalezorlandoarm](https://github.com/tgonzalezorlandoarm)) - Bump sd-notify to 0.4.1 [\#708](https://github.com/parallaxsecond/parsec/pull/708) ([tgonzalezorlandoarm](https://github.com/tgonzalezorlandoarm)) - fuzz: Bump bumpalo to 3.14.0 [\#706](https://github.com/parallaxsecond/parsec/pull/706) ([tgonzalezorlandoarm](https://github.com/tgonzalezorlandoarm)) - Update the tss-esapi crate to version 7.3.0 [\#702](https://github.com/parallaxsecond/parsec/pull/702) ([tgonzalezorlandoarm](https://github.com/tgonzalezorlandoarm)) - Fix cargo-tarpaulin version to 0.26.1 [\#701](https://github.com/parallaxsecond/parsec/pull/701) ([tgonzalezorlandoarm](https://github.com/tgonzalezorlandoarm)) - Add a Security Vulnerability Reporting section in the README [\#700](https://github.com/parallaxsecond/parsec/pull/700) ([tgonzalezorlandoarm](https://github.com/tgonzalezorlandoarm)) - Update maintainers list [\#699](https://github.com/parallaxsecond/parsec/pull/699) ([tgonzalezorlandoarm](https://github.com/tgonzalezorlandoarm)) - ci: Fix coverage builds, nightly issues [\#698](https://github.com/parallaxsecond/parsec/pull/698) ([tgonzalezorlandoarm](https://github.com/tgonzalezorlandoarm)) - Update cryptoki and cryptoki-sys crates [\#697](https://github.com/parallaxsecond/parsec/pull/697) ([tgonzalezorlandoarm](https://github.com/tgonzalezorlandoarm)) - Use arrays instead of vec! when possible [\#696](https://github.com/parallaxsecond/parsec/pull/696) ([tgonzalezorlandoarm](https://github.com/tgonzalezorlandoarm)) - Make wrong\_permitted\_algorithm test use a non-deprecated Hash [\#695](https://github.com/parallaxsecond/parsec/pull/695) ([tgonzalezorlandoarm](https://github.com/tgonzalezorlandoarm)) - Fix coverage builds for different providers [\#694](https://github.com/parallaxsecond/parsec/pull/694) ([tgonzalezorlandoarm](https://github.com/tgonzalezorlandoarm)) - Update MSRV to Rust 1.66.0 [\#692](https://github.com/parallaxsecond/parsec/pull/692) ([tgonzalezorlandoarm](https://github.com/tgonzalezorlandoarm)) - Bump ASN1 crates dependencies [\#691](https://github.com/parallaxsecond/parsec/pull/691) ([anta5010](https://github.com/anta5010)) - Minor fixes to changelog [\#690](https://github.com/parallaxsecond/parsec/pull/690) ([gowthamsk-arm](https://github.com/gowthamsk-arm)) - Upgrade proc-macro2 package [\#688](https://github.com/parallaxsecond/parsec/pull/688) ([tgonzalezorlandoarm](https://github.com/tgonzalezorlandoarm)) - Update CONTRIBUTORS.md [\#687](https://github.com/parallaxsecond/parsec/pull/687) ([Firstyear](https://github.com/Firstyear)) - Disable the optional features for the 'structopt' crate [\#686](https://github.com/parallaxsecond/parsec/pull/686) ([tgonzalezorlandoarm](https://github.com/tgonzalezorlandoarm)) - Upgrade enumflags2 crate [\#685](https://github.com/parallaxsecond/parsec/pull/685) ([tgonzalezorlandoarm](https://github.com/tgonzalezorlandoarm)) - Remove unmaintained 'users' crate [\#684](https://github.com/parallaxsecond/parsec/pull/684) ([tgonzalezorlandoarm](https://github.com/tgonzalezorlandoarm)) - Fix compilation issues [\#682](https://github.com/parallaxsecond/parsec/pull/682) ([tgonzalezorlandoarm](https://github.com/tgonzalezorlandoarm)) - Bump base64 dependency to 0.21.0 [\#679](https://github.com/parallaxsecond/parsec/pull/679) ([ema](https://github.com/ema)) ## [1.2.0](https://github.com/parallaxsecond/parsec/tree/1.2.0) (2023-04-05) [Full Changelog](https://github.com/parallaxsecond/parsec/compare/1.2.0-rc1...1.2.0) **Closed issues:** - Parsec 1.1 fails to build with meta-security master branch [\#663](https://github.com/parallaxsecond/parsec/issues/663) ## [1.2.0-rc1](https://github.com/parallaxsecond/parsec/tree/1.2.0-rc1) (2023-03-21) [Full Changelog](https://github.com/parallaxsecond/parsec/compare/1.1.0...1.2.0-rc1) **Closed issues:** - Parsec fails to compile for arm32 [\#647](https://github.com/parallaxsecond/parsec/issues/647) **Merged pull requests:** - Update crates [\#671](https://github.com/parallaxsecond/parsec/pull/671) ([gowthamsk-arm](https://github.com/gowthamsk-arm)) - Update rusqlite to fix security issue [\#662](https://github.com/parallaxsecond/parsec/pull/662) ([gowthamsk-arm](https://github.com/gowthamsk-arm)) - Update MSRV to 1.58 [\#661](https://github.com/parallaxsecond/parsec/pull/661) ([gowthamsk-arm](https://github.com/gowthamsk-arm)) - Remove dependency on crate "version" [\#657](https://github.com/parallaxsecond/parsec/pull/657) ([ema](https://github.com/ema)) - Update TPM TCTI configuration docs [\#656](https://github.com/parallaxsecond/parsec/pull/656) ([paulhowardarm](https://github.com/paulhowardarm)) - Add support for a Quickstart Docker image [\#654](https://github.com/parallaxsecond/parsec/pull/654) ([dennisgove](https://github.com/dennisgove)) - Update to remove const\_err [\#653](https://github.com/parallaxsecond/parsec/pull/653) ([marcsvll](https://github.com/marcsvll)) - Fix Clippy warnings for rustc version 1.65 [\#652](https://github.com/parallaxsecond/parsec/pull/652) ([mohamedasaker-arm](https://github.com/mohamedasaker-arm)) - Bump sd-notify to 0.3.0 [\#651](https://github.com/parallaxsecond/parsec/pull/651) ([stevecapperarm](https://github.com/stevecapperarm)) ## [1.1.0](https://github.com/parallaxsecond/parsec/tree/1.1.0) (2022-09-29) [Full Changelog](https://github.com/parallaxsecond/parsec/compare/1.1.0-rc2...1.1.0) ## [1.1.0-rc2](https://github.com/parallaxsecond/parsec/tree/1.1.0-rc2) (2022-09-13) [Full Changelog](https://github.com/parallaxsecond/parsec/compare/1.1.0-rc1...1.1.0-rc2) **Merged pull requests:** - Update change log for release candidate 1.1.0-rc2 [\#639](https://github.com/parallaxsecond/parsec/pull/639) ([mohamedasaker-arm](https://github.com/mohamedasaker-arm)) - Release candidate prep 1.1.0 rc2 [\#638](https://github.com/parallaxsecond/parsec/pull/638) ([mohamedasaker-arm](https://github.com/mohamedasaker-arm)) ## [1.1.0-rc1](https://github.com/parallaxsecond/parsec/tree/1.1.0-rc1) (2022-09-07) [Full Changelog](https://github.com/parallaxsecond/parsec/compare/1.0.0...1.1.0-rc1) **Implemented enhancements:** - Update PKCS11 dependency [\#604](https://github.com/parallaxsecond/parsec/issues/604) - Allow binary PIN values for PKCS11 providers [\#603](https://github.com/parallaxsecond/parsec/issues/603) - Implement get\_random in the PKCS11 provider [\#594](https://github.com/parallaxsecond/parsec/issues/594) - Implement get\_random in TPM provider [\#593](https://github.com/parallaxsecond/parsec/issues/593) - Create script for Quickstart package [\#534](https://github.com/parallaxsecond/parsec/issues/534) - Recognise a PKCS11 hardware token with its serial number instead of slot number [\#481](https://github.com/parallaxsecond/parsec/issues/481) - Implement configurable exclusion of deprecated primitives [\#119](https://github.com/parallaxsecond/parsec/issues/119) **Fixed bugs:** - RSA padding oracle issue [\#619](https://github.com/parallaxsecond/parsec/issues/619) - PKCS11 provider serial\_number configuration [\#615](https://github.com/parallaxsecond/parsec/issues/615) - Export of public EC key fails with PKCS\#11 back-end on NXP Layerscape [\#599](https://github.com/parallaxsecond/parsec/issues/599) - Wrong permissions on KIM files [\#598](https://github.com/parallaxsecond/parsec/issues/598) - Send back PsaErrorInvalidPadding when needed [\#620](https://github.com/parallaxsecond/parsec/pull/620) ([ionut-arm](https://github.com/ionut-arm)) **Security fixes:** - Update Spiffe dependency [\#602](https://github.com/parallaxsecond/parsec/issues/602) **Closed issues:** - Add key persistence tests for TS provider [\#568](https://github.com/parallaxsecond/parsec/issues/568) - Create stability tests for SQLite KIM [\#519](https://github.com/parallaxsecond/parsec/issues/519) - Change default socket path for E2E tests [\#463](https://github.com/parallaxsecond/parsec/issues/463) **Merged pull requests:** - Update Change log and service version no. [\#637](https://github.com/parallaxsecond/parsec/pull/637) ([mohamedasaker-arm](https://github.com/mohamedasaker-arm)) - Update maintainers list [\#636](https://github.com/parallaxsecond/parsec/pull/636) ([mohamedasaker-arm](https://github.com/mohamedasaker-arm)) - Fix spiffy issue [\#635](https://github.com/parallaxsecond/parsec/pull/635) ([gowthamsk-arm](https://github.com/gowthamsk-arm)) - Add sqlite stability tests [\#634](https://github.com/parallaxsecond/parsec/pull/634) ([gowthamsk-arm](https://github.com/gowthamsk-arm)) - Feature/119 implement configurable exclusion of deprecated primitives [\#633](https://github.com/parallaxsecond/parsec/pull/633) ([mohamedasaker-arm](https://github.com/mohamedasaker-arm)) - Feature/603 allow binary pin values for pkcs11 [\#631](https://github.com/parallaxsecond/parsec/pull/631) ([mohamedasaker-arm](https://github.com/mohamedasaker-arm)) - Add `Eq` to the types with `PartialEq` [\#630](https://github.com/parallaxsecond/parsec/pull/630) ([ionut-arm](https://github.com/ionut-arm)) - build and share docker image across jobs [\#628](https://github.com/parallaxsecond/parsec/pull/628) ([mohamedasaker-arm](https://github.com/mohamedasaker-arm)) - Kim file permissions [\#627](https://github.com/parallaxsecond/parsec/pull/627) ([gowthamsk-arm](https://github.com/gowthamsk-arm)) - Testing/568 add key persistence tests for ts provider [\#625](https://github.com/parallaxsecond/parsec/pull/625) ([mohamedasaker-arm](https://github.com/mohamedasaker-arm)) - Fix problem reported by Clippy \(rust 1.62\) [\#624](https://github.com/parallaxsecond/parsec/pull/624) ([mohamedasaker-arm](https://github.com/mohamedasaker-arm)) - Validate hash sign operation before execution. [\#623](https://github.com/parallaxsecond/parsec/pull/623) ([gowthamsk-arm](https://github.com/gowthamsk-arm)) - Fix Hugues' email address [\#622](https://github.com/parallaxsecond/parsec/pull/622) ([hug-dev](https://github.com/hug-dev)) - Compare trimmed token serial numbers \(PKCS11 provider\) [\#621](https://github.com/parallaxsecond/parsec/pull/621) ([mohamedasaker-arm](https://github.com/mohamedasaker-arm)) - Added some context to error messages. [\#618](https://github.com/parallaxsecond/parsec/pull/618) ([fredrik-jansson-se](https://github.com/fredrik-jansson-se)) - Implement get\_random in the PKCS11 provider [\#613](https://github.com/parallaxsecond/parsec/pull/613) ([gowthamsk-arm](https://github.com/gowthamsk-arm)) - Add a script to create the Quickstart package [\#612](https://github.com/parallaxsecond/parsec/pull/612) ([mohamedasaker-arm](https://github.com/mohamedasaker-arm)) - Change default socket path for E2E tests [\#610](https://github.com/parallaxsecond/parsec/pull/610) ([gowthamsk-arm](https://github.com/gowthamsk-arm)) - Fix `cargo-audit` TOML config [\#609](https://github.com/parallaxsecond/parsec/pull/609) ([ionut-arm](https://github.com/ionut-arm)) - Recognise a PKCS11 hardware token with its serial number instead of slot number [\#608](https://github.com/parallaxsecond/parsec/pull/608) ([mohamedasaker-arm](https://github.com/mohamedasaker-arm)) - Bump version of cryptoki [\#605](https://github.com/parallaxsecond/parsec/pull/605) ([ionut-arm](https://github.com/ionut-arm)) - Fix issue \#599 - allow EC\_POINT public key data to omit ASN.1 structure wrapping [\#600](https://github.com/parallaxsecond/parsec/pull/600) ([paulhowardarm](https://github.com/paulhowardarm)) - Add generate random support into TPM provider [\#595](https://github.com/parallaxsecond/parsec/pull/595) ([anta5010](https://github.com/anta5010)) ## [1.0.0](https://github.com/parallaxsecond/parsec/tree/1.0.0) (2022-03-30) [Full Changelog](https://github.com/parallaxsecond/parsec/compare/1.0.0-rc3...1.0.0) **Security fixes:** - RUSTSEC-2022-0013 [\#587](https://github.com/parallaxsecond/parsec/issues/587) **Merged pull requests:** - Update Changelog file to include 1.0.0 [\#596](https://github.com/parallaxsecond/parsec/pull/596) ([ionut-arm](https://github.com/ionut-arm)) ## [1.0.0-rc3](https://github.com/parallaxsecond/parsec/tree/1.0.0-rc3) (2022-03-21) [Full Changelog](https://github.com/parallaxsecond/parsec/compare/1.0.0-rc2...1.0.0-rc3) **Fixed bugs:** - Cargo audit failing [\#544](https://github.com/parallaxsecond/parsec/issues/544) **Merged pull requests:** - Prepare for Release Candidate 3 [\#592](https://github.com/parallaxsecond/parsec/pull/592) ([ionut-arm](https://github.com/ionut-arm)) ## [1.0.0-rc2](https://github.com/parallaxsecond/parsec/tree/1.0.0-rc2) (2022-03-02) [Full Changelog](https://github.com/parallaxsecond/parsec/compare/1.0.0-rc1...1.0.0-rc2) **Implemented enhancements:** - Updates for Release Candidate 2 [\#584](https://github.com/parallaxsecond/parsec/pull/584) ([ionut-arm](https://github.com/ionut-arm)) **Closed issues:** - Update the Parsec Book to include SQLiteKeyInfoManager [\#532](https://github.com/parallaxsecond/parsec/issues/532) ## [1.0.0-rc1](https://github.com/parallaxsecond/parsec/tree/1.0.0-rc1) (2022-02-16) [Full Changelog](https://github.com/parallaxsecond/parsec/compare/0.8.1...1.0.0-rc1) **Implemented enhancements:** - `parsec.service` hardening [\#569](https://github.com/parallaxsecond/parsec/issues/569) - Implement `CryptoCanDo` for the Trusted Services and Mbed Crypto providers [\#543](https://github.com/parallaxsecond/parsec/issues/543) - Implement CryptoCanDo for TPM provider [\#542](https://github.com/parallaxsecond/parsec/issues/542) - Refactor the PKCS11 CryptoCanDo implementation [\#541](https://github.com/parallaxsecond/parsec/issues/541) - Implement ActivateCredential key attestation [\#539](https://github.com/parallaxsecond/parsec/issues/539) - Making the SQLiteKIM the default [\#531](https://github.com/parallaxsecond/parsec/issues/531) - Create a new KeyInfoManager based on SQLite [\#424](https://github.com/parallaxsecond/parsec/issues/424) - Add support for other cryptographic services in the Trusted Service provider [\#341](https://github.com/parallaxsecond/parsec/issues/341) - Add system emulation tests for TS provider [\#304](https://github.com/parallaxsecond/parsec/issues/304) - Add support for importing ECC public key in the TPM provider [\#170](https://github.com/parallaxsecond/parsec/issues/170) - Add asymmetric encryption to TS provider [\#580](https://github.com/parallaxsecond/parsec/pull/580) ([ionut-arm](https://github.com/ionut-arm)) - Change dependency revision for TSS crate [\#579](https://github.com/parallaxsecond/parsec/pull/579) ([ionut-arm](https://github.com/ionut-arm)) - Add systemd hardening options [\#572](https://github.com/parallaxsecond/parsec/pull/572) ([ionut-arm](https://github.com/ionut-arm)) - Make SQLite KIM default [\#570](https://github.com/parallaxsecond/parsec/pull/570) ([ionut-arm](https://github.com/ionut-arm)) - Feature sqlite kim [\#566](https://github.com/parallaxsecond/parsec/pull/566) ([ionut-arm](https://github.com/ionut-arm)) - Add error handling to ActivateCredential [\#562](https://github.com/parallaxsecond/parsec/pull/562) ([ionut-arm](https://github.com/ionut-arm)) - Add ActivateCredential tests and fixes [\#560](https://github.com/parallaxsecond/parsec/pull/560) ([ionut-arm](https://github.com/ionut-arm)) - Activate credential [\#558](https://github.com/parallaxsecond/parsec/pull/558) ([ionut-arm](https://github.com/ionut-arm)) - Expand support for importing public keys for TPM [\#540](https://github.com/parallaxsecond/parsec/pull/540) ([ionut-arm](https://github.com/ionut-arm)) - \[CryptoAuthLib provider\] PsaAeadEncrypt and PsaAeadDecrypt implemented [\#536](https://github.com/parallaxsecond/parsec/pull/536) ([TomaszPawelecGL](https://github.com/TomaszPawelecGL)) **Fixed bugs:** - Disable test from old E2E suite [\#574](https://github.com/parallaxsecond/parsec/issues/574) - Errors in validating ECC key bits in PKCS11 provider [\#545](https://github.com/parallaxsecond/parsec/issues/545) - UnixDomainSocket connection returns error from server [\#528](https://github.com/parallaxsecond/parsec/issues/528) - Fuzz Testing & Nightly Cargo udeps are failing due to prost-derive [\#514](https://github.com/parallaxsecond/parsec/issues/514) - TPM Provider does not persist generated keys accross reboot [\#504](https://github.com/parallaxsecond/parsec/issues/504) - Issue with PKCS11 backend with Nitrokey HSM [\#380](https://github.com/parallaxsecond/parsec/issues/380) - Skip flakey test [\#577](https://github.com/parallaxsecond/parsec/pull/577) ([ionut-arm](https://github.com/ionut-arm)) - Fix codecov build [\#573](https://github.com/parallaxsecond/parsec/pull/573) ([ionut-arm](https://github.com/ionut-arm)) - Fix handling of `bits` in PKCS11 imports [\#546](https://github.com/parallaxsecond/parsec/pull/546) ([ionut-arm](https://github.com/ionut-arm)) **Closed issues:** - Align with stable TSS crate [\#567](https://github.com/parallaxsecond/parsec/issues/567) - Stable 0.8.1 release depends on tss-esapi alpha [\#527](https://github.com/parallaxsecond/parsec/issues/527) - Create E2E tests for SQLite KIM [\#516](https://github.com/parallaxsecond/parsec/issues/516) - Switch to dynamic key names in tests [\#453](https://github.com/parallaxsecond/parsec/issues/453) - Add capabilities discovery operations [\#426](https://github.com/parallaxsecond/parsec/issues/426) **Merged pull requests:** - Update Changelog and service version no. [\#583](https://github.com/parallaxsecond/parsec/pull/583) ([ionut-arm](https://github.com/ionut-arm)) - Bump bindgen dependency version [\#582](https://github.com/parallaxsecond/parsec/pull/582) ([ionut-arm](https://github.com/ionut-arm)) - Bump SQLite dependency [\#581](https://github.com/parallaxsecond/parsec/pull/581) ([ionut-arm](https://github.com/ionut-arm)) - \[CryptoAuthLib provider\] PsaRawKeyAgreement operation implementation [\#578](https://github.com/parallaxsecond/parsec/pull/578) ([akazimierskigl](https://github.com/akazimierskigl)) - Implement can-do-crypto for TS and mbed-crypto providers [\#565](https://github.com/parallaxsecond/parsec/pull/565) ([anta5010](https://github.com/anta5010)) - Add error message if submodule not initialised [\#564](https://github.com/parallaxsecond/parsec/pull/564) ([ionut-arm](https://github.com/ionut-arm)) - \[CryptoAuthLib provider\] PsaCipherEncrypt and PsaCipherDecrypt implementation [\#563](https://github.com/parallaxsecond/parsec/pull/563) ([akazimierskigl](https://github.com/akazimierskigl)) - Add clippy and fmt checkt to e2e\_tests [\#561](https://github.com/parallaxsecond/parsec/pull/561) ([ionut-arm](https://github.com/ionut-arm)) - Re-factor e2e tests to use common key attributes functions [\#556](https://github.com/parallaxsecond/parsec/pull/556) ([anta5010](https://github.com/anta5010)) - Merge can-do-crypto branch into main [\#555](https://github.com/parallaxsecond/parsec/pull/555) ([anta5010](https://github.com/anta5010)) - Merge main branch changes into can-do crypto [\#554](https://github.com/parallaxsecond/parsec/pull/554) ([anta5010](https://github.com/anta5010)) - Jn9e9/issue453 [\#552](https://github.com/parallaxsecond/parsec/pull/552) ([jn9e9](https://github.com/jn9e9)) - e2e CanDoCrypto tests for Hashes, ECC curves and Crypto algorithms [\#551](https://github.com/parallaxsecond/parsec/pull/551) ([anta5010](https://github.com/anta5010)) - Implement CanDoCrypto trait and use it for PKCS11 and TPM providers [\#550](https://github.com/parallaxsecond/parsec/pull/550) ([anta5010](https://github.com/anta5010)) - Use ec\_params for can-do-crypto checks instead of hard-coded values [\#549](https://github.com/parallaxsecond/parsec/pull/549) ([anta5010](https://github.com/anta5010)) - Small refactor of PKCS11 CryptoCanDo [\#548](https://github.com/parallaxsecond/parsec/pull/548) ([anta5010](https://github.com/anta5010)) - Merge origin/main into can-do-crypto [\#547](https://github.com/parallaxsecond/parsec/pull/547) ([anta5010](https://github.com/anta5010)) - Increase the MSRV to 1.53.0 [\#535](https://github.com/parallaxsecond/parsec/pull/535) ([hug-dev](https://github.com/hug-dev)) - Update the CHANGELOG file with 0.8.1 [\#533](https://github.com/parallaxsecond/parsec/pull/533) ([hug-dev](https://github.com/hug-dev)) - Added the CanDoCrypto operation as well as fixing some of the other test scripts. [\#522](https://github.com/parallaxsecond/parsec/pull/522) ([Kakemone](https://github.com/Kakemone)) ## [0.8.1](https://github.com/parallaxsecond/parsec/tree/0.8.1) (2021-09-17) [Full Changelog](https://github.com/parallaxsecond/parsec/compare/0.8.0...0.8.1) **Implemented enhancements:** - Add Unit Tests to SQLiteKeyInfoManager [\#510](https://github.com/parallaxsecond/parsec/issues/510) - Change KeyTriple to Include Auth ID, Provider Name & Provider UUID [\#488](https://github.com/parallaxsecond/parsec/issues/488) - Update provider to use new version fo TransKeyCtx [\#515](https://github.com/parallaxsecond/parsec/pull/515) ([ionut-arm](https://github.com/ionut-arm)) **Fixed bugs:** - Decide and implement a new serialization format for KeyInfo [\#509](https://github.com/parallaxsecond/parsec/issues/509) - Memory leak in TS context [\#501](https://github.com/parallaxsecond/parsec/issues/501) - Disable broken workflows [\#525](https://github.com/parallaxsecond/parsec/pull/525) ([ionut-arm](https://github.com/ionut-arm)) **Closed issues:** - Make a Parsec Ockam Vault: investigation issue [\#506](https://github.com/parallaxsecond/parsec/issues/506) - Add Basic SQLiteKeyInfoManager Storage/Retrieval Functionality [\#503](https://github.com/parallaxsecond/parsec/issues/503) - Add config tests for multiple provider names [\#496](https://github.com/parallaxsecond/parsec/issues/496) **Merged pull requests:** - Bump version for release [\#526](https://github.com/parallaxsecond/parsec/pull/526) ([ionut-arm](https://github.com/ionut-arm)) - Use as\_ptr for TS service name [\#524](https://github.com/parallaxsecond/parsec/pull/524) ([anta5010](https://github.com/anta5010)) - Lower Hash algorithm [\#499](https://github.com/parallaxsecond/parsec/pull/499) ([hug-dev](https://github.com/hug-dev)) - Update CHANGELOG [\#498](https://github.com/parallaxsecond/parsec/pull/498) ([hug-dev](https://github.com/hug-dev)) ## [0.8.0](https://github.com/parallaxsecond/parsec/tree/0.8.0) (2021-08-05) [Full Changelog](https://github.com/parallaxsecond/parsec/compare/0.7.2...0.8.0) **Implemented enhancements:** - Add Provider Name Config Option [\#487](https://github.com/parallaxsecond/parsec/issues/487) - Add PKCS11 provider export-attributes switch [\#462](https://github.com/parallaxsecond/parsec/issues/462) - Refactor the all-providers workflow [\#455](https://github.com/parallaxsecond/parsec/issues/455) - Adjust linking for TS provider [\#427](https://github.com/parallaxsecond/parsec/issues/427) - Allow providers to be optional or conditional depending on platform feature availability [\#401](https://github.com/parallaxsecond/parsec/issues/401) - Add cross-compilation tests for the TPM provider [\#382](https://github.com/parallaxsecond/parsec/issues/382) - Make the slot\_number field optional [\#375](https://github.com/parallaxsecond/parsec/issues/375) - Design workflow and associated APIs for key attestation in Parsec [\#370](https://github.com/parallaxsecond/parsec/issues/370) - Implement error handling for TS caller errors [\#332](https://github.com/parallaxsecond/parsec/issues/332) - Add release-build tests to CI [\#163](https://github.com/parallaxsecond/parsec/issues/163) - Add the possibility of changing key store location of Mbed Crypto provider [\#53](https://github.com/parallaxsecond/parsec/issues/53) - Add TS provider to all-providers [\#482](https://github.com/parallaxsecond/parsec/pull/482) ([ionut-arm](https://github.com/ionut-arm)) - Adjust TS provider linking [\#474](https://github.com/parallaxsecond/parsec/pull/474) ([ionut-arm](https://github.com/ionut-arm)) - Add cargo-audit config [\#473](https://github.com/parallaxsecond/parsec/pull/473) ([ionut-arm](https://github.com/ionut-arm)) - Update dependency on Trusted Services [\#467](https://github.com/parallaxsecond/parsec/pull/467) ([ionut-arm](https://github.com/ionut-arm)) - Add import and export support for ECC for PKCS11 [\#452](https://github.com/parallaxsecond/parsec/pull/452) ([ionut-arm](https://github.com/ionut-arm)) - Add a SPIFFE based authenticator [\#449](https://github.com/parallaxsecond/parsec/pull/449) ([hug-dev](https://github.com/hug-dev)) - Add ECC functionality to PKCS11 prov [\#446](https://github.com/parallaxsecond/parsec/pull/446) ([ionut-arm](https://github.com/ionut-arm)) - Enable coverage testing for TS provider [\#434](https://github.com/parallaxsecond/parsec/pull/434) ([ionut-arm](https://github.com/ionut-arm)) - Create SECURITY.md [\#414](https://github.com/parallaxsecond/parsec/pull/414) ([ionut-arm](https://github.com/ionut-arm)) - Add TPM provider cross-compilation [\#403](https://github.com/parallaxsecond/parsec/pull/403) ([ionut-arm](https://github.com/ionut-arm)) - Added Option\ to PKCS 11 Provider constructor [\#402](https://github.com/parallaxsecond/parsec/pull/402) ([Sven-bg](https://github.com/Sven-bg)) **Fixed bugs:** - If a response is an error, log it before sending it [\#417](https://github.com/parallaxsecond/parsec/issues/417) - Fix ingress/egress trace logs [\#416](https://github.com/parallaxsecond/parsec/issues/416) - Make `KeyInfo` a private type [\#400](https://github.com/parallaxsecond/parsec/issues/400) - Unable to build 0.7.2 for i686 \(and ppc64/ppc64le\) [\#379](https://github.com/parallaxsecond/parsec/issues/379) - Unable to build 0.7.2 for armv7 [\#378](https://github.com/parallaxsecond/parsec/issues/378) - Document clearly how Mbed Crypto provider keys are stored [\#373](https://github.com/parallaxsecond/parsec/issues/373) - Fix code coverage reports [\#495](https://github.com/parallaxsecond/parsec/pull/495) ([ionut-arm](https://github.com/ionut-arm)) - Modify the git submodule command [\#490](https://github.com/parallaxsecond/parsec/pull/490) ([hug-dev](https://github.com/hug-dev)) - Do not login if no user pin was entered [\#489](https://github.com/parallaxsecond/parsec/pull/489) ([hug-dev](https://github.com/hug-dev)) - Fix git command and use Arm machine [\#485](https://github.com/parallaxsecond/parsec/pull/485) ([ionut-arm](https://github.com/ionut-arm)) - Fix CircleCI config format. [\#484](https://github.com/parallaxsecond/parsec/pull/484) ([ionut-arm](https://github.com/ionut-arm)) - Add submodule initialisation to CircleCI [\#483](https://github.com/parallaxsecond/parsec/pull/483) ([ionut-arm](https://github.com/ionut-arm)) - Make cross-compilation run on release version [\#454](https://github.com/parallaxsecond/parsec/pull/454) ([ionut-arm](https://github.com/ionut-arm)) - Bump picky crate versions [\#443](https://github.com/parallaxsecond/parsec/pull/443) ([ionut-arm](https://github.com/ionut-arm)) - Remove the TS coverage computation [\#436](https://github.com/parallaxsecond/parsec/pull/436) ([ionut-arm](https://github.com/ionut-arm)) - Fix nightly workflow [\#435](https://github.com/parallaxsecond/parsec/pull/435) ([ionut-arm](https://github.com/ionut-arm)) - Fix ServiceConfig import in fuzz\_service [\#433](https://github.com/parallaxsecond/parsec/pull/433) ([ionut-arm](https://github.com/ionut-arm)) - Fix Contributing link [\#415](https://github.com/parallaxsecond/parsec/pull/415) ([ionut-arm](https://github.com/ionut-arm)) - Fix ownership of ibmtpm folder [\#385](https://github.com/parallaxsecond/parsec/pull/385) ([ionut-arm](https://github.com/ionut-arm)) - Fix CircleCI config [\#384](https://github.com/parallaxsecond/parsec/pull/384) ([ionut-arm](https://github.com/ionut-arm)) - Implement a few fixes [\#374](https://github.com/parallaxsecond/parsec/pull/374) ([ionut-arm](https://github.com/ionut-arm)) **Security fixes:** - Resurrect fuzz testing framework [\#422](https://github.com/parallaxsecond/parsec/issues/422) - Set up Github security policy [\#398](https://github.com/parallaxsecond/parsec/issues/398) - Investigate testing of Cryptoauthlib provider [\#315](https://github.com/parallaxsecond/parsec/issues/315) - rust-spiffe: make sure that the claims returned by the validation operation are as expected [\#290](https://github.com/parallaxsecond/parsec/issues/290) - rust-spiffe: provide a local validation of the JWT-SVID [\#289](https://github.com/parallaxsecond/parsec/issues/289) - Revive the fuzz testing framework [\#429](https://github.com/parallaxsecond/parsec/pull/429) ([ionut-arm](https://github.com/ionut-arm)) **Closed issues:** - NXP PKCS\#11 Parsec integration testing. [\#456](https://github.com/parallaxsecond/parsec/issues/456) - Split the build tests on a different CI workflow [\#447](https://github.com/parallaxsecond/parsec/issues/447) - Support ECC signing keys in the PKCS\#11 provider [\#421](https://github.com/parallaxsecond/parsec/issues/421) - Stability: Communication with backends [\#412](https://github.com/parallaxsecond/parsec/issues/412) - Adopt CII Best Practices Badge from the LF [\#411](https://github.com/parallaxsecond/parsec/issues/411) - Unable to build parsec 0.7.2 with rust 1.43.1. Parsec 0.6.0 builds fine. [\#409](https://github.com/parallaxsecond/parsec/issues/409) - Stability: Build toolchain [\#408](https://github.com/parallaxsecond/parsec/issues/408) - Stability: Environment variables [\#405](https://github.com/parallaxsecond/parsec/issues/405) - Stability: Dynamic libraries dependencies [\#397](https://github.com/parallaxsecond/parsec/issues/397) - Stability: systemd communication [\#396](https://github.com/parallaxsecond/parsec/issues/396) - Stability: OS signals [\#395](https://github.com/parallaxsecond/parsec/issues/395) - Stability: Persistent state \(key mappings\) [\#394](https://github.com/parallaxsecond/parsec/issues/394) - Stability: Configuration file [\#393](https://github.com/parallaxsecond/parsec/issues/393) - Stability: CLI invocation [\#392](https://github.com/parallaxsecond/parsec/issues/392) - Stability: Authenticators [\#391](https://github.com/parallaxsecond/parsec/issues/391) - Stability: Communication with clients \(listeners endpoint\) [\#390](https://github.com/parallaxsecond/parsec/issues/390) - Stability: Communication with clients \(operation contracts\) [\#389](https://github.com/parallaxsecond/parsec/issues/389) - Stability: Communication with clients \(requests/responses\) [\#388](https://github.com/parallaxsecond/parsec/issues/388) - Setup environment stability test [\#386](https://github.com/parallaxsecond/parsec/issues/386) - Archive for 0.7.0 contains .cargo/ folder [\#377](https://github.com/parallaxsecond/parsec/issues/377) - Add more Fixed Common header tests [\#351](https://github.com/parallaxsecond/parsec/issues/351) **Merged pull requests:** - Switch imports to crates.io [\#497](https://github.com/parallaxsecond/parsec/pull/497) ([ionut-arm](https://github.com/ionut-arm)) - Add the Class attribute when generating key pairs [\#493](https://github.com/parallaxsecond/parsec/pull/493) ([hug-dev](https://github.com/hug-dev)) - Add tests checking absence of slot\_number [\#492](https://github.com/parallaxsecond/parsec/pull/492) ([hug-dev](https://github.com/hug-dev)) - Split out the all-providers cargo check into its own CI job. [\#472](https://github.com/parallaxsecond/parsec/pull/472) ([MattDavis00](https://github.com/MattDavis00)) - Make KeyInfo a private type Fix \#400 [\#469](https://github.com/parallaxsecond/parsec/pull/469) ([Kakemone](https://github.com/Kakemone)) - Added psa\_export\_key & psa\_generate\_random to TS Provider [\#468](https://github.com/parallaxsecond/parsec/pull/468) ([MattDavis00](https://github.com/MattDavis00)) - Add a allow\_export flag to restrict exporting [\#466](https://github.com/parallaxsecond/parsec/pull/466) ([hug-dev](https://github.com/hug-dev)) - Added missing ingress logs to providers. \#416 [\#465](https://github.com/parallaxsecond/parsec/pull/465) ([MattDavis00](https://github.com/MattDavis00)) - \#417 Added additional error logging to front end handle\_request function. [\#464](https://github.com/parallaxsecond/parsec/pull/464) ([MattDavis00](https://github.com/MattDavis00)) - Update the TS revision used [\#461](https://github.com/parallaxsecond/parsec/pull/461) ([ionut-arm](https://github.com/ionut-arm)) - Add a way to allow providers to fail instantiation [\#451](https://github.com/parallaxsecond/parsec/pull/451) ([hug-dev](https://github.com/hug-dev)) - Randomly select the shutdown signal [\#448](https://github.com/parallaxsecond/parsec/pull/448) ([hug-dev](https://github.com/hug-dev)) - Execute e2e tests with an old version of client [\#445](https://github.com/parallaxsecond/parsec/pull/445) ([hug-dev](https://github.com/hug-dev)) - \[CryptoAuthLib provider\] Implementation of export key operation [\#442](https://github.com/parallaxsecond/parsec/pull/442) ([TomaszPawelecGL](https://github.com/TomaszPawelecGL)) - Move CLI log into its own file [\#441](https://github.com/parallaxsecond/parsec/pull/441) ([hug-dev](https://github.com/hug-dev)) - Add various tests checking contracts [\#440](https://github.com/parallaxsecond/parsec/pull/440) ([hug-dev](https://github.com/hug-dev)) - Isolate config logic and add e2e config tests [\#432](https://github.com/parallaxsecond/parsec/pull/432) ([hug-dev](https://github.com/hug-dev)) - \[CryptoAuthLib provider\] Implementation of psa\_export\_public\_key operation. [\#431](https://github.com/parallaxsecond/parsec/pull/431) ([RobertDrazkowskiGL](https://github.com/RobertDrazkowskiGL)) - \[CryptoAuthLib provider\] Support for psa\_sign\_message and psa\_verify\_message. [\#425](https://github.com/parallaxsecond/parsec/pull/425) ([RobertDrazkowskiGL](https://github.com/RobertDrazkowskiGL)) - Replace persistence tests with key mappings tests [\#420](https://github.com/parallaxsecond/parsec/pull/420) ([hug-dev](https://github.com/hug-dev)) - Add Codecov and cii badges [\#419](https://github.com/parallaxsecond/parsec/pull/419) ([ionut-arm](https://github.com/ionut-arm)) - CryptoAuthentication Library provider - support for PsaSignHash and PsaVerifyHash operations. [\#413](https://github.com/parallaxsecond/parsec/pull/413) ([RobertDrazkowskiGL](https://github.com/RobertDrazkowskiGL)) - Make it compile for Rust 1.43.1 [\#410](https://github.com/parallaxsecond/parsec/pull/410) ([hug-dev](https://github.com/hug-dev)) - PSA\_IMPORT\_KEY introduction. [\#399](https://github.com/parallaxsecond/parsec/pull/399) ([RobertDrazkowskiGL](https://github.com/RobertDrazkowskiGL)) - CryptoAuthLib provider testability improvements: [\#387](https://github.com/parallaxsecond/parsec/pull/387) ([RobertDrazkowskiGL](https://github.com/RobertDrazkowskiGL)) - Add CircleCI config [\#383](https://github.com/parallaxsecond/parsec/pull/383) ([ionut-arm](https://github.com/ionut-arm)) - Import newest versions of cryptoki and tss-esapi [\#381](https://github.com/parallaxsecond/parsec/pull/381) ([hug-dev](https://github.com/hug-dev)) - Update CHANGELOG [\#367](https://github.com/parallaxsecond/parsec/pull/367) ([hug-dev](https://github.com/hug-dev)) - Implementation of PsaGenerateKey and PsaDestroyKey operations [\#354](https://github.com/parallaxsecond/parsec/pull/354) ([RobertDrazkowskiGL](https://github.com/RobertDrazkowskiGL)) ## [0.7.2](https://github.com/parallaxsecond/parsec/tree/0.7.2) (2021-03-25) [Full Changelog](https://github.com/parallaxsecond/parsec/compare/0.7.1...0.7.2) **Merged pull requests:** - Prepare for 0.7.2 [\#368](https://github.com/parallaxsecond/parsec/pull/368) ([hug-dev](https://github.com/hug-dev)) ## [0.7.1](https://github.com/parallaxsecond/parsec/tree/0.7.1) (2021-03-25) [Full Changelog](https://github.com/parallaxsecond/parsec/compare/0.7.0...0.7.1) **Closed issues:** - Investigate calculating test coverage [\#342](https://github.com/parallaxsecond/parsec/issues/342) **Merged pull requests:** - Update tss-esapi dependency [\#366](https://github.com/parallaxsecond/parsec/pull/366) ([hug-dev](https://github.com/hug-dev)) - Add quickstart reference [\#365](https://github.com/parallaxsecond/parsec/pull/365) ([hug-dev](https://github.com/hug-dev)) - Update CHANGELOG [\#364](https://github.com/parallaxsecond/parsec/pull/364) ([hug-dev](https://github.com/hug-dev)) ## [0.7.0](https://github.com/parallaxsecond/parsec/tree/0.7.0) (2021-03-23) [Full Changelog](https://github.com/parallaxsecond/parsec/compare/0.6.0...0.7.0) **Implemented enhancements:** - Stop the duplication of key ID conversions [\#331](https://github.com/parallaxsecond/parsec/issues/331) - Add key management operations support [\#267](https://github.com/parallaxsecond/parsec/issues/267) - Enable TS context initialization [\#266](https://github.com/parallaxsecond/parsec/issues/266) - Create the Trusted Service bindings [\#265](https://github.com/parallaxsecond/parsec/issues/265) - Improve import key support in TPM provider [\#251](https://github.com/parallaxsecond/parsec/issues/251) - Investigate and define the work required for SPIFFE-based client identity management [\#232](https://github.com/parallaxsecond/parsec/issues/232) - Make existence of key info consistent with existence of key [\#149](https://github.com/parallaxsecond/parsec/issues/149) - Extract Docker images into own repo [\#124](https://github.com/parallaxsecond/parsec/issues/124) - Add version structures for better handling of versions [\#43](https://github.com/parallaxsecond/parsec/issues/43) - Rearrange modules for a more structured feel [\#32](https://github.com/parallaxsecond/parsec/issues/32) - Change CI to use published Docker image [\#357](https://github.com/parallaxsecond/parsec/pull/357) ([ionut-arm](https://github.com/ionut-arm)) - Improve coverage script [\#348](https://github.com/parallaxsecond/parsec/pull/348) ([ionut-arm](https://github.com/ionut-arm)) - Add coverage checking in nightly run [\#347](https://github.com/parallaxsecond/parsec/pull/347) ([ionut-arm](https://github.com/ionut-arm)) - Trusted service provider [\#330](https://github.com/parallaxsecond/parsec/pull/330) ([ionut-arm](https://github.com/ionut-arm)) - Add admin configuration [\#316](https://github.com/parallaxsecond/parsec/pull/316) ([ionut-arm](https://github.com/ionut-arm)) - Add new parsec provider using ATECCx08 cryptochip via CryptoAuthentication Library [\#303](https://github.com/parallaxsecond/parsec/pull/303) ([RobertDrazkowskiGL](https://github.com/RobertDrazkowskiGL)) - Improve error handling in builder [\#298](https://github.com/parallaxsecond/parsec/pull/298) ([ionut-arm](https://github.com/ionut-arm)) - Add Changelog file \(\#278\) [\#280](https://github.com/parallaxsecond/parsec/pull/280) ([ionut-arm](https://github.com/ionut-arm)) - Remove PKCS11 single thread lock \(\#264\) [\#277](https://github.com/parallaxsecond/parsec/pull/277) ([ionut-arm](https://github.com/ionut-arm)) **Fixed bugs:** - Move the spiffe related features in its own branch [\#327](https://github.com/parallaxsecond/parsec/issues/327) - Resolve default implementation issue for `list_keys` in `Provide` [\#312](https://github.com/parallaxsecond/parsec/issues/312) - ListKeys should only be callable on the Core provider [\#310](https://github.com/parallaxsecond/parsec/issues/310) - Service should not start if some components weren't built successfully [\#297](https://github.com/parallaxsecond/parsec/issues/297) - No changelog for the releases [\#278](https://github.com/parallaxsecond/parsec/issues/278) - PKCS11 multi-threading [\#264](https://github.com/parallaxsecond/parsec/issues/264) - Fix ImportKey to allow importing private key [\#126](https://github.com/parallaxsecond/parsec/issues/126) - PKCS 11 provider stress tests sometimes fail [\#116](https://github.com/parallaxsecond/parsec/issues/116) - Update docker registry for TPM2 images [\#356](https://github.com/parallaxsecond/parsec/pull/356) ([ionut-arm](https://github.com/ionut-arm)) - Run the Codecov script outside container [\#353](https://github.com/parallaxsecond/parsec/pull/353) ([ionut-arm](https://github.com/ionut-arm)) - Fix code coverage docker command [\#352](https://github.com/parallaxsecond/parsec/pull/352) ([ionut-arm](https://github.com/ionut-arm)) - Remove the spiffe-based authenticator [\#328](https://github.com/parallaxsecond/parsec/pull/328) ([hug-dev](https://github.com/hug-dev)) **Security fixes:** - Add a test for admin operations [\#309](https://github.com/parallaxsecond/parsec/issues/309) - Implement admin logic [\#308](https://github.com/parallaxsecond/parsec/issues/308) - Investigate admin role and admin-level operations [\#292](https://github.com/parallaxsecond/parsec/issues/292) - Add failure-counter mechanism [\#176](https://github.com/parallaxsecond/parsec/issues/176) **Closed issues:** - Implement ListClients and DeleteClient in the core provider [\#311](https://github.com/parallaxsecond/parsec/issues/311) - Correct lint issues found after the toolchain upgrade to version 1.49.0 [\#305](https://github.com/parallaxsecond/parsec/issues/305) - Investigate cross-compilation to Linux on Aarch64 [\#300](https://github.com/parallaxsecond/parsec/issues/300) - Investigate adding ListClients and DeleteClient operations [\#293](https://github.com/parallaxsecond/parsec/issues/293) - Consume the new, safer Rust PKCS\#11 interface into Parsec when it is available [\#272](https://github.com/parallaxsecond/parsec/issues/272) - Add a SPIFFE JWT-SVID multitenancy test [\#269](https://github.com/parallaxsecond/parsec/issues/269) - Add a JWT-SVID Authenticator [\#268](https://github.com/parallaxsecond/parsec/issues/268) - Investigate and define the work required for compatibility with Arm Firmware Framework for Armv8-A \(FF-A\) [\#247](https://github.com/parallaxsecond/parsec/issues/247) **Merged pull requests:** - Prepare for 0.7.0 release [\#363](https://github.com/parallaxsecond/parsec/pull/363) ([hug-dev](https://github.com/hug-dev)) - Update to latest TSS crate version [\#362](https://github.com/parallaxsecond/parsec/pull/362) ([ionut-arm](https://github.com/ionut-arm)) - Enable code coverage for PKCS11, disable for TS [\#361](https://github.com/parallaxsecond/parsec/pull/361) ([ionut-arm](https://github.com/ionut-arm)) - Add Edmund to Contributors list [\#359](https://github.com/parallaxsecond/parsec/pull/359) ([ionut-arm](https://github.com/ionut-arm)) - Add myself to contributors, re. rust-cryptoki [\#358](https://github.com/parallaxsecond/parsec/pull/358) ([nickray](https://github.com/nickray)) - Add some cross-compilation tests [\#355](https://github.com/parallaxsecond/parsec/pull/355) ([hug-dev](https://github.com/hug-dev)) - Upgrade all dependencies to their latest version [\#345](https://github.com/parallaxsecond/parsec/pull/345) ([hug-dev](https://github.com/hug-dev)) - Create KeyInfoManagerClient [\#343](https://github.com/parallaxsecond/parsec/pull/343) ([ionut-arm](https://github.com/ionut-arm)) - Parsec PsaHashCompare operation implementation for CryptoAuthLib provider [\#333](https://github.com/parallaxsecond/parsec/pull/333) ([akazimierskigl](https://github.com/akazimierskigl)) - Parsec PsaGenerateRandom operation implementation for CryptoAuthLib provider [\#325](https://github.com/parallaxsecond/parsec/pull/325) ([RobertDrazkowskiGL](https://github.com/RobertDrazkowskiGL)) - Add consistency in key creation/deletion [\#324](https://github.com/parallaxsecond/parsec/pull/324) ([hug-dev](https://github.com/hug-dev)) - Make the authenticators their own features [\#322](https://github.com/parallaxsecond/parsec/pull/322) ([puiterwijk](https://github.com/puiterwijk)) - Improve mandatory Provide methods [\#321](https://github.com/parallaxsecond/parsec/pull/321) ([ionut-arm](https://github.com/ionut-arm)) - Use newest TSS crate [\#320](https://github.com/parallaxsecond/parsec/pull/320) ([ionut-arm](https://github.com/ionut-arm)) - Add ListClients and DeleteClient operations [\#318](https://github.com/parallaxsecond/parsec/pull/318) ([hug-dev](https://github.com/hug-dev)) - Added support for PsaHashCompute to CryptoAuthLib provider. [\#317](https://github.com/parallaxsecond/parsec/pull/317) ([RobertDrazkowskiGL](https://github.com/RobertDrazkowskiGL)) - Update service dependencies [\#314](https://github.com/parallaxsecond/parsec/pull/314) ([ionut-arm](https://github.com/ionut-arm)) - Add a test checking ListKeys provider target [\#313](https://github.com/parallaxsecond/parsec/pull/313) ([hug-dev](https://github.com/hug-dev)) - Fix lint warning [\#306](https://github.com/parallaxsecond/parsec/pull/306) ([ionut-arm](https://github.com/ionut-arm)) - Return correct key provider id in list\_keys [\#302](https://github.com/parallaxsecond/parsec/pull/302) ([jn9e9](https://github.com/jn9e9)) - Use the new abstraction on the PKCS11 interface [\#301](https://github.com/parallaxsecond/parsec/pull/301) ([hug-dev](https://github.com/hug-dev)) - Switch Travis CI build to cron-only [\#299](https://github.com/parallaxsecond/parsec/pull/299) ([ionut-arm](https://github.com/ionut-arm)) - Add a JWT-SVID authenticator [\#283](https://github.com/parallaxsecond/parsec/pull/283) ([hug-dev](https://github.com/hug-dev)) - Add Patrick to the contributor list [\#281](https://github.com/parallaxsecond/parsec/pull/281) ([puiterwijk](https://github.com/puiterwijk)) ## [0.6.0](https://github.com/parallaxsecond/parsec/tree/0.6.0) (2020-10-20) [Full Changelog](https://github.com/parallaxsecond/parsec/compare/0.5.0...0.6.0) **Implemented enhancements:** - Add multitenancy testing infrastructure 👩‍🔧 [\#245](https://github.com/parallaxsecond/parsec/issues/245) - Delete "Provider" suffix out of provider names [\#134](https://github.com/parallaxsecond/parsec/issues/134) - Improve error message on service startup [\#260](https://github.com/parallaxsecond/parsec/pull/260) ([ionut-arm](https://github.com/ionut-arm)) **Fixed bugs:** - Limit key imports in TPM provider [\#255](https://github.com/parallaxsecond/parsec/pull/255) ([ionut-arm](https://github.com/ionut-arm)) **Closed issues:** - Add authenticator configuration [\#270](https://github.com/parallaxsecond/parsec/issues/270) - Assemble a PR checklist for code reviewers [\#258](https://github.com/parallaxsecond/parsec/issues/258) - Adjust README disclaimer wording [\#231](https://github.com/parallaxsecond/parsec/issues/231) **Merged pull requests:** - Add multitenancy tests [\#276](https://github.com/parallaxsecond/parsec/pull/276) ([hug-dev](https://github.com/hug-dev)) - Put config tests in all\_providers [\#275](https://github.com/parallaxsecond/parsec/pull/275) ([hug-dev](https://github.com/hug-dev)) - Remove warnings about parsec and parsec-clients [\#274](https://github.com/parallaxsecond/parsec/pull/274) ([hug-dev](https://github.com/hug-dev)) - Add authentication configuration [\#273](https://github.com/parallaxsecond/parsec/pull/273) ([hug-dev](https://github.com/hug-dev)) - Refactored provider names [\#263](https://github.com/parallaxsecond/parsec/pull/263) ([Swell61](https://github.com/Swell61)) - Add list keys [\#261](https://github.com/parallaxsecond/parsec/pull/261) ([joechrisellis](https://github.com/joechrisellis)) ## [0.5.0](https://github.com/parallaxsecond/parsec/tree/0.5.0) (2020-10-02) [Full Changelog](https://github.com/parallaxsecond/parsec/compare/0.4.0...0.5.0) **Implemented enhancements:** - Creating a build-time configuration file [\#256](https://github.com/parallaxsecond/parsec/issues/256) - Merge integration tests in E2E test suite [\#228](https://github.com/parallaxsecond/parsec/issues/228) - Support dbus-parsec with NXP secureobj library [\#223](https://github.com/parallaxsecond/parsec/issues/223) - Verify which dependencies can/should be updated [\#158](https://github.com/parallaxsecond/parsec/issues/158) - Add more test cases [\#151](https://github.com/parallaxsecond/parsec/issues/151) - Test Parsec installation as a systemd daemon [\#49](https://github.com/parallaxsecond/parsec/issues/49) - Improve E2E testing [\#253](https://github.com/parallaxsecond/parsec/pull/253) ([ionut-arm](https://github.com/ionut-arm)) - Upgrade and clean dependencies [\#246](https://github.com/parallaxsecond/parsec/pull/246) ([hug-dev](https://github.com/hug-dev)) - Import private key support for TPM provider [\#243](https://github.com/parallaxsecond/parsec/pull/243) ([joechrisellis](https://github.com/joechrisellis)) - Allow software operations in PKCS11 provider [\#241](https://github.com/parallaxsecond/parsec/pull/241) ([ionut-arm](https://github.com/ionut-arm)) - Improve key metadata handling [\#240](https://github.com/parallaxsecond/parsec/pull/240) ([ionut-arm](https://github.com/ionut-arm)) - Add support for `psa_generate_random` operation for MbedCrypto provider [\#208](https://github.com/parallaxsecond/parsec/pull/208) ([joechrisellis](https://github.com/joechrisellis)) **Fixed bugs:** - Memory cleanup of sensitive data [\#122](https://github.com/parallaxsecond/parsec/issues/122) - Fix attribute conversion in PKCS11 provider [\#254](https://github.com/parallaxsecond/parsec/pull/254) ([ionut-arm](https://github.com/ionut-arm)) - Fix sign attribute in PKCS11 [\#252](https://github.com/parallaxsecond/parsec/pull/252) ([ionut-arm](https://github.com/ionut-arm)) - Add Uuid from the interface directly [\#242](https://github.com/parallaxsecond/parsec/pull/242) ([hug-dev](https://github.com/hug-dev)) - Add `buffer_size_limit` config option for providers [\#233](https://github.com/parallaxsecond/parsec/pull/233) ([joechrisellis](https://github.com/joechrisellis)) **Security fixes:** - Add memory zeroizing when needed [\#239](https://github.com/parallaxsecond/parsec/pull/239) ([hug-dev](https://github.com/hug-dev)) **Closed issues:** - Implement ListAuthenticators [\#216](https://github.com/parallaxsecond/parsec/issues/216) - Better error message when file not found [\#210](https://github.com/parallaxsecond/parsec/issues/210) - Implement an authenticator based on the domain socket peer credential [\#200](https://github.com/parallaxsecond/parsec/issues/200) **Merged pull requests:** - Add Unix peer credentials authenticator [\#214](https://github.com/parallaxsecond/parsec/pull/214) ([joechrisellis](https://github.com/joechrisellis)) ## [0.4.0](https://github.com/parallaxsecond/parsec/tree/0.4.0) (2020-09-01) [Full Changelog](https://github.com/parallaxsecond/parsec/compare/0.3.0...0.4.0) **Implemented enhancements:** - Implement asymmetric encrypt/decrypt in the PKCS\#11 provider [\#224](https://github.com/parallaxsecond/parsec/issues/224) - Implement asymmetric encrypting/decrypting for TPM provider [\#217](https://github.com/parallaxsecond/parsec/issues/217) - Create a Parsec Command Line Interface Client [\#202](https://github.com/parallaxsecond/parsec/issues/202) - Create a mechanism for the listener to pass system-level data to the authenticator [\#199](https://github.com/parallaxsecond/parsec/issues/199) - Auto create `/tmp/parsec` with correct permissions on startup [\#195](https://github.com/parallaxsecond/parsec/issues/195) - Update attribute handling in PKCS11 provider [\#227](https://github.com/parallaxsecond/parsec/pull/227) ([ionut-arm](https://github.com/ionut-arm)) - Add asymmetric encryption support to TPM provider [\#225](https://github.com/parallaxsecond/parsec/pull/225) ([ionut-arm](https://github.com/ionut-arm)) - Improve error message when config file is not found [\#211](https://github.com/parallaxsecond/parsec/pull/211) ([ionut-arm](https://github.com/ionut-arm)) **Fixed bugs:** - Update Adam Parco email address in maintainers files [\#230](https://github.com/parallaxsecond/parsec/issues/230) - Update email address [\#235](https://github.com/parallaxsecond/parsec/pull/235) ([hug-dev](https://github.com/hug-dev)) - Bugfix: fix off-by-one error \(default body length limit\) [\#234](https://github.com/parallaxsecond/parsec/pull/234) ([joechrisellis](https://github.com/joechrisellis)) - Fix clippy errors [\#206](https://github.com/parallaxsecond/parsec/pull/206) ([ionut-arm](https://github.com/ionut-arm)) **Closed issues:** - Add an option to pass a path to a build-config file [\#174](https://github.com/parallaxsecond/parsec/issues/174) **Merged pull requests:** - Add missing\_docs lint and missing docs [\#236](https://github.com/parallaxsecond/parsec/pull/236) ([hug-dev](https://github.com/hug-dev)) - Added aead encrypt decrypt, hash compute compare and raw key agreement [\#229](https://github.com/parallaxsecond/parsec/pull/229) ([sbailey-arm](https://github.com/sbailey-arm)) - Fix test and enable Travis [\#221](https://github.com/parallaxsecond/parsec/pull/221) ([ionut-arm](https://github.com/ionut-arm)) - Add implementation for ListAuthenticators operation [\#220](https://github.com/parallaxsecond/parsec/pull/220) ([joechrisellis](https://github.com/joechrisellis)) - Add check to prevent the Parsec service from running as root [\#219](https://github.com/parallaxsecond/parsec/pull/219) ([joechrisellis](https://github.com/joechrisellis)) - CoreProvider can query the other providers [\#215](https://github.com/parallaxsecond/parsec/pull/215) ([ionut-arm](https://github.com/ionut-arm)) - Rebase on new tss\_esapi [\#213](https://github.com/parallaxsecond/parsec/pull/213) ([puiterwijk](https://github.com/puiterwijk)) - Add Asymmetric Encrypt/Decrypt to mbed supported opcodes [\#212](https://github.com/parallaxsecond/parsec/pull/212) ([puiterwijk](https://github.com/puiterwijk)) - Create `Connection` abstraction for client communication [\#207](https://github.com/parallaxsecond/parsec/pull/207) ([joechrisellis](https://github.com/joechrisellis)) - Added user and group checks. Auto create socket dir. [\#205](https://github.com/parallaxsecond/parsec/pull/205) ([sbailey-arm](https://github.com/sbailey-arm)) ## [0.3.0](https://github.com/parallaxsecond/parsec/tree/0.3.0) (2020-07-16) [Full Changelog](https://github.com/parallaxsecond/parsec/compare/0.2.0...0.3.0) **Implemented enhancements:** - Create a Mbed Crypto Secure Element driver calling Parsec Rust Client [\#128](https://github.com/parallaxsecond/parsec/issues/128) - Threat model of Parsec [\#89](https://github.com/parallaxsecond/parsec/issues/89) - Precise the providers' order importance [\#203](https://github.com/parallaxsecond/parsec/pull/203) ([hug-dev](https://github.com/hug-dev)) - Keep list\_providers order; add cfg tests [\#197](https://github.com/parallaxsecond/parsec/pull/197) ([ionut-arm](https://github.com/ionut-arm)) **Merged pull requests:** - Added PsaExportKey [\#204](https://github.com/parallaxsecond/parsec/pull/204) ([sbailey-arm](https://github.com/sbailey-arm)) - Migrated uses of a locally declared RsaPublic key to new create picky-asn1-x509 [\#201](https://github.com/parallaxsecond/parsec/pull/201) ([sbailey-arm](https://github.com/sbailey-arm)) - Added asymmetric encrypt and decrypt to Mbed Crypto provider [\#196](https://github.com/parallaxsecond/parsec/pull/196) ([sbailey-arm](https://github.com/sbailey-arm)) ## [0.2.0](https://github.com/parallaxsecond/parsec/tree/0.2.0) (2020-07-02) [Full Changelog](https://github.com/parallaxsecond/parsec/compare/0.1.2...0.2.0) **Implemented enhancements:** - Further simplification of the Mbed Crypto provider [\#187](https://github.com/parallaxsecond/parsec/issues/187) - Create config "service" [\#181](https://github.com/parallaxsecond/parsec/issues/181) - Use psa-crypto crate in the Mbed Crypto Provider [\#177](https://github.com/parallaxsecond/parsec/issues/177) - Have a real integration test example [\#161](https://github.com/parallaxsecond/parsec/issues/161) - Separate provider code into modules [\#133](https://github.com/parallaxsecond/parsec/issues/133) - Update with PSA Crypto 1.0.0 interface [\#129](https://github.com/parallaxsecond/parsec/issues/129) - Create a Parsec Rust Client [\#127](https://github.com/parallaxsecond/parsec/issues/127) - TPM provider should establish most-secure primitives for itself [\#121](https://github.com/parallaxsecond/parsec/issues/121) - Improvements for tests/ci.sh [\#108](https://github.com/parallaxsecond/parsec/issues/108) - Split out ProviderConfig [\#103](https://github.com/parallaxsecond/parsec/issues/103) - Check clippy::pedantic lints [\#100](https://github.com/parallaxsecond/parsec/issues/100) - Modify configuration to have provider-specific table [\#70](https://github.com/parallaxsecond/parsec/issues/70) - Create a PSA Crypto Rust wrapper crate [\#62](https://github.com/parallaxsecond/parsec/issues/62) - Add TCTI configuration functionality [\#194](https://github.com/parallaxsecond/parsec/pull/194) ([ionut-arm](https://github.com/ionut-arm)) - Updated Parsec to use latest parsec-interface \(0.17.0\) [\#193](https://github.com/parallaxsecond/parsec/pull/193) ([sbailey-arm](https://github.com/sbailey-arm)) - Modify socket path [\#192](https://github.com/parallaxsecond/parsec/pull/192) ([hug-dev](https://github.com/hug-dev)) - Changed local\_ids for Atomic counter and removed key\_slot\_semaphore. [\#191](https://github.com/parallaxsecond/parsec/pull/191) ([sbailey-arm](https://github.com/sbailey-arm)) - Removed duplicate macros for sign output size and export pub key size. [\#190](https://github.com/parallaxsecond/parsec/pull/190) ([sbailey-arm](https://github.com/sbailey-arm)) - Move Parsec over to psa-crypto [\#186](https://github.com/parallaxsecond/parsec/pull/186) ([sbailey-arm](https://github.com/sbailey-arm)) - Add trace logging on Provide method calls [\#185](https://github.com/parallaxsecond/parsec/pull/185) ([hug-dev](https://github.com/hug-dev)) - Update fuzz target [\#184](https://github.com/parallaxsecond/parsec/pull/184) ([ionut-arm](https://github.com/ionut-arm)) - Improve log security [\#183](https://github.com/parallaxsecond/parsec/pull/183) ([ionut-arm](https://github.com/ionut-arm)) - Add GlobalConfig [\#182](https://github.com/parallaxsecond/parsec/pull/182) ([ionut-arm](https://github.com/ionut-arm)) - Add community repo link [\#180](https://github.com/parallaxsecond/parsec/pull/180) ([hug-dev](https://github.com/hug-dev)) - Use crates.io version of the interface [\#179](https://github.com/parallaxsecond/parsec/pull/179) ([hug-dev](https://github.com/hug-dev)) - Import the newest Parsec interface [\#178](https://github.com/parallaxsecond/parsec/pull/178) ([hug-dev](https://github.com/hug-dev)) - Improve handling of list\_opcodes [\#173](https://github.com/parallaxsecond/parsec/pull/173) ([ionut-arm](https://github.com/ionut-arm)) - Add default context cipher selection for TPM provider [\#172](https://github.com/parallaxsecond/parsec/pull/172) ([ionut-arm](https://github.com/ionut-arm)) - Add ECDSA support for TPM provider [\#171](https://github.com/parallaxsecond/parsec/pull/171) ([ionut-arm](https://github.com/ionut-arm)) - Improve TPM provider [\#168](https://github.com/parallaxsecond/parsec/pull/168) ([ionut-arm](https://github.com/ionut-arm)) - Improve digest handling in PKCS11 provider [\#167](https://github.com/parallaxsecond/parsec/pull/167) ([ionut-arm](https://github.com/ionut-arm)) - Split provider code into separate modules [\#165](https://github.com/parallaxsecond/parsec/pull/165) ([ionut-arm](https://github.com/ionut-arm)) - Add integration test [\#162](https://github.com/parallaxsecond/parsec/pull/162) ([ionut-arm](https://github.com/ionut-arm)) - Move end to end tests to own crate [\#160](https://github.com/parallaxsecond/parsec/pull/160) ([ionut-arm](https://github.com/ionut-arm)) - Move test client back in the Parsec repo [\#150](https://github.com/parallaxsecond/parsec/pull/150) ([ionut-arm](https://github.com/ionut-arm)) - Remove stress test on Travis CI for PKCS 11 [\#145](https://github.com/parallaxsecond/parsec/pull/145) ([hug-dev](https://github.com/hug-dev)) - Add tests checking if key attributes are respected [\#135](https://github.com/parallaxsecond/parsec/pull/135) ([hug-dev](https://github.com/hug-dev)) - Add Contributors file [\#132](https://github.com/parallaxsecond/parsec/pull/132) ([ionut-arm](https://github.com/ionut-arm)) - Update with the latest interface [\#131](https://github.com/parallaxsecond/parsec/pull/131) ([hug-dev](https://github.com/hug-dev)) - Improvments for tests/ci.sh [\#117](https://github.com/parallaxsecond/parsec/pull/117) ([anta5010](https://github.com/anta5010)) **Fixed bugs:** - Integration tests should be isolated in their crate [\#155](https://github.com/parallaxsecond/parsec/issues/155) - Key should be deleted from the KIM if generation/import fails [\#139](https://github.com/parallaxsecond/parsec/issues/139) - Fixed PKCS\#11 provieder failing failed\_created\_key\_should\_be\_removed test [\#188](https://github.com/parallaxsecond/parsec/pull/188) ([sbailey-arm](https://github.com/sbailey-arm)) - Replace calendar iframe with URL [\#166](https://github.com/parallaxsecond/parsec/pull/166) ([ionut-arm](https://github.com/ionut-arm)) - Fix clippy errors [\#157](https://github.com/parallaxsecond/parsec/pull/157) ([ionut-arm](https://github.com/ionut-arm)) - Allow PKCS11 tests to fail on Travis [\#154](https://github.com/parallaxsecond/parsec/pull/154) ([ionut-arm](https://github.com/ionut-arm)) **Security fixes:** - Implement mitigation 4 of TM [\#189](https://github.com/parallaxsecond/parsec/pull/189) ([hug-dev](https://github.com/hug-dev)) **Closed issues:** - Allow TPM owner hierarchy auth to be non-string [\#120](https://github.com/parallaxsecond/parsec/issues/120) **Merged pull requests:** - Update partners file with web links and logos [\#159](https://github.com/parallaxsecond/parsec/pull/159) ([paulhowardarm](https://github.com/paulhowardarm)) - Update CONTRIBUTORS.md [\#143](https://github.com/parallaxsecond/parsec/pull/143) ([Superhepper](https://github.com/Superhepper)) - A few more README updates including fixes for broken doc links [\#141](https://github.com/parallaxsecond/parsec/pull/141) ([paulhowardarm](https://github.com/paulhowardarm)) - README enhancements, PARTNERS file and new visual style for the project [\#136](https://github.com/parallaxsecond/parsec/pull/136) ([paulhowardarm](https://github.com/paulhowardarm)) ## [0.1.2](https://github.com/parallaxsecond/parsec/tree/0.1.2) (2020-02-27) [Full Changelog](https://github.com/parallaxsecond/parsec/compare/0.1.1...0.1.2) **Implemented enhancements:** - Modify configuration to have provider-specific structs [\#114](https://github.com/parallaxsecond/parsec/pull/114) ([anta5010](https://github.com/anta5010)) - Improve code documentation [\#113](https://github.com/parallaxsecond/parsec/pull/113) ([ionut-arm](https://github.com/ionut-arm)) ## [0.1.1](https://github.com/parallaxsecond/parsec/tree/0.1.1) (2020-02-21) [Full Changelog](https://github.com/parallaxsecond/parsec/compare/0.1.0...0.1.1) **Implemented enhancements:** - Check for more Clippy lints [\#91](https://github.com/parallaxsecond/parsec/issues/91) - Switch to picky-asn1-der for ASN.1-DER parsing [\#84](https://github.com/parallaxsecond/parsec/issues/84) - Have all the providers dynamically loadable [\#79](https://github.com/parallaxsecond/parsec/issues/79) - Pass config.toml path as command-line argument [\#78](https://github.com/parallaxsecond/parsec/issues/78) - Convert Key ID Manager String errors to ResponseStatus in the KIM itself [\#77](https://github.com/parallaxsecond/parsec/issues/77) - Test strategy for our providers on the CI [\#69](https://github.com/parallaxsecond/parsec/issues/69) - Add a PKCS 11 Provider [\#66](https://github.com/parallaxsecond/parsec/issues/66) - Add a Trusted Platform Module Provider [\#65](https://github.com/parallaxsecond/parsec/issues/65) - Assess the contents of unsafe blocks in Mbed Provider [\#63](https://github.com/parallaxsecond/parsec/issues/63) - Drop key handles implicitly [\#57](https://github.com/parallaxsecond/parsec/issues/57) - Add cross-compilation to Aarch64 logic and investigate CI testing [\#55](https://github.com/parallaxsecond/parsec/issues/55) - Add fuzz tests [\#54](https://github.com/parallaxsecond/parsec/issues/54) - Update to Mbed Crypto v2.0.0 [\#38](https://github.com/parallaxsecond/parsec/issues/38) - Improve logging message structure [\#36](https://github.com/parallaxsecond/parsec/issues/36) - Make PARSEC a daemon [\#35](https://github.com/parallaxsecond/parsec/issues/35) - Improve builders for service components [\#31](https://github.com/parallaxsecond/parsec/issues/31) - Implement a thread pool [\#29](https://github.com/parallaxsecond/parsec/issues/29) - Use dynamically-sized buffers in Mbed provider [\#27](https://github.com/parallaxsecond/parsec/issues/27) - Implement configuration [\#26](https://github.com/parallaxsecond/parsec/issues/26) - Prepare for upload to crates io [\#109](https://github.com/parallaxsecond/parsec/pull/109) ([ionut-arm](https://github.com/ionut-arm)) - Add cargo clippy lints to the CI [\#99](https://github.com/parallaxsecond/parsec/pull/99) ([hug-dev](https://github.com/hug-dev)) - Implement fuzz testing [\#97](https://github.com/parallaxsecond/parsec/pull/97) ([ionut-arm](https://github.com/ionut-arm)) - Add body length limit [\#96](https://github.com/parallaxsecond/parsec/pull/96) ([ionut-arm](https://github.com/ionut-arm)) - Ensure the safety of unsafe blocks [\#93](https://github.com/parallaxsecond/parsec/pull/93) ([hug-dev](https://github.com/hug-dev)) - Replace most panicking behaviours with Result [\#92](https://github.com/parallaxsecond/parsec/pull/92) ([hug-dev](https://github.com/hug-dev)) - Modify Travis CI test script [\#90](https://github.com/parallaxsecond/parsec/pull/90) ([hug-dev](https://github.com/hug-dev)) - Deny compilation for some rustc lints [\#87](https://github.com/parallaxsecond/parsec/pull/87) ([hug-dev](https://github.com/hug-dev)) - Switch crates to use picky-asn1-der [\#85](https://github.com/parallaxsecond/parsec/pull/85) ([hug-dev](https://github.com/hug-dev)) - Modify tests directory structure [\#83](https://github.com/parallaxsecond/parsec/pull/83) ([hug-dev](https://github.com/hug-dev)) - Allow optional providers and key ID managers [\#82](https://github.com/parallaxsecond/parsec/pull/82) ([hug-dev](https://github.com/hug-dev)) - Add a command-line option to select configuration [\#81](https://github.com/parallaxsecond/parsec/pull/81) ([hug-dev](https://github.com/hug-dev)) - Add a TPM provider [\#75](https://github.com/parallaxsecond/parsec/pull/75) ([hug-dev](https://github.com/hug-dev)) - Add SIGHUP signal handling to reload configuration [\#71](https://github.com/parallaxsecond/parsec/pull/71) ([hug-dev](https://github.com/hug-dev)) - Add a PKCS 11 provider [\#68](https://github.com/parallaxsecond/parsec/pull/68) ([hug-dev](https://github.com/hug-dev)) - Simplify the README.md file [\#67](https://github.com/parallaxsecond/parsec/pull/67) ([hug-dev](https://github.com/hug-dev)) - Add cross compilation tests to the CI with cross [\#64](https://github.com/parallaxsecond/parsec/pull/64) ([hug-dev](https://github.com/hug-dev)) - Add cross-compilation logic for Mbed Crypto [\#61](https://github.com/parallaxsecond/parsec/pull/61) ([hug-dev](https://github.com/hug-dev)) - Make key slot release implicit [\#59](https://github.com/parallaxsecond/parsec/pull/59) ([ionut-arm](https://github.com/ionut-arm)) - Make buffers dynamically sized in Mbed Provider [\#58](https://github.com/parallaxsecond/parsec/pull/58) ([ionut-arm](https://github.com/ionut-arm)) - Upgrade dependency on Mbed Crypto to v2.0.0 [\#56](https://github.com/parallaxsecond/parsec/pull/56) ([ionut-arm](https://github.com/ionut-arm)) - Add provider configuration [\#51](https://github.com/parallaxsecond/parsec/pull/51) ([ionut-arm](https://github.com/ionut-arm)) - Improve handling of systemd activation [\#50](https://github.com/parallaxsecond/parsec/pull/50) ([lnicola](https://github.com/lnicola)) - Replace println calls with log crate [\#48](https://github.com/parallaxsecond/parsec/pull/48) ([hug-dev](https://github.com/hug-dev)) - Add a compile-time option for a daemon binary [\#46](https://github.com/parallaxsecond/parsec/pull/46) ([hug-dev](https://github.com/hug-dev)) - Add service builder and configuration [\#44](https://github.com/parallaxsecond/parsec/pull/44) ([ionut-arm](https://github.com/ionut-arm)) - Add stress test to the suite [\#42](https://github.com/parallaxsecond/parsec/pull/42) ([ionut-arm](https://github.com/ionut-arm)) - Add SIGTERM handler for a graceful shutdown [\#39](https://github.com/parallaxsecond/parsec/pull/39) ([hug-dev](https://github.com/hug-dev)) - Add a GitHub Actions workflow for CI [\#34](https://github.com/parallaxsecond/parsec/pull/34) ([hug-dev](https://github.com/hug-dev)) - Add and improve component builders [\#33](https://github.com/parallaxsecond/parsec/pull/33) ([ionut-arm](https://github.com/ionut-arm)) **Fixed bugs:** - TPM provider must support Owner Hierarchy authentication [\#102](https://github.com/parallaxsecond/parsec/issues/102) - Audit our use of panicking [\#74](https://github.com/parallaxsecond/parsec/issues/74) - Audit our use of unsafe code [\#73](https://github.com/parallaxsecond/parsec/issues/73) - Review response codes returned by providers [\#72](https://github.com/parallaxsecond/parsec/issues/72) - Warning during compilation about `llvm-config --prefix` [\#60](https://github.com/parallaxsecond/parsec/issues/60) - Key handle manipulation is not thread-safe in Mbed Crypto [\#40](https://github.com/parallaxsecond/parsec/issues/40) - Add owner hierarchy auth param [\#104](https://github.com/parallaxsecond/parsec/pull/104) ([ionut-arm](https://github.com/ionut-arm)) - Add a verify-only integration test [\#88](https://github.com/parallaxsecond/parsec/pull/88) ([hug-dev](https://github.com/hug-dev)) - Add sign to ASN.1 Integer types for RSAPublicKey [\#86](https://github.com/parallaxsecond/parsec/pull/86) ([hug-dev](https://github.com/hug-dev)) - Make sure Cargo features work [\#76](https://github.com/parallaxsecond/parsec/pull/76) ([hug-dev](https://github.com/hug-dev)) - Make UnixStreams block on read/write [\#47](https://github.com/parallaxsecond/parsec/pull/47) ([ionut-arm](https://github.com/ionut-arm)) - Keep key ID within bounds for Mbed provider [\#45](https://github.com/parallaxsecond/parsec/pull/45) ([ionut-arm](https://github.com/ionut-arm)) - Add locking around key handle operations in mbed provider [\#41](https://github.com/parallaxsecond/parsec/pull/41) ([ionut-arm](https://github.com/ionut-arm)) - Use new version of test client to fix CI [\#37](https://github.com/parallaxsecond/parsec/pull/37) ([hug-dev](https://github.com/hug-dev)) **Closed issues:** - Deny compilation if there is any warning [\#80](https://github.com/parallaxsecond/parsec/issues/80) **Merged pull requests:** - Remove references to key lifetime [\#52](https://github.com/parallaxsecond/parsec/pull/52) ([hug-dev](https://github.com/hug-dev)) - Use thread pool instead of new thread per request [\#30](https://github.com/parallaxsecond/parsec/pull/30) ([ionut-arm](https://github.com/ionut-arm)) - Add the integration tests in the parsec repository [\#28](https://github.com/parallaxsecond/parsec/pull/28) ([hug-dev](https://github.com/hug-dev)) ## [0.1.0](https://github.com/parallaxsecond/parsec/tree/0.1.0) (2019-10-09) [Full Changelog](https://github.com/parallaxsecond/parsec/compare/047e395ee5edbad82d52738e275f756e7bd0480b...0.1.0) **Closed issues:** - Building/running PARSEC [\#4](https://github.com/parallaxsecond/parsec/issues/4) - Add Jenkins, CI/CD, unit testing, and code coverage [\#3](https://github.com/parallaxsecond/parsec/issues/3) - Implement stubbed server API for client testing [\#2](https://github.com/parallaxsecond/parsec/issues/2) - Create PASL golang client API [\#1](https://github.com/parallaxsecond/parsec/issues/1) **Merged pull requests:** - Add versioning requirement on the interface [\#25](https://github.com/parallaxsecond/parsec/pull/25) ([hug-dev](https://github.com/hug-dev)) - Fixed Ionut's email address [\#24](https://github.com/parallaxsecond/parsec/pull/24) ([robdimond-arm](https://github.com/robdimond-arm)) - Remove Go client from PARSEC service [\#22](https://github.com/parallaxsecond/parsec/pull/22) ([hug-dev](https://github.com/hug-dev)) - Add documentation updates [\#21](https://github.com/parallaxsecond/parsec/pull/21) ([hug-dev](https://github.com/hug-dev)) - Docs: Update documentation to reflect the source code state [\#20](https://github.com/parallaxsecond/parsec/pull/20) ([ionut-arm](https://github.com/ionut-arm)) - Add support for ListProviders operation update [\#19](https://github.com/parallaxsecond/parsec/pull/19) ([hug-dev](https://github.com/hug-dev)) - Add a MAINTAINERS file [\#18](https://github.com/parallaxsecond/parsec/pull/18) ([hug-dev](https://github.com/hug-dev)) - Merge Integration into Master [\#17](https://github.com/parallaxsecond/parsec/pull/17) ([ionut-arm](https://github.com/ionut-arm)) - Update conn and key interfaces for initialization [\#16](https://github.com/parallaxsecond/parsec/pull/16) ([jamesonhyde-docker](https://github.com/jamesonhyde-docker)) - Update response to handle a mis-aligned header and response test [\#15](https://github.com/parallaxsecond/parsec/pull/15) ([jamesonhyde-docker](https://github.com/jamesonhyde-docker)) - Various improvements of the service internals [\#14](https://github.com/parallaxsecond/parsec/pull/14) ([hug-dev](https://github.com/hug-dev)) - Go client implementations [\#12](https://github.com/parallaxsecond/parsec/pull/12) ([jamesonhyde-docker](https://github.com/jamesonhyde-docker)) - update logo from plasma to parsec [\#11](https://github.com/parallaxsecond/parsec/pull/11) ([adamparco](https://github.com/adamparco)) - Initial go client interface for signing keys [\#10](https://github.com/parallaxsecond/parsec/pull/10) ([jamesonhyde-docker](https://github.com/jamesonhyde-docker)) - Provide minimal software solution based on Mbed Crypto [\#9](https://github.com/parallaxsecond/parsec/pull/9) ([hug-dev](https://github.com/hug-dev)) - Add API landing page [\#8](https://github.com/parallaxsecond/parsec/pull/8) ([ionut-arm](https://github.com/ionut-arm)) - Adding doc fragments. [\#7](https://github.com/parallaxsecond/parsec/pull/7) ([ionut-arm](https://github.com/ionut-arm)) - update name from PASL to PLASMA [\#6](https://github.com/parallaxsecond/parsec/pull/6) ([adamparco](https://github.com/adamparco)) \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)* parsec-service-1.3.0/CODE-OF-CONDUCT.md000064400000000000000000000002111046102023000151650ustar 00000000000000## Parsec Code of Conduct Parsec follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md). parsec-service-1.3.0/CONTRIBUTORS.md000064400000000000000000000013211046102023000150140ustar 00000000000000# Contributors to the Parsec project This file aims to acknowledge the specific contributors referred to in the "Contributors to the Parsec project" copyright notice. ## Organizations * Arm Ltd. * Docker Inc. * Mirantis Inc. ## Individuals * Anton Antonov (@anta5010) * Paul Howard (@paulhowardarm) * Ionut Mihalcea (@ionut-arm) * Hugues de Valon (@hug-dev) * Jesper Brynolf (@Superhepper) * Samuel Bailey (@sbailey-arm) * Patrick Uiterwijk (@puiterwijk) * Nicolas Stalder (@nickray) * Edmund Grimley Evans (@egrimley-arm) * Matt Davis (@MattDavis00) * Mohamed Omar Asaker (@mohamedasaker-arm) * Gowtham Suresh Kumar (@gowthamsk-arm) * William Brown (@firstyear) * Tomas Agustin Gonzalez Orlando (@tgonzalezorlandoarm) parsec-service-1.3.0/Cargo.lock0000644000001553720000000000100117400ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "ahash" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ "cfg-if", "once_cell", "version_check", ] [[package]] name = "aho-corasick" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0" dependencies = [ "memchr", ] [[package]] name = "allocator-api2" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" [[package]] name = "anyhow" version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "asn1-rs" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30ff05a702273012438132f449575dbc804e27b2f3cbe3069aa237d26c98fa33" dependencies = [ "asn1-rs-derive", "asn1-rs-impl", "displaydoc", "nom 7.1.3", "num-traits", "rusticata-macros", "thiserror", "time", ] [[package]] name = "asn1-rs-derive" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db8b7511298d5b7784b40b092d9e9dcd3a627a5707e4b5e507931ab0d44eeebf" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", "synstructure", ] [[package]] name = "asn1-rs-impl" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] name = "base64" version = "0.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2" [[package]] name = "bincode" version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" dependencies = [ "serde", ] [[package]] name = "bindgen" version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd4865004a46a0aafb2a0a5eb19d3c9fc46ee5f063a6cfc605c69ac9ecf5263d" dependencies = [ "bitflags 1.3.2", "cexpr 0.4.0", "clang-sys", "lazy_static", "lazycell", "peeking_take_while", "proc-macro2", "quote", "regex", "rustc-hash", "shlex 0.1.1", ] [[package]] name = "bindgen" version = "0.66.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" dependencies = [ "bitflags 2.4.0", "cexpr 0.6.0", "clang-sys", "lazy_static", "lazycell", "log", "peeking_take_while", "prettyplease", "proc-macro2", "quote", "regex", "rustc-hash", "shlex 1.2.0", "syn 2.0.38", "which", ] [[package]] name = "bitfield" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d7e60934ceec538daadb9d8432424ed043a904d8e0243f3c6446bce549a46ac" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] name = "bumpalo" version = "3.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" [[package]] name = "bytes" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" [[package]] name = "cc" version = "1.0.83" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" dependencies = [ "libc", ] [[package]] name = "cexpr" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" dependencies = [ "nom 5.1.3", ] [[package]] name = "cexpr" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ "nom 7.1.3", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clang-sys" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" dependencies = [ "glob", "libc", "libloading", ] [[package]] name = "clap" version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ "bitflags 1.3.2", "textwrap", "unicode-width", ] [[package]] name = "cmake" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb6210b637171dfba4cda12e579ac6dc73f5165ad56133e5d72ef3131f320855" dependencies = [ "cc", ] [[package]] name = "const-oid" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4c78c047431fee22c1a7bb92e00ad095a02a983affe4d8a72e2a2c62c1b94f3" [[package]] name = "cryptoauthlib-sys" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da232dd4f06ee4600b33a455bb17fcc6c2c3a54ee7fd60496d3a73668a6cb6e4" dependencies = [ "cmake", ] [[package]] name = "cryptoki" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e08651cefd925cd83d8d1b4f96276c18fe5ee148ab8c8a47f462316d36bc01af" dependencies = [ "bitflags 1.3.2", "cryptoki-sys", "libloading", "log", "paste", "secrecy", ] [[package]] name = "cryptoki-sys" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a978e5e226446ac68eded4f92796947130f0d21de1e21bf80298f9f50d917d5" dependencies = [ "libloading", ] [[package]] name = "data-encoding" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] name = "der" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6919815d73839e7ad218de758883aae3a257ba6759ce7a9992501efbb53d705c" dependencies = [ "const-oid", ] [[package]] name = "der-parser" version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe398ac75057914d7d07307bf67dc7f3f574a26783b4fc7805a20ffa9f506e82" dependencies = [ "asn1-rs", "displaydoc", "nom 7.1.3", "num-bigint", "num-traits", "rusticata-macros", ] [[package]] name = "deranged" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2696e8a945f658fd14dc3b87242e6b80cd0f36ff04ea560fa39082368847946" [[package]] name = "derivative" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "displaydoc" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", "syn 2.0.38", ] [[package]] name = "either" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" [[package]] name = "enumflags2" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5998b4f30320c9d93aed72f63af821bfdac50465b75428fce77b48ec482c3939" dependencies = [ "enumflags2_derive", ] [[package]] name = "enumflags2_derive" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f95e2801cd355d4a1a3e3953ce6ee5ae9603a5c833455343a8bfe3f44d418246" dependencies = [ "proc-macro2", "quote", "syn 2.0.38", ] [[package]] name = "env_logger" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" dependencies = [ "humantime", "is-terminal", "log", "regex", "termcolor", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" dependencies = [ "libc", "windows-sys", ] [[package]] name = "fallible-iterator" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fallible-streaming-iterator" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "fixedbitset" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "form_urlencoded" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] [[package]] name = "futures" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-macro" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", "syn 2.0.38", ] [[package]] name = "futures-sink" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "getrandom" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "glob" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "grpcio" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d99e00eed7e0a04ee2705112e7cfdbe1a3cc771147f22f016a8cd2d002187b" dependencies = [ "futures", "grpcio-sys", "libc", "log", "parking_lot", "protobuf", ] [[package]] name = "grpcio-sys" version = "0.9.1+1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9447d1a926beeef466606cc45717f80897998b548e7dc622873d453e1ecb4be4" dependencies = [ "bindgen 0.57.0", "cc", "cmake", "libc", "libz-sys", "pkg-config", "walkdir", ] [[package]] name = "hashbrown" version = "0.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dfda62a12f55daeae5015f81b0baea145391cb4520f86c248fc615d72640d12" dependencies = [ "ahash", "allocator-api2", ] [[package]] name = "hashlink" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8094feaf31ff591f651a2664fb9cfd92bba7a60ce3197265e9482ebe753c8f7" dependencies = [ "hashbrown", ] [[package]] name = "heck" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" dependencies = [ "unicode-segmentation", ] [[package]] name = "hermit-abi" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "home" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5444c27eef6923071f7ebcc33e3444508466a76f7a2b93da00ed6e19f30c1ddb" dependencies = [ "windows-sys", ] [[package]] name = "hostname-validator" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f558a64ac9af88b5ba400d99b579451af0d39c6d360980045b91aac966d705e2" [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "idna" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", ] [[package]] name = "indexmap" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8adf3ddd720272c6ea8bf59463c04e0f93d0bbf7c5439b691bca2987e0270897" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "instant" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" dependencies = [ "cfg-if", ] [[package]] name = "is-terminal" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" dependencies = [ "hermit-abi", "rustix", "windows-sys", ] [[package]] name = "itertools" version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" [[package]] name = "js-sys" version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] [[package]] name = "jsonwebkey" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c57c852b14147e2bd58c14fde40398864453403ef632b1101db130282ee6e2cc" dependencies = [ "base64 0.13.1", "bitflags 1.3.2", "generic-array", "jsonwebtoken", "num-bigint", "serde", "serde_json", "thiserror", "yasna", "zeroize", ] [[package]] name = "jsonwebtoken" version = "8.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6971da4d9c3aa03c3d8f3ff0f4155b534aad021292003895a469716b2a230378" dependencies = [ "base64 0.21.4", "pem", "ring", "serde", "serde_json", "simple_asn1", ] [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "lazycell" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" version = "0.2.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "libloading" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" dependencies = [ "cfg-if", "winapi", ] [[package]] name = "libsqlite3-sys" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afc22eff61b133b115c6e8c74e818c628d6d5e7a502afea6f64dee076dd94326" dependencies = [ "cc", "pkg-config", "vcpkg", ] [[package]] name = "libz-sys" version = "1.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d97137b25e321a73eef1418d1d5d2eda4d77e12813f8e6dead84bc52c5870a7b" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "linux-raw-sys" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" [[package]] name = "lock_api" version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" dependencies = [ "serde", ] [[package]] name = "mbox" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f88d5c34d63aad11aa4321ef55ccb064af58b3ad8091079ae22bf83e5eb75d6" dependencies = [ "libc", "rustc_version", "stable_deref_trait", ] [[package]] name = "memchr" version = "2.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "multimap" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" [[package]] name = "nom" version = "5.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08959a387a676302eebf4ddbcbc611da04285579f76f88ee0506c63b1a61dd4b" dependencies = [ "memchr", "version_check", ] [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "num" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" dependencies = [ "num-bigint", "num-complex", "num-integer", "num-iter", "num-rational", "num-traits", ] [[package]] name = "num-bigint" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" dependencies = [ "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-complex" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" dependencies = [ "num-traits", ] [[package]] name = "num-derive" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfb77679af88f8b125209d354a202862602672222e7f2313fdd6dc349bad4712" dependencies = [ "proc-macro2", "quote", "syn 2.0.38", ] [[package]] name = "num-integer" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" dependencies = [ "autocfg", "num-traits", ] [[package]] name = "num-iter" version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" dependencies = [ "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-rational" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" dependencies = [ "autocfg", "num-bigint", "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] [[package]] name = "num_cpus" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ "hermit-abi", "libc", ] [[package]] name = "oid" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c19903c598813dba001b53beeae59bb77ad4892c5c1b9b3500ce4293a0d06c2" dependencies = [ "serde", ] [[package]] name = "oid-registry" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38e20717fa0541f39bd146692035c37bedfa532b3e5071b35761082407546b2a" dependencies = [ "asn1-rs", ] [[package]] name = "once_cell" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "parking_lot" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" dependencies = [ "instant", "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" dependencies = [ "cfg-if", "instant", "libc", "redox_syscall 0.2.16", "smallvec", "winapi", ] [[package]] name = "parsec-interface" version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc706e09209b30f10baa35709d41b9cc01d4931b21c00679f59db96cd1650add" dependencies = [ "bincode", "derivative", "log", "num", "num-derive", "num-traits", "prost", "psa-crypto", "secrecy", "serde", "uuid", "zeroize", ] [[package]] name = "parsec-service" version = "1.3.0" dependencies = [ "anyhow", "base64 0.21.4", "bincode", "bindgen 0.66.1", "cryptoki", "derivative", "env_logger", "hex", "libc", "log", "num-traits", "parsec-interface", "picky-asn1", "picky-asn1-der", "picky-asn1-x509", "prost", "prost-build", "psa-crypto", "rand", "rusqlite", "rust-cryptoauthlib", "sd-notify", "serde", "signal-hook", "spiffe", "structopt", "threadpool", "toml", "tss-esapi", "zeroize", ] [[package]] name = "paste" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "peeking_take_while" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "pem" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" dependencies = [ "base64 0.13.1", ] [[package]] name = "percent-encoding" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pest" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c022f1e7b65d6a24c0dbbd5fb344c66881bc01f3e5ae74a1c8100f2f985d98a4" dependencies = [ "memchr", "thiserror", "ucd-trie", ] [[package]] name = "petgraph" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" dependencies = [ "fixedbitset", "indexmap", ] [[package]] name = "picky-asn1" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "295eea0f33c16be21e2a98b908fdd4d73c04dd48c8480991b76dbcf0cb58b212" dependencies = [ "oid", "serde", "serde_bytes", ] [[package]] name = "picky-asn1-der" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5df7873a9e36d42dadb393bea5e211fe83d793c172afad5fb4ec846ec582793f" dependencies = [ "picky-asn1", "serde", "serde_bytes", ] [[package]] name = "picky-asn1-x509" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c5f20f71a68499ff32310f418a6fad8816eac1a2859ed3f0c5c741389dd6208" dependencies = [ "base64 0.21.4", "oid", "picky-asn1", "picky-asn1-der", "serde", ] [[package]] name = "pin-project-lite" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkcs8" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cabda3fb821068a9a4fab19a683eac3af12edf0f34b94a8be53c4972b8149d0" dependencies = [ "der", "spki", ] [[package]] name = "pkg-config" version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "prettyplease" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" dependencies = [ "proc-macro2", "syn 2.0.38", ] [[package]] name = "proc-macro-error" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", "syn 1.0.109", "version_check", ] [[package]] name = "proc-macro-error-attr" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" dependencies = [ "proc-macro2", "quote", "version_check", ] [[package]] name = "proc-macro2" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] [[package]] name = "prost" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "444879275cb4fd84958b1a1d5420d15e6fcf7c235fe47f053c9c2a80aceb6001" dependencies = [ "bytes", "prost-derive", ] [[package]] name = "prost-build" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62941722fb675d463659e49c4f3fe1fe792ff24fe5bbaa9c08cd3b98a1c354f5" dependencies = [ "bytes", "heck", "itertools", "lazy_static", "log", "multimap", "petgraph", "prost", "prost-types", "regex", "tempfile", "which", ] [[package]] name = "prost-derive" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9cc1a3263e07e0bf68e96268f37665207b49560d98739662cdfaae215c720fe" dependencies = [ "anyhow", "itertools", "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "prost-types" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534b7a0e836e3c482d2693070f982e39e7611da9695d4d1f5a4b186b51faef0a" dependencies = [ "bytes", "prost", ] [[package]] name = "protobuf" version = "2.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "106dd99e98437432fed6519dedecfade6a06a73bb7b2a1e019fdd2bee5778d94" [[package]] name = "psa-crypto" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89c2256e525b9a45ec3bbb3382a43dd8809240279e0aab8ea7ee220e9295445b" dependencies = [ "log", "psa-crypto-sys", "serde", "zeroize", ] [[package]] name = "psa-crypto-sys" version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f170cac3a328e1678916b276067ec170a5a51db1b9b8b4c00b44c2839819a963" dependencies = [ "bindgen 0.66.1", "cc", "cmake", "regex", "walkdir", ] [[package]] name = "quote" version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "redox_syscall" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "redox_syscall" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "regex" version = "1.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebee201405406dbf528b8b672104ae6d6d63e6d118cb10e4d51abbc7b58044ff" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59b23e92ee4318893fa3fe3e6fb365258efbfe6ac6ab30f090cdcbb7aa37efa9" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "ring" version = "0.16.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" dependencies = [ "cc", "libc", "once_cell", "spin", "untrusted", "web-sys", "winapi", ] [[package]] name = "rusqlite" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "549b9d036d571d42e6e85d1c1425e2ac83491075078ca9a15be021c56b1641f2" dependencies = [ "bitflags 2.4.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink", "libsqlite3-sys", "smallvec", ] [[package]] name = "rust-cryptoauthlib" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adab07508c090715a5cd3d072f2b8ab60d7e9e04c5af19e1d3d819651b5b25a2" dependencies = [ "cryptoauthlib-sys", "lazy_static", "log", "rand", "strum_macros", ] [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc_version" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" dependencies = [ "semver", ] [[package]] name = "rusticata-macros" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" dependencies = [ "nom 7.1.3", ] [[package]] name = "rustix" version = "0.38.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a74ee2d7c2581cd139b42447d7d9389b889bdaad3a73f1ebb16f2a3237bb19c" dependencies = [ "bitflags 2.4.0", "errno", "libc", "linux-raw-sys", "windows-sys", ] [[package]] name = "ryu" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sd-notify" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "621e3680f3e07db4c9c2c3fb07c6223ab2fab2e54bd3c04c3ae037990f428c32" [[package]] name = "secrecy" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" dependencies = [ "serde", "zeroize", ] [[package]] name = "semver" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" dependencies = [ "semver-parser", ] [[package]] name = "semver-parser" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" dependencies = [ "pest", ] [[package]] name = "serde" version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" dependencies = [ "serde_derive", ] [[package]] name = "serde_bytes" version = "0.11.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff" dependencies = [ "serde", ] [[package]] name = "serde_derive" version = "1.0.188" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" dependencies = [ "proc-macro2", "quote", "syn 2.0.38", ] [[package]] name = "serde_json" version = "1.0.107" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "serde_spanned" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" dependencies = [ "serde", ] [[package]] name = "shlex" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" [[package]] name = "shlex" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" [[package]] name = "signal-hook" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" dependencies = [ "libc", "signal-hook-registry", ] [[package]] name = "signal-hook-registry" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] [[package]] name = "simple_asn1" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adc4e5204eb1910f40f9cfa375f6f05b68c3abac4b6fd879c8ff5e7ae8a0a085" dependencies = [ "num-bigint", "num-traits", "thiserror", "time", ] [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "942b4a808e05215192e39f4ab80813e599068285906cc91aa64f923db842bd5a" [[package]] name = "spiffe" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f30161ecb25b9acc06eb61d750aaf1c4b3a536e22ff19fc2d250976537e93a11" dependencies = [ "futures", "grpcio", "jsonwebkey", "jsonwebtoken", "pkcs8", "protobuf", "serde", "serde_json", "simple_asn1", "thiserror", "time", "url", "x509-parser", "zeroize", ] [[package]] name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spki" version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44d01ac02a6ccf3e07db148d2be087da624fea0221a16152ed01f0496a6b0a27" dependencies = [ "der", ] [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "structopt" version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" dependencies = [ "clap", "lazy_static", "structopt-derive", ] [[package]] name = "structopt-derive" version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" dependencies = [ "heck", "proc-macro-error", "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "strum_macros" version = "0.21.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d06aaeeee809dbc59eb4556183dd927df67db1540de5be8d3ec0b6636358a5ec" dependencies = [ "heck", "proc-macro2", "quote", "syn 1.0.109", ] [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "synstructure" version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", "unicode-xid", ] [[package]] name = "target-lexicon" version = "0.12.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d0e916b1148c8e263850e1ebcbd046f333e0683c724876bb0da63ea4373dc8a" [[package]] name = "tempfile" version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ "cfg-if", "fastrand", "redox_syscall 0.3.5", "rustix", "windows-sys", ] [[package]] name = "termcolor" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6093bad37da69aab9d123a8091e4be0aa4a03e4d601ec641c327398315f62b64" dependencies = [ "winapi-util", ] [[package]] name = "textwrap" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ "unicode-width", ] [[package]] name = "thiserror" version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.49" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc" dependencies = [ "proc-macro2", "quote", "syn 2.0.38", ] [[package]] name = "threadpool" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" dependencies = [ "num_cpus", ] [[package]] name = "time" version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe" dependencies = [ "deranged", "itoa", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20" dependencies = [ "time-core", ] [[package]] name = "tinyvec" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "toml" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" dependencies = [ "serde", "serde_spanned", "toml_datetime", "toml_edit", ] [[package]] name = "toml_datetime" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", "winnow", ] [[package]] name = "tss-esapi" version = "7.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de234df360c349f78ecd33f0816ab3842db635732212b5cfad67f2638336864e" dependencies = [ "bitfield", "enumflags2", "hostname-validator", "log", "mbox", "num-derive", "num-traits", "oid", "picky-asn1", "picky-asn1-x509", "regex", "serde", "tss-esapi-sys", "zeroize", ] [[package]] name = "tss-esapi-sys" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "535cd192581c2ec4d5f82e670b1d3fbba6a23ccce8c85de387642051d7cad5b5" dependencies = [ "pkg-config", "target-lexicon", ] [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "ucd-trie" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" [[package]] name = "unicode-bidi" version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" [[package]] name = "unicode-width" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" [[package]] name = "unicode-xid" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "untrusted" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] [[package]] name = "uuid" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "walkdir" version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn 2.0.38", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", "syn 2.0.38", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" [[package]] name = "web-sys" version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b85cbef8c220a6abc02aefd892dfc0fc23afb1c6a426316ec33253a3877249b" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "which" version = "4.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" dependencies = [ "either", "home", "once_cell", "rustix", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" dependencies = [ "winapi", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "winnow" version = "0.5.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "037711d82167854aff2018dfd193aa0fef5370f456732f0d5a0c59b0f1b4b907" dependencies = [ "memchr", ] [[package]] name = "x509-parser" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fb9bace5b5589ffead1afb76e43e34cff39cd0f3ce7e170ae0c29e53b88eb1c" dependencies = [ "asn1-rs", "base64 0.13.1", "data-encoding", "der-parser", "lazy_static", "nom 7.1.3", "oid-registry", "rusticata-macros", "thiserror", "time", ] [[package]] name = "yasna" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e262a29d0e61ccf2b6190d7050d4b237535fc76ce4c1210d9caa316f71dffa75" dependencies = [ "num-bigint", ] [[package]] name = "zeroize" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", "syn 2.0.38", ] parsec-service-1.3.0/Cargo.toml0000644000000074450000000000100117600ustar # 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-service" version = "1.3.0" authors = ["Parsec Project Contributors"] description = "A language-agnostic API to secure services in a platform-agnostic way" readme = "README.md" keywords = [ "security", "service", ] categories = [ "cryptography", "hardware-support", ] license = "Apache-2.0" repository = "https://github.com/parallaxsecond/parsec" [package.metadata.docs.rs] features = [ "pkcs11-provider", "tpm-provider", "mbed-crypto-provider", "cryptoauthlib-provider", "direct-authenticator", ] [[bin]] name = "parsec" path = "src/bin/main.rs" [dependencies.anyhow] version = "1.0.38" [dependencies.base64] version = "0.21.0" [dependencies.bincode] version = "1.3.1" [dependencies.cryptoki] version = "0.6.0" optional = true default-features = false [dependencies.derivative] version = "2.2.0" [dependencies.env_logger] version = "0.10.0" [dependencies.hex] version = "0.4.2" optional = true [dependencies.libc] version = "0.2.86" [dependencies.log] version = "0.4.14" features = ["serde"] [dependencies.num-traits] version = "0.2.14" [dependencies.parsec-interface] version = "0.29.1" [dependencies.picky-asn1] version = "0.8.0" optional = true [dependencies.picky-asn1-der] version = "0.4.0" optional = true [dependencies.picky-asn1-x509] version = "0.12.0" optional = true [dependencies.prost] version = "0.9.0" optional = true [dependencies.psa-crypto] version = "0.12.0" features = [ "operations", "std", ] optional = true default-features = false [dependencies.rand] version = "0.8.3" features = ["small_rng"] optional = true [dependencies.rusqlite] version = "0.29.0" features = ["bundled"] [dependencies.rust-cryptoauthlib] version = "0.4.5" optional = true [dependencies.sd-notify] version = "0.4.1" [dependencies.serde] version = "1.0.123" features = ["derive"] [dependencies.signal-hook] version = "0.3.4" [dependencies.spiffe] version = "0.2.1" optional = true [dependencies.structopt] version = "0.3.26" default-features = false [dependencies.threadpool] version = "1.8.1" [dependencies.toml] version = "0.8.0" [dependencies.tss-esapi] version = "7.4.0" optional = true [dependencies.zeroize] version = "1.2.0" features = ["zeroize_derive"] [dev-dependencies.rand] version = "0.8.3" features = ["small_rng"] [dev-dependencies.rust-cryptoauthlib] version = "0.4.4" features = ["software-backend"] [build-dependencies.bindgen] version = "0.66.1" optional = true [build-dependencies.prost-build] version = "0.9.0" optional = true [features] all-authenticators = [ "direct-authenticator", "unix-peer-credentials-authenticator", "jwt-svid-authenticator", ] all-providers = [ "tpm-provider", "pkcs11-provider", "mbed-crypto-provider", "trusted-service-provider", ] cryptoauthlib-provider = ["rust-cryptoauthlib"] default = ["unix-peer-credentials-authenticator"] direct-authenticator = [] jwt-svid-authenticator = ["spiffe"] mbed-crypto-provider = ["psa-crypto"] pkcs11-provider = [ "cryptoki", "picky-asn1-der", "picky-asn1", "picky-asn1-x509", "psa-crypto", "rand", "hex", ] tpm-provider = [ "tss-esapi", "picky-asn1-der", "picky-asn1", "picky-asn1-x509", "hex", ] trusted-service-provider = [ "psa-crypto", "bindgen", "prost-build", "prost", ] unix-peer-credentials-authenticator = [] parsec-service-1.3.0/Cargo.toml.orig0000644000000056400000000000100127120ustar [package] name = "parsec-service" version = "1.3.0" authors = ["Parsec Project Contributors"] description = "A language-agnostic API to secure services in a platform-agnostic way" license = "Apache-2.0" repository = "https://github.com/parallaxsecond/parsec" readme = "README.md" keywords = ["security", "service"] categories = ["cryptography", "hardware-support"] edition = "2018" [[bin]] name = "parsec" path = "src/bin/main.rs" [dependencies] parsec-interface = "0.29.1" rand = { version = "0.8.3", features = ["small_rng"], optional = true } base64 = "0.21.0" threadpool = "1.8.1" signal-hook = "0.3.4" sd-notify = "0.4.1" toml = "0.8.0" serde = { version = "1.0.123", features = ["derive"] } env_logger = "0.10.0" log = { version = "0.4.14", features = ["serde"] } cryptoki = { version = "0.6.0", optional = true, default-features = false } picky-asn1-der = { version = "0.4.0", optional = true } picky-asn1 = { version = "0.8.0", optional = true } tss-esapi = { version = "7.4.0", optional = true } bincode = "1.3.1" structopt = { version = "0.3.26", default-features = false} derivative = "2.2.0" hex = { version = "0.4.2", optional = true } psa-crypto = { version = "0.12.0", default-features = false, features = ["operations","std"], optional = true } zeroize = { version = "1.2.0", features = ["zeroize_derive"] } picky-asn1-x509 = { version = "0.12.0", optional = true } libc = "0.2.86" anyhow = "1.0.38" rust-cryptoauthlib = { version = "0.4.5", optional = true } spiffe = { version = "0.2.1", optional = true } prost = { version = "0.9.0", optional = true } rusqlite = { version = "0.29.0", features = ["bundled"] } num-traits = "0.2.14" [dev-dependencies] rand = { version = "0.8.3", features = ["small_rng"] } rust-cryptoauthlib = { version = "0.4.4", features=["software-backend"]} [build-dependencies] bindgen = { version = "0.66.1", optional = true } prost-build = { version = "0.9.0", optional = true } [package.metadata.docs.rs] features = ["pkcs11-provider", "tpm-provider", "mbed-crypto-provider", "cryptoauthlib-provider", "direct-authenticator"] # The features should not be modified in a breaking way. # See https://github.com/parallaxsecond/parsec/issues/408 for details. [features] default = ["unix-peer-credentials-authenticator"] # Providers mbed-crypto-provider = ["psa-crypto"] pkcs11-provider = ["cryptoki", "picky-asn1-der", "picky-asn1", "picky-asn1-x509", "psa-crypto", "rand", "hex"] tpm-provider = ["tss-esapi", "picky-asn1-der", "picky-asn1", "picky-asn1-x509", "hex"] cryptoauthlib-provider = ["rust-cryptoauthlib"] trusted-service-provider = ["psa-crypto", "bindgen", "prost-build", "prost"] all-providers = ["tpm-provider", "pkcs11-provider", "mbed-crypto-provider", "trusted-service-provider"] # Authenticators direct-authenticator = [] unix-peer-credentials-authenticator = [] jwt-svid-authenticator = ["spiffe"] all-authenticators = ["direct-authenticator", "unix-peer-credentials-authenticator", "jwt-svid-authenticator"] parsec-service-1.3.0/Cargo.toml.orig000064400000000000000000000056401046102023000154340ustar 00000000000000[package] name = "parsec-service" version = "1.3.0" authors = ["Parsec Project Contributors"] description = "A language-agnostic API to secure services in a platform-agnostic way" license = "Apache-2.0" repository = "https://github.com/parallaxsecond/parsec" readme = "README.md" keywords = ["security", "service"] categories = ["cryptography", "hardware-support"] edition = "2018" [[bin]] name = "parsec" path = "src/bin/main.rs" [dependencies] parsec-interface = "0.29.1" rand = { version = "0.8.3", features = ["small_rng"], optional = true } base64 = "0.21.0" threadpool = "1.8.1" signal-hook = "0.3.4" sd-notify = "0.4.1" toml = "0.8.0" serde = { version = "1.0.123", features = ["derive"] } env_logger = "0.10.0" log = { version = "0.4.14", features = ["serde"] } cryptoki = { version = "0.6.0", optional = true, default-features = false } picky-asn1-der = { version = "0.4.0", optional = true } picky-asn1 = { version = "0.8.0", optional = true } tss-esapi = { version = "7.4.0", optional = true } bincode = "1.3.1" structopt = { version = "0.3.26", default-features = false} derivative = "2.2.0" hex = { version = "0.4.2", optional = true } psa-crypto = { version = "0.12.0", default-features = false, features = ["operations","std"], optional = true } zeroize = { version = "1.2.0", features = ["zeroize_derive"] } picky-asn1-x509 = { version = "0.12.0", optional = true } libc = "0.2.86" anyhow = "1.0.38" rust-cryptoauthlib = { version = "0.4.5", optional = true } spiffe = { version = "0.2.1", optional = true } prost = { version = "0.9.0", optional = true } rusqlite = { version = "0.29.0", features = ["bundled"] } num-traits = "0.2.14" [dev-dependencies] rand = { version = "0.8.3", features = ["small_rng"] } rust-cryptoauthlib = { version = "0.4.4", features=["software-backend"]} [build-dependencies] bindgen = { version = "0.66.1", optional = true } prost-build = { version = "0.9.0", optional = true } [package.metadata.docs.rs] features = ["pkcs11-provider", "tpm-provider", "mbed-crypto-provider", "cryptoauthlib-provider", "direct-authenticator"] # The features should not be modified in a breaking way. # See https://github.com/parallaxsecond/parsec/issues/408 for details. [features] default = ["unix-peer-credentials-authenticator"] # Providers mbed-crypto-provider = ["psa-crypto"] pkcs11-provider = ["cryptoki", "picky-asn1-der", "picky-asn1", "picky-asn1-x509", "psa-crypto", "rand", "hex"] tpm-provider = ["tss-esapi", "picky-asn1-der", "picky-asn1", "picky-asn1-x509", "hex"] cryptoauthlib-provider = ["rust-cryptoauthlib"] trusted-service-provider = ["psa-crypto", "bindgen", "prost-build", "prost"] all-providers = ["tpm-provider", "pkcs11-provider", "mbed-crypto-provider", "trusted-service-provider"] # Authenticators direct-authenticator = [] unix-peer-credentials-authenticator = [] jwt-svid-authenticator = ["spiffe"] all-authenticators = ["direct-authenticator", "unix-peer-credentials-authenticator", "jwt-svid-authenticator"] parsec-service-1.3.0/LICENSE000064400000000000000000000261361046102023000135550ustar 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-service-1.3.0/MAINTAINERS.toml000064400000000000000000000035711046102023000152150ustar 00000000000000# Parsec maintainers file # # This file lists the maintainers of the parallaxsecond/parsec 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", "heavypackets", "hug-dev", "ionut-arm", "justincormack", "paulhowardarm", "anta5010", "gowthamsk-arm", "mohamedasaker-arm", "tgonzalezorlandoarm", ] [people] # A reference list of all people associated with the project. [people.adamparco] Name = "Adam Parco" Email = "aparco@mirantis.com" GitHub = "adamparco" [people.heavypackets] Name = "Sabree Blackmon" Email = "sabree.blackmon@docker.com" GitHub = "heavypackets" [people.hug-dev] Name = "Hugues de Valon" Email = "hugues.de-valon@einride.tech" GitHub = "hug-dev" [people.ionut-arm] Name = "Ionut Mihalcea" Email = "ionut.mihalcea@arm.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" [people.anta5010] Name = "Anton Antonov" Email = "anton.antonov@arm.com" GitHub = "anta5010" [people.gowthamsk-arm] Name = "Gowtham Suresh Kumar" Email = "gowtham.sureshkumar@arm.com" GitHub = "gowthamsk-arm" [people.mohamedasaker-arm] Name = "Mohamed Omar Asaker" Email = "mohamed.omarasaker@arm.com" GitHub = "mohamedasaker-arm" [people.tgonzalezorlandoarm] Name = "Tomás Agustín González Orlando" Email = "tomasagustin.gonzalezorlando@arm.com" GitHub = "tgonzalezorlandoarm" parsec-service-1.3.0/PARTNERS.md000064400000000000000000000034761046102023000143320ustar 00000000000000 # **Contributing Organizations**

arm.com                docker.com               mirantis.com               linaro.org              

**Note**: Arm is contributing to Parsec as part of [Project Cassini](https://www.arm.com/-/media/global/solutions/artificial-intelligence/Project_Cassini.pdf). # **Adopters and Industry Partners**

rancher.com                redhat.com                nxp.com               

parsec-service-1.3.0/README.md000064400000000000000000000103711046102023000140210ustar 00000000000000

Parsec logo

Crates.io Code documentation

# Welcome To PARSEC **PARSEC** is the **P**latform **A**bst**R**action for **SEC**urity, an open-source initiative to provide a common API to hardware security and cryptographic services in a platform-agnostic way. This abstraction layer keeps workloads decoupled from physical platform details, enabling cloud-native delivery flows within the data center and at the edge.

Parsec Concept Diagram

Read the Parsec documentation [**online**](https://parallaxsecond.github.io/parsec-book/). Read the whitepaper [**Security for the Infrastructure Edge**](https://www.arm.com/-/media/global/people/Security-For-The-Infrastructure-Edge-White-paper-NDA). # Why PARSEC? Use Parsec when you need: - A **portable interface to your platform's Root of Trust** in order to manage keys and perform cryptographic operations without knowledge of the hardware. - A simple and portable way to access the **best available security** of your platform in your **preferred programming language**. # What PARSEC Provides The value proposition of Parsec is that it provides the following: - **Abstraction** – a common API that is truly agnostic and based on modern cryptographic principles - **Mediation** – security as a microservice, brokering access to the hardware and providing isolated key stores in a multi-tenant environment - **Ergonomics** – a client library ecosystem that brings the API to the fingertips of developers in any programming language: “easy to consume, hard to get wrong” - **Openness** – an open-source project inviting contributions to enhance the ecosystem both within the service and among its client libraries # Maintainers PARSEC is a collaborative project. The current list of the individuals and organizations who maintain this project can be found [**here**](./MAINTAINERS.toml). # Partner Organizations See who is [**using and contributing to PARSEC**](./PARTNERS.md). # Getting Started If you are running on x86 Linux, check out [this guide](https://parallaxsecond.github.io/parsec-book/getting_started/linux_x86.html) to get started with Parsec quickly! For examples of how to access PARSEC as a client application, check [this Rust client documentation](https://docs.rs/parsec-client/*/parsec_client/core/basic_client/struct.BasicClient.html). Check the [**user**](https://parallaxsecond.github.io/parsec-book/parsec_users.html), [**client developer**](https://parallaxsecond.github.io/parsec-book/parsec_client/index.html) and [**service developer**](https://parallaxsecond.github.io/parsec-book/parsec_service/index.html) guides for more information on building, installing, testing and using Parsec! # Community Come and ask questions or talk with the Parsec Community in our Slack channel or biweekly meetings. See the [Community](https://github.com/parallaxsecond/community) repository for more information on how to join. # Contributing We would be happy for you to contribute to Parsec! Please check the [**Contribution Guidelines**](https://parallaxsecond.github.io/parsec-book/contributing/index.html) to know more about the contribution process. Check the [**open issues**](https://github.com/orgs/parallaxsecond/projects/1) on the board if you need any ideas 🙂! # Security Vulnerability Reporting Check [**PARSEC's security policy**](./SECURITY.md). # License The software is provided under Apache-2.0. Contributions to this project are accepted under the same license. parsec-service-1.3.0/SECURITY.md000064400000000000000000000032231046102023000143310ustar 00000000000000# Security policy Security is of paramount importance to the Parsec project. We do all we can to identify and fix issues, however some problems might slip through the cracks. Any efforts towards responsible disclosure of security problems are greatly appreciated and your contributions will be acknowledged. ## Supported versions Currently only the most recent version of the Parsec service is eligible for patching. This could change in the future. | Version | Supported | |------------------|-----------| | 0.7.0 and higher | ✅ | | 0.6.0 and lower | ❌ | ## Our disclosure policy All security vulnerabilities affecting the Parsec service - including those reported using the steps highlighted below, those discovered during routine testing, and those found in our dependency tree either through `cargo-audit` or otherwise - will receive [security advisories](https://github.com/parallaxsecond/parsec/security/advisories) in a timely manner. The advisories should include sufficient information about the cause, effect, and possible mitigations for the vulnerability. If any information is missing, or you would like to raise a question about the advisories, please open an issue in [our repo](https://github.com/parallaxsecond/parsec). Efforts to mitigate for the reported vulnerabilities will be tracked using Github issues linked to the corresponding advisories. ## Reporting a vulnerability To report a vulnerability, please send an email to [cncf-parsec-maintainers@lists.cncf.io](mailto:cncf-parsec-maintainers@lists.cncf.io). We will reply to acknowledge your report and we'll strive to keep you in the loop as we try to reach a resolution. parsec-service-1.3.0/build.rs000064400000000000000000000054331046102023000142120ustar 00000000000000#![allow(clippy::multiple_crate_versions, unused)] use std::env; use std::fs::read_dir; use std::io::{Error, ErrorKind, Result}; use std::path::{Path, PathBuf}; #[cfg(feature = "trusted-service-provider")] fn generate_ts_bindings(ts_include_dir: String) -> Result<()> { let header = ts_include_dir.clone() + "/components/service/locator/interface/service_locator.h"; let encoding_header = ts_include_dir.clone() + "/protocols/rpc/common/packed-c/encoding.h"; if !Path::new(&header).exists() { return Err(Error::new( ErrorKind::Other, "Trusted Services Locator header is missing. Have you run 'git submodule update --init'?", )); } println!("cargo:rerun-if-changed={}", header); let bindings = bindgen::Builder::default() .clang_arg(format!( "-I{}", ts_include_dir + "/components/rpc/common/interface" )) .rustfmt_bindings(true) .header(header) .header(encoding_header) .generate_comments(false) .size_t_is_usize(true) .derive_default(true) .generate() .map_err(|_| { Error::new( ErrorKind::Other, "Unable to generate bindings to trusted services locator", ) })?; let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); bindings.write_to_file(out_path.join("ts_bindings.rs"))?; println!("cargo:rustc-link-lib=dylib=ts"); Ok(()) } #[cfg(feature = "trusted-service-provider")] fn generate_proto_sources(contract_dir: String) -> Result<()> { let crypto_pb_dir = contract_dir.clone() + "/service/crypto/protobuf"; let dir_entries = read_dir(Path::new(&crypto_pb_dir))?; let files: Result> = dir_entries .map(|protos_file| { protos_file? .path() .into_os_string() .into_string() .map_err(|_| { Error::new( ErrorKind::InvalidData, "conversion from OsString to String failed", ) }) }) // Fail the entire operation if there was an error. .collect(); let proto_files: Vec = files? .into_iter() .filter(|string| string.ends_with(".proto")) .collect(); let files_slices: Vec<&str> = proto_files.iter().map(|file| &file[..]).collect(); prost_build::compile_protos(&files_slices, &[&contract_dir]) } #[cfg(feature = "trusted-service-provider")] fn main() -> Result<()> { { generate_ts_bindings(String::from("trusted-services-vendor"))?; generate_proto_sources(String::from("trusted-services-vendor/protocols"))?; } Ok(()) } #[cfg(not(feature = "trusted-service-provider"))] fn main() {} parsec-service-1.3.0/ci.sh000075500000000000000000000452421046102023000135010ustar 00000000000000#!/usr/bin/env bash # Copyright 2019 Contributors to the Parsec project. # SPDX-License-Identifier: Apache-2.0 set -ex # The clean up procedure is called when the script finished or is interrupted cleanup () { echo "Shutdown Parsec and clean up" # Stop Parsec if running stop_service # Stop tpm_server if running if [ -n "$TPM_SRV_PID" ]; then kill $TPM_SRV_PID || true; fi if [ -n "$TPM_MC_SRV_PID" ]; then kill $TPM_MC_SRV_PID || true; fi # Remove the slot_number line added earlier find e2e_tests -name "*toml" -not -name "Cargo.toml" -exec sed -i 's/^slot_number =.*/# slot_number/' {} \; find e2e_tests -name "*toml" -not -name "Cargo.toml" -exec sed -i 's/^serial_number =.*/# serial_number/' {} \; # Remove fake mapping and temp files rm -rf "mappings" "kim-mappings" rm -f "NVChip" rm -f "e2e_tests/provider_cfg/tmp_config.toml" rm -f "parsec.sock" if [ -z "$NO_CARGO_CLEAN" ]; then cargo clean; fi } usage () { printf " Continuous Integration test script This script will execute various tests targeting a platform with a single provider or all providers included. It is meant to be executed inside one of the container which Dockerfiles are in tests/per_provider/provider_cfg/*/ or tests/all_providers/ Usage: ./ci.sh [--no-cargo-clean] [--no-stress-test] PROVIDER_NAME where PROVIDER_NAME can be one of: - mbed-crypto - pkcs11 - tpm - trusted-service - cryptoauthlib - all - coverage - on-disk-kim " } error_msg () { echo "Error: $1" usage exit 1 } wait_for_service() { while [ -z "$(pgrep parsec)" ]; do sleep 1 done sleep 5 # Check that Parsec successfully started and is running pgrep parsec >/dev/null } stop_service() { # Randomly signals with SIGINT or SIGTERM to test that both can be used to # gracefully shutdowm Parsec. if ! (($RANDOM % 2)); then pkill -SIGINT parsec || true else pkill -SIGTERM parsec || true fi while [ -n "$(pgrep parsec)" ]; do sleep 1 done } reset_tpm() { # In order to reset the TPM, we need to restart the TPM server and send a Startup(CLEAR) pkill tpm_server sleep 1 tpm_server & TPM_SRV_PID=$! sleep 5 tpm2_startup -c -T mssim } reload_service() { echo "Trigger a configuration reload to load the new mappings or config file" pkill -SIGHUP parsec sleep 5 } run_normal_tests() { echo "Execute normal tests" RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml normal_tests } run_old_e2e_tests() { # The old client fails if ListProviders returns a ProviderID it does not know. # See https://github.com/parallaxsecond/parsec-interface-rs/issues/111 if [ "$PROVIDER_NAME" = "pkcs11" ] || [ "$PROVIDER_NAME" = "mbed-crypto" ] || [ "$PROVIDER_NAME" = "tpm" ]; then echo "Execute old end-to-end normal tests" # The version of the Parsec client used in those old tests expect the socket to be at # /tmp/parsec/parsec.sock. This can not be created in the Dockerfile as this is where # the repository is checked out. ln -s /tmp/parsec.sock /tmp/parsec/parsec.sock RUST_BACKTRACE=1 cargo test --manifest-path /tmp/old_e2e_tests/Cargo.toml normal_tests -- \ --skip asym_verify_fail --skip per_provider::normal_tests::asym_sign_verify::fail_verify_hash fi } run_key_mappings_tests() { # There is no keys generated for CryptoAuthLib yet. # This condition should be removed when the keys are generated for the CAL provider if ! [[ "$PROVIDER_NAME" = "cryptoauthlib" ]]; then echo "Execute key mappings tests" RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml key_mappings fi } setup_mappings() { # Add the Docker image's mappings in this Parsec service for the key mappings # test. # The key mappings test in e2e_tests/tests/per_provider/key_mappings.rs will try # to use the key generated via the generate-keys.sh script in the test image. # As mock Trusted Service saves its keys on the current directory we need to move them # as well. KIM_NAME=$1 if [ "$PROVIDER_NAME" = "trusted-service" ]; then # Copy the generated mappings and keys of the Trusted service cp -r /tmp/$KIM_NAME/ts-keys/* . else if [ "$KIM_NAME" = "ondisk" ]; then cp -r /tmp/$KIM_NAME/mappings/ . else cp -r /tmp/$KIM_NAME/kim-mappings/ . fi # As Mbed Crypto saves its keys on the current directory we need to move them # as well. if [ "$PROVIDER_NAME" = "mbed-crypto" ]; then cp /tmp/$KIM_NAME/*.psa_its . fi if [ "$PROVIDER_NAME" = "tpm" ]; then cp /tmp/$KIM_NAME/NVChip . fi fi } # Use the newest version of the Rust toolchain rustup update MSRV=1.66.0 # Parse arguments NO_CARGO_CLEAN= NO_STRESS_TEST= PROVIDER_NAME= CONFIG_PATH=$(pwd)/e2e_tests/provider_cfg/tmp_config.toml while [ "$#" -gt 0 ]; do case "$1" in --no-cargo-clean ) NO_CARGO_CLEAN="True" ;; --no-stress-test ) NO_STRESS_TEST="True" ;; mbed-crypto | pkcs11 | tpm | trusted-service | cryptoauthlib | all | cargo-check | on-disk-kim) if [ -n "$PROVIDER_NAME" ]; then error_msg "Only one provider name must be given" fi PROVIDER_NAME=$1 # Copy provider specific config, unless CI is running `cargo-check` or `on-disk-kim` CI if [ "$PROVIDER_NAME" != "cargo-check" ] && [ "$PROVIDER_NAME" != "on-disk-kim" ]; then cp $(pwd)/e2e_tests/provider_cfg/$1/config.toml $CONFIG_PATH elif [ "$PROVIDER_NAME" = "on-disk-kim" ]; then PROVIDER_NAME=all cp $(pwd)/e2e_tests/provider_cfg/all/on-disk-kim-all-providers.toml $CONFIG_PATH fi if [ "$PROVIDER_NAME" = "all" ] || [ "$PROVIDER_NAME" = "cargo-check" ]; then FEATURES="--features=all-providers,all-authenticators" TEST_FEATURES="--features=all-providers" else FEATURES="--features=$1-provider,direct-authenticator" TEST_FEATURES="--features=$1-provider" fi ;; coverage ) PROVIDER_NAME=$1 ;; *) error_msg "Unknown argument: $1" ;; esac shift done # Check if the PROVIDER_NAME was given. if [ -z "$PROVIDER_NAME" ]; then error_msg "a provider name needs to be given as input argument to that script." fi trap cleanup EXIT if [ "$PROVIDER_NAME" = "tpm" ] || [ "$PROVIDER_NAME" = "all" ] || [ "$PROVIDER_NAME" = "coverage" ]; then # Copy the NVChip for previously stored state. This is needed for the key mappings test. cp /tmp/ondisk/NVChip . # Start and configure TPM server tpm_server & TPM_SRV_PID=$! sleep 5 # The -c flag is not used because some keys were created in the TPM via the generate-keys.sh # script. Ownership has already been taken with "tpm_pass". tpm2_startup -T mssim # Start and configure TPM server for MakeCredential TPM_MC_PORT=4321 mkdir -p /tmp/mc_tpm pushd /tmp/mc_tpm tpm_server -port $TPM_MC_PORT & TPM_MC_SRV_PID=$! sleep 5 tpm2_startup -c -T mssim:port=$TPM_MC_PORT popd fi if [ "$PROVIDER_NAME" = "pkcs11" ] || [ "$PROVIDER_NAME" = "all" ] || [ "$PROVIDER_NAME" = "coverage" ]; then pushd e2e_tests # This command suppose that the slot created by the container will be the first one that appears # when printing all the available slots. SLOT_NUMBER=`softhsm2-util --show-slots | head -n2 | tail -n1 | cut -d " " -f 2` SERIAL_NUMBER=`softhsm2-util --show-slots | grep "Serial number:*" | head -n1 | egrep -ow "[0-9a-zA-Z]+" | tail -n1` # Find all TOML files in the directory (except Cargo.toml) and replace the commented slot number with the valid one find . -name "*toml" -not -name "Cargo.toml" -exec sed -i "s/^# slot_number.*$/slot_number = $SLOT_NUMBER/" {} \; find . -name "*toml" -not -name "Cargo.toml" -exec sed -i "s/^# serial_number.*$/serial_number = \"$SERIAL_NUMBER\"/" {} \; popd fi # Initialising any submodules. Currently used for building the Trusted Service provider git submodule update --init if [ "$PROVIDER_NAME" = "coverage" ]; then rustup toolchain install ${MSRV} PROVIDERS="trusted-service mbed-crypto tpm pkcs11" EXCLUDES="fuzz/*,e2e_tests/*,src/providers/cryptoauthlib/*,src/authenticators/jwt_svid_authenticator/*" UNIT_TEST_FEATURES="unix-peer-credentials-authenticator,direct-authenticator" # Install tarpaulin # TODO: Stop using the --version parameter when MSRV is upgraded. cargo +${MSRV} install cargo-tarpaulin --version 0.26.1 mkdir -p reports for provider in $PROVIDERS; do # Set up run PROVIDER_NAME=$provider TEST_FEATURES="--features=$provider-provider" UNIT_TEST_FEATURES="$UNIT_TEST_FEATURES,$provider-provider" cp $(pwd)/e2e_tests/provider_cfg/$provider/config.toml $CONFIG_PATH mkdir -p reports/$provider setup_mappings ondisk cp -r $(pwd)/e2e_tests/fake_mappings/* mappings # Start service RUST_LOG=info cargo +${MSRV} tarpaulin --out Xml --forward --command build --exclude-files="$EXCLUDES" \ --output-dir $(pwd)/reports/$provider --features="$provider-provider,direct-authenticator" \ --run-types bins --timeout 3600 -- -c $CONFIG_PATH & wait_for_service # Run tests run_normal_tests run_key_mappings_tests stop_service cp $(pwd)/e2e_tests/provider_cfg/$PROVIDER_NAME/config-sqlite.toml $CONFIG_PATH setup_mappings sqlite if [ "$PROVIDER_NAME" = "tpm" ]; then reset_tpm fi # Start service RUST_LOG=info cargo +${MSRV} tarpaulin --out Xml --forward --command build --exclude-files="$EXCLUDES" \ --output-dir $(pwd)/reports/$provider --features="$provider-provider,direct-authenticator" \ --run-types bins --timeout 3600 -- -c $CONFIG_PATH & wait_for_service run_key_mappings_tests stop_service done # Run unit tests mkdir -p reports/unit cargo +${MSRV} tarpaulin --tests --out Xml --features=$UNIT_TEST_FEATURES --exclude-files="$EXCLUDES" --output-dir $(pwd)/reports/unit exit 0 fi if [ "$PROVIDER_NAME" = "all" ]; then # Start SPIRE server and agent pushd /tmp/spire-0.11.1 ./bin/spire-server run -config conf/server/server.conf & sleep 2 TOKEN=`bin/spire-server token generate -spiffeID spiffe://example.org/myagent | cut -d ' ' -f 2` ./bin/spire-agent run -config conf/agent/agent.conf -joinToken $TOKEN & sleep 2 # Register parsec-client-1 ./bin/spire-server entry create -parentID spiffe://example.org/myagent \ -spiffeID spiffe://example.org/parsec-client-1 -selector unix:uid:$(id -u parsec-client-1) # Register parsec-client-2 ./bin/spire-server entry create -parentID spiffe://example.org/myagent \ -spiffeID spiffe://example.org/parsec-client-2 -selector unix:uid:$(id -u parsec-client-2) sleep 5 popd fi echo "Build test" if [ "$PROVIDER_NAME" = "cargo-check" ]; then # We test that everything in the service still builds with the current Rust stable # and an old Rust compiler. # The old Rust compiler version is found by manually checking the oldest Rust version of all # Linux distributions that we support: # - Fedora 36 and more recent releases # - RHEL-9 (intend to support in the future) # - openSUSE Tumbleweed # - openSUSE Leap 15.4 rustup toolchain install ${MSRV} # TODO: The "jwt-svid-authenticator" is currently not being used. RUST_BACKTRACE=1 cargo +${MSRV} check --release --features=all-providers,direct-authenticator,unix-peer-credentials-authenticator # Latest stable rustup toolchain install stable RUST_BACKTRACE=1 cargo +stable check --release $FEATURES # We test that each feature still exist. RUST_BACKTRACE=1 cargo check RUST_BACKTRACE=1 cargo check --features="mbed-crypto-provider" RUST_BACKTRACE=1 cargo check --features="pkcs11-provider" RUST_BACKTRACE=1 cargo check --features="tpm-provider" RUST_BACKTRACE=1 cargo check --features="cryptoauthlib-provider" RUST_BACKTRACE=1 cargo check --features="trusted-service-provider" RUST_BACKTRACE=1 cargo check --features="all-providers" RUST_BACKTRACE=1 cargo check --features="direct-authenticator" RUST_BACKTRACE=1 cargo check --features="unix-peer-credentials-authenticator" RUST_BACKTRACE=1 cargo check --features="jwt-svid-authenticator" RUST_BACKTRACE=1 cargo check --features="all-authenticators" exit 0 fi RUST_BACKTRACE=1 cargo build $FEATURES echo "Static checks" # On native target clippy or fmt might not be available. if rustup component list | grep -q fmt; then cargo fmt --all -- --check cargo fmt --all --manifest-path e2e_tests/Cargo.toml -- --check fi if rustup component list | grep -q clippy; then cargo clippy --all-targets $FEATURES -- -D clippy::all -D clippy::cargo cargo clippy --all-targets $TEST_FEATURES --manifest-path e2e_tests/Cargo.toml -- -D clippy::all -D clippy::cargo fi echo "Unit, doc and integration tests" RUST_BACKTRACE=1 cargo test $FEATURES # Removing any mappings or on disk keys left over from integration tests rm -rf mappings/ rm -f *.psa_its echo "Start Parsec for end-to-end tests" RUST_LOG=info RUST_BACKTRACE=1 cargo run --release $FEATURES -- --config $CONFIG_PATH & # Sleep time needed to make sure Parsec is ready before launching the tests. wait_for_service if [ "$PROVIDER_NAME" = "all" ]; then echo "Execute all-providers normal tests" RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml all_providers::normal echo "Execute all-providers cross tests" RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml all_providers::cross echo "Execute all-providers multi-tenancy tests" # Needed because parsec-client-1 and 2 write to those locations owned by root chmod 777 /tmp/parsec/e2e_tests chmod 777 /tmp/ chmod -R 775 /opt/rust/registry chgrp -R parsec-clients /opt/rust/registry # PATH is defined before each command for user to use their own version of the Rust toolchain su -c "PATH=\"/home/parsec-client-1/.cargo/bin:${PATH}\";RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml --target-dir /home/parsec-client-1 all_providers::multitenancy::client1_before" parsec-client-1 su -c "PATH=\"/home/parsec-client-2/.cargo/bin:${PATH}\";RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml --target-dir /home/parsec-client-2 all_providers::multitenancy::client2" parsec-client-2 su -c "PATH=\"/home/parsec-client-1/.cargo/bin:${PATH}\";RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml --target-dir /home/parsec-client-1 all_providers::multitenancy::client1_after" parsec-client-1 # Change the authentication method sed -i 's/^\(auth_type\s*=\s*\).*$/\1\"UnixPeerCredentials\"/' $CONFIG_PATH reload_service su -c "PATH=\"/home/parsec-client-1/.cargo/bin:${PATH}\";RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml --target-dir /home/parsec-client-1 all_providers::multitenancy::client1_before" parsec-client-1 su -c "PATH=\"/home/parsec-client-2/.cargo/bin:${PATH}\";RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml --target-dir /home/parsec-client-2 all_providers::multitenancy::client2" parsec-client-2 su -c "PATH=\"/home/parsec-client-1/.cargo/bin:${PATH}\";RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml --target-dir /home/parsec-client-1 all_providers::multitenancy::client1_after" parsec-client-1 # Change the authentication method sed -i 's/^\(auth_type\s*=\s*\).*$/\1\"JwtSvid\"/' $CONFIG_PATH sed -i 's@#workload_endpoint@workload_endpoint@' $CONFIG_PATH pkill -SIGHUP parsec sleep 5 su -c "PATH=\"/home/parsec-client-1/.cargo/bin:${PATH}\";RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml --target-dir /home/parsec-client-1 all_providers::multitenancy::client1_before" parsec-client-1 su -c "PATH=\"/home/parsec-client-2/.cargo/bin:${PATH}\";RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml --target-dir /home/parsec-client-2 all_providers::multitenancy::client2" parsec-client-2 su -c "PATH=\"/home/parsec-client-1/.cargo/bin:${PATH}\";RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml --target-dir /home/parsec-client-1 all_providers::multitenancy::client1_after" parsec-client-1 # Last test as it changes the service configuration echo "Execute all-providers config tests" RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml all_providers::config -- --test-threads=1 else setup_mappings ondisk # Add the fake mappings for the key mappings test as well. The test will check that # those keys have successfully been deleted. # TODO: add fake mappings for the CryptoAuthLib provider. cp -r $(pwd)/e2e_tests/fake_mappings/* mappings reload_service # Per provider tests run_normal_tests run_old_e2e_tests run_key_mappings_tests if [ -z "$NO_STRESS_TEST" ]; then echo "Shutdown Parsec" stop_service echo "Start Parsec for stress tests" # Change the log level for the stress tests because logging is limited on the # CI servers. RUST_LOG=error RUST_BACKTRACE=1 cargo run --release $FEATURES -- --config $CONFIG_PATH & wait_for_service echo "Execute stress tests" RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml stress_test fi # For the TPM provider we check that keys can still be used after a TPM Reset if [ "$PROVIDER_NAME" = "tpm" ]; then # We first create the keys RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml before_tpm_reset stop_service reset_tpm # We then spin up the service again and check that the keys can still be used RUST_LOG=error RUST_BACKTRACE=1 cargo run --release $FEATURES -- --config $CONFIG_PATH & wait_for_service RUST_BACKTRACE=1 cargo test $TEST_FEATURES --manifest-path ./e2e_tests/Cargo.toml after_tpm_reset fi cp $(pwd)/e2e_tests/provider_cfg/$PROVIDER_NAME/config-sqlite.toml $CONFIG_PATH setup_mappings sqlite if [ "$PROVIDER_NAME" = "tpm" ]; then reset_tpm fi reload_service run_key_mappings_tests fi parsec-service-1.3.0/config.toml000064400000000000000000000377011046102023000147120ustar 00000000000000# Parsec Configuration File # (Required) Core settings apply to the service as a whole rather than to individual components within it. [core_settings] # Whether or not to allow the service to run as the root user. If this is false, the service will refuse to # start if it is run as root. If this is true, the safety check is disabled and the service will be allowed to # start even if it is being run as root. The recommended (and default) setting is FALSE; allowing Parsec to # run as root violates the principle of least privilege. #allow_root = false # Size of the thread pool used for processing requests. Defaults to the number of processors on # the machine. #thread_pool_size = 8 # Duration of sleep when the connection pool is empty. This can limit the response # times for requests and so should be set to a low number. Default value is 10. #idle_listener_sleep_duration = 10 # in milliseconds # Log level to be applied across the service. Can be overwritten for certain modules which have the same # configuration key. Possible values: "debug", "info", "warn", "error", "trace" # WARNING: This option will not be updated if the configuration is reloaded with a different one. #log_level = "warn" # Control whether log entries contain a timestamp. #log_timestamp = false # Decide how large (in bytes) request bodies can be before they get rejected automatically. # Defaults to 1MB. #body_len_limit = 1048576 # Decide whether detailed information about errors occuring should be included in log messages. # WARNING: the details might include sensitive information about the keys used by Parsec clients, # such as key names or policies #log_error_details = false # Decide how large (in bytes) buffers inside responses from this provider can be. Requests that ask # for buffers larger than this threshold will be rejected. Defaults to 1MB. #buffer_size_limit = 1048576 # Decide whether deprecated algorithms and key types are allowed when generating keys or not. # Note: While importing a deprecated key, only a warning log is generated. # The default behaviour is to reject the deprecated primitives. Hence, the default value is false. #allow_deprecated = false # (Required) Configuration for the service IPC listener component. [listener] # (Required) Type of IPC that the service will support. listener_type = "DomainSocket" # (Required) Timeout of the read and write operations on the IPC channel. After the # timeout expires, the connection is dropped. timeout = 200 # in milliseconds # Specify the Unix Domain Socket path. The path is fixed and should always be the default one for # clients to connect. However, it is useful to change it for tests. # WARNING: If a file already exists at that path, the service will remove it before creating the # socket file. #socket_path = "/run/parsec/parsec.sock" # (Required) Authenticator configuration. # WARNING: the authenticator MUST NOT be changed if there are existing keys stored in Parsec. # In a future version, Parsec might support multiple authenticators, see parallaxsecond/parsec#271 # for details. [authenticator] # (Required) Type of authenticator that will be used to authenticate clients' authentication # payloads. # Possible values: "Direct", "UnixPeerCredentials" and "JwtSvid". # WARNING: The "Direct" authenticator is only secure under specific requirements. Please make sure # to read the Recommendations on a Secure Parsec Deployment at # https://parallaxsecond.github.io/parsec-book/parsec_security/secure_deployment.html auth_type = "UnixPeerCredentials" # List of admins to be identified by the authenticator. # The "name" field of each entry in the list must contain the application name (as required by the # identifier in `auth_type`). For example, for `UnixPeerCredentials`, the names should be UIDs of # the admin users. # WARNING: Admins have special privileges and access to operations that are not permitted for normal # users of the service. Only enable this feature with some list of admins if you are confident # about the need for those permissions. # Read more here: https://parallaxsecond.github.io/parsec-book/parsec_client/operations/index.html#core-operations #admins = [ { name = "admin_1" }, { name = "admin_2" } ] # (Required only for JwtSvid) Location of the Workload API endpoint # WARNING: only use this authenticator if the Workload API socket is TRUSTED. A malicious entity # owning that socket would have access to all the keys owned by clients using this authentication # method. This path *must* be trusted for as long as Parsec is running. #workload_endpoint="unix:///run/spire/sockets/agent.sock" # (Required) Configuration for the components managing key info for providers. # Defined as an array of tables: https://github.com/toml-lang/toml#user-content-array-of-tables [[key_manager]] # (Required) Name of the key info manager. Used to tie providers to the manager supporting them. name = "sqlite-manager" # (Required) Type of key info manager to be used. # Possible values: "SQLite", "OnDisk" # NOTE: The SQLite KIM is now the recommended type, with the OnDisk KIM to be deprecated at some # point in the future. manager_type = "SQLite" # Path to the location where the database will be persisted #store_path = "/var/lib/parsec/kim-mappings/sqlite/sqlite-key-info-manager.sqlite3" # Example of OnDisk Key Info Manager configuration #[[key_manager]] # (Required) Name of the key info manager. #name = "on-disk-manager" # (Required) Type of key info manager to be used. #manager_type = "OnDisk" # Path to the location where the mappings will be persisted (in this case, the filesystem path) #store_path = "/var/lib/parsec/mappings" # (Required) Provider configurations. # Defined as an array of tables: https://github.com/toml-lang/toml#user-content-array-of-tables # IMPORTANT: The order in which providers below are declared matters: providers should be listed # in terms of priority, the highest priority provider being declared first in this file. # The first provider will be used as default provider by the Parsec clients. See below example # configurations for the different providers supported by the Parsec service. # Example of an Mbed Crypto provider configuration. [[provider]] # ⚠ # ⚠ WARNING: Provider name cannot change. # ⚠ WARNING: Choose a suitable naming scheme for your providers now. # ⚠ WARNING: Provider name defaults to "mbed-crypto-provider" if not provided, you will not be able to change # ⚠ the provider's name from this if you decide to use the default. # ⚠ WARNING: Changing provider name after use will lead to loss of existing keys. # ⚠ # (Optional) The name of the provider name = "mbed-crypto-provider" # (Required) Type of provider. provider_type = "MbedCrypto" # (Required) Name of key info manager that will support this provider. # NOTE: The key info manager only holds mappings between Parsec key name and Mbed Crypto ID, along # with other metadata associated with the key. The keys themselves, however, are stored by the Mbed # Crypto library by default within the working directory of the service, NOT in the same location # as the mappings mentioned previously. If you want the keys to be persisted across reboots, ensure # that the working directory is not temporary. key_info_manager = "sqlite-manager" # Example of a PKCS 11 provider configuration #[[provider]] # ⚠ # ⚠ WARNING: Provider name cannot change. # ⚠ WARNING: Choose a suitable naming scheme for your providers now. # ⚠ WARNING: Provider name defaults to "pkcs11-provider" if not provided, you will not be able to change # ⚠ the provider's name from this if you decide to use the default. # ⚠ WARNING: Changing provider name after use will lead to loss of existing keys. # ⚠ # (Optional) The name of the provider #name = "pkcs11-provider" #provider_type = "Pkcs11" #key_info_manager = "sqlite-manager" # (Required for this provider) Path to the location of the dynamic library loaded by this provider. # For the PKCS 11 provider, this library implements the PKCS 11 API on the target platform. #library_path = "/usr/local/lib/softhsm/libsofthsm2.so" # (Optional) PKCS 11 serial number of the token that will be used by Parsec. # If the token serial number is entered, then the slot that has the provided serial number will be used. Otherwise, if both `serial_number` and `slot_number` are given but do not match, a warning is issued and serial number takes precedence. # Note: Matching the serial_number done after trimming the leading and trailing whitespaces for serial numbers shorter than 16 charachter. #serial_number = "0123456789abcdef" # (Optional) PKCS 11 slot that will be used by Parsec If Token serial number is not entered. i.e, serial_number is preferred # If the slot number is not entered and there is only one slot available - with a valid token - it will be automatically used #slot_number = 123456789 # (Optional) User pin for authentication with the specific slot. If not set, the sessions will not # be logged in. It might prevent some operations to execute successfully on some tokens. #user_pin = "123456" # (Optional) Control whether missing public key operation (such as verifying signatures or asymmetric # encryption) are fully performed in software. #software_public_operations = false # (Optional) Control whether it is allowed for a key to be exportable. On some platforms creating a # key that can be exported will fail with an obscure error. If this flag is set to false, creating # a key with its export usage flag set to true will return a PsaErrorNotPermitted error. #allow_export = true # Example of a TPM provider configuration #[[provider]] # ⚠ # ⚠ WARNING: Provider name cannot change. # ⚠ WARNING: Choose a suitable naming scheme for your providers now. # ⚠ WARNING: Provider name defaults to "tpm-provider" if not provided, you will not be able to change # ⚠ the provider's name from this if you decide to use the default. # ⚠ WARNING: Changing provider name after use will lead to loss of existing keys. # ⚠ # (Optional) The name of the provider #name = "tpm-provider" #provider_type = "Tpm" #key_info_manager = "sqlite-manager" # (Required) TPM TCTI device to use with this provider. The string can include configuration values - if no # configuration value is given, the defaults are used. Options are: # - "device": uses a TPM device available as a file node; path can be given as a configuration string, # e.g "device:/path/to/tpm". The default path is /dev/tpm0, but this default is only suitable in deployments # where Parsec would have exclusive usage of the device, and where Parsec is executing at a sufficiently high # privilege for such access. It is more common for the TPM device to be managed by an Access Broker / Resource # Manager (ABRM) component, either within the kernel or via a userspace daemon (the TABRMD). Trying to # use /dev/tpm0 directly in such cases will lead to "device busy" errors on service start-up. Instead, Parsec should # normally be configured to access the TPM via the suitable ABRM. To use the in-kernel ABRM, the "device" # setting should be configured to use the managed TPM device path, typically /dev/tpmrm0. Permissions on this # device are normally less restrictive. In most Linux distributions, this device can be accessed by any user # within the "tss" group, so whatever user the Parsec service is running as should be made a member of this group. # To use the userspace ABRMD, adopt the "tabrmd" setting below, instead of "device". # - "mssim": uses the TPM simulator server with the socket; server path and/or port can be given as configuration values, # e.g. "mssim:host=168.0.1.1,port=1234"; "host" can be set to IPv4, IPv6 or a hostname; default values are # "localhost" for "host" and 2321 for "port" # - "tabrmd": uses the TPM2 Access Broker & Resource Management Daemon; dbus name and type ("session" or # "system") can be given as parameters: e.g. "tabrmd:bus_name=some.bus.Name,bus_type=session"; default # values are "com.intel.tss2.Tabrmd" for "bus_name" and "system" for "bus_type" #tcti = "mssim" # (Required) Authentication value for performing operations on the TPM Owner Hierarchy. The string can # be empty, however we strongly suggest that you use a secure passcode. # To align with TPM tooling, PARSEC allows "owner_hierarchy_auth" to have a prefix indicating a string value, # e.g. "str:password", or to represent a string version of a hex value, e.g. "hex:1a2b3c". If no prefix is # provided, the value is considered to be a string. #owner_hierarchy_auth = "password" # (Optional) Authentication value for performing operations on the TPM Endorsement Hierarchy. The string can # be empty, however we strongly suggest that you use a secure passcode. # This authentication value is used for operations that rely on keys stored in the Endorsement Hierarchy, e.g. # key attestation using the default Endorsement Key. # To align with TPM tooling, PARSEC allows "endorsement_hierarchy_auth" to have a prefix indicating a string value, # e.g. "str:password", or to represent a string version of a hex value, e.g. "hex:1a2b3c". If no prefix is # provided, the value is considered to be a string. #endorsement_hierarchy_auth = "password" # (Optional) Allows the service to still start without this provider if there is no TPM on the system. The priority list of providers will be as if this provider was commented out. #skip_if_no_tpm = false # Example of a CryptoAuthLib provider configuration # All below parameters depend on what devices, interfaces or parameters are required or supported by # "rust-cryptoauthlib" wrapper for cryptoauthlib and underlying hardware. #[[provider]] # ⚠ # ⚠ WARNING: Provider name cannot change. # ⚠ WARNING: Choose a suitable naming scheme for your providers now. # ⚠ WARNING: Provider name defaults to "cryptoauthlib-provider" if not provided, you will not be able to change # ⚠ the provider's name from this if you decide to use the default. # ⚠ WARNING: Changing provider name after use will lead to loss of existing keys. # ⚠ # (Optional) The name of the provider #name = "cryptoauthlib-provider" #provider_type = "CryptoAuthLib" #key_info_manager = "sqlite-manager" ########## # (Required) Interface for ATCA device # Supported values: "i2c", "test-interface" #iface_type = "i2c" ########## # (Required) ATCA device type. # Supported values: "atecc508a", "atecc608a", "always-fail", "always-success", "fail-unimplemented" #device_type = "atecc508a" ########## # (Optional) Default wake delay for ATCA device # - required for hardware interfaces (e.g. i2c) #wake_delay = 1500 ########## # (Optional) Default number of rx retries for ATCA device # - required for hardware interfaces (e.g. i2c) #rx_retries = 20 ########## # (Optional) i2c slave addres # - required for i2c #slave_address = 0xc0 ########## # (Optional) i2c bus number # - required for i2c #bus = 1 ########## # (Optional) i2c bus baud rate # - required for i2c #baud = 400000 ########## # (Optional) Atecc access key configuration file # - required for i2c # - this file contains potentially sensitive data, it should be stored in folder where only 'parsec' user may access it #access_key_file_name = "/etc/parsec/cal_access_keys.toml" ########### # Tree: # iface_type = ["test-interface", "i2c"] # # iface_type = "test-interface" # device_type = ["always-fail", "always-success", "fail-unimplemented"] # iface_type = "i2c" # device_type = ["atecc508a", "atecc608a"] # wake_delay = # rx_retries = # slave_address = # bus = # baud = # Example of a Trusted Service provider configuration. #[[provider]] # ⚠ # ⚠ WARNING: Provider name cannot change. # ⚠ WARNING: Choose a suitable naming scheme for your providers now. # ⚠ WARNING: Provider name defaults to "trusted-service-provider" if not provided, you will not be able to change # ⚠ the provider's name from this if you decide to use the default. # ⚠ WARNING: Changing provider name after use will lead to loss of existing keys. # ⚠ # (Optional) The name of the provider #name = "trusted-service-provider" # (Required) Type of provider. #provider_type = "TrustedService" # (Required) Name of key info manager that will support this provider. #key_info_manager = "sqlite-manager" parsec-service-1.3.0/doc/images/parsec/ARM1007_PARSEC Logo_ST2_RGB_Stacked_Colour.png000064400000000000000000000775511046102023000255640ustar 00000000000000PNG  IHDRi\hK pHYs%%IR$ IDATxuX6ӽ?D:V0G"1|lxhcK*AD~!DSͻW[4 !`OvPPs3Kg}d%0! Է}AuRJ=η,}y}ؿ].`WM_SJODaDS>AEb!54{|.X'Mu}m7gK A %`r^9i b. m̚Yu^/(y҇I|-Xz_Su"l꼩b"m5UWulQ:cS_;Izږױ),mokZ:mDc3b>l eiJm:0hq3su5̈IDюZ{-j]b_wp'K0X! iԦ-hnoYQ}`f4L*`0͆ͭlSw)Øy i(aQ9ÙQ!cAYCÚg="8tRz:0VnY>Gmt9 4;-.7554;8u#4! n]n.,Z"q% ivn!Owo1h _&giivnAJD{{tH=0 &'؍/>SJ,Z텨k!e]28PE1;{64gLKH?a=M?Ŀ;|Iϝ7ݟm.:.xOԄ4pKT/\N4 u]m|\==? h~ؿ^:޴`9̤U h[t,B,>h0[g>EѻVT\Z|k \wB&%(`HV,]J1{y%i{gl౧ i t4JS A{Q]@t`74eibUʔã!W6U,~5%;Ň fE̽3}U87,}o۸G|9A9/֧z@1w{JY}H^ Bk}C@_6j!KݦY*2\ \mO7aM37'IWJFn癏&&8lny9iyX3iv#4yS={SN*(BHzd^{%W )\,`Gr==pjpBjfW!uA{)§599 *jh bjm>4̂`r5/PÊ܈RN@\i&Tl!;o*Dh*?F;pT@\ILU_uA?~\5sT9snAJݞln|L f/~ ( BIuGMeW]`PH h+! D KL4i  (@HP! @B4i  (7U0⪩{O &:airTăiw󥂆:aB4i  (@HPo6`GX?UuJ|ܗ8pJn̏^m:L)5UˎJ~|q~X=! ?ghW`4nQ ߥN!{?n p4hn\>^8Q\V7ps#r9[nuϰ$> T)'=v"4w4YÞU5OJB*} {}JH@֧sS>*iX-! ,M=Cbe|=K)z0{ۜa`U4|yeݚ$BQJCݦSϓ:xꡟU| 3e ſk`iXmz`NjҿšJEEs sjOTm}h`4V,5ӇrOR,5M]͈ßG*XzvBV)[3MKZ75U׾sjFThi,\Sup7qX5! 3?"?j?*]Dre'B1m8 iX>]82h(N_>emԼ%! +:Z~R3ܗSS|.#['#(gSC iXZ~'k{v#^qhsiN wz#Cosn&C`Y/s>PhRMյ/cDj&_3Z9wϾcg^=f kǞΜ4U/%8emU^w-vsz>mjJ /–+n{?SIZ @H+T*I)}qׯr95h'0bBnT]7#s5L*Z]F !%"4ŷyw/XG溿Mյ/G̩U WQ;>-u0_B֢4{~nskAͣlP1&G_8<fKHZ\ϋ*Dq*o1b2u栦t6{dyeMSϣ`΄4Eߐ&_b@aMEIv4{eaT np˜"~"V~0{BV!B!q5OJ Z G rϣƊD /#0Z"iX#fuT]%4U.9brCP8h׌ؠܪ` 6! mrq}mU[nm7Nv4P߹#.3,U*;}xaU *\,=-.eP/E,5:H_˞ƜÁ|nm7nVmjUZ]`4NTn:jzġr95(Z}yC|ђMH*EQJk4^qD 5jsɭh'`՚*N)kd;o1c5/vqZCݦ-ȭr ojs}74^ShÚ?NqƙHm6(Xhyw-wo'GkRBl]s}۫8^ثXhu~E4pr8~4wh}Y=<=->h {KHl i3Orj_Ek#ncTZ GkB95UT? +M׾n#n]Su-Ζ g4,Ie[d='{.<vWn:Z-FT Zs`! KcɌꇦ*ތXq?,T1&=Z-†B0CZ29橩e959-OkF4EA[~.Bb_2Ͼ \d~_*-rk%͟U-dg;Z =?fnEJ 4Ѿr䌒wKQ&uR~o<7Qq .-`BfKcw|L)x-Ο9]"SBf' |{kS]U5vwhag6[\0Kk+]VTGSݱ1h *i;2,}+@궫y*k;a6/6NZdeQ`Y4쥁3[nmf2>f|zcMՅ}ϙ+UŃ|E`u nU iNw,vgK{-y{"=Z}Ϯ\?Gm=4MEg )B>Nwy,̆\D sjO%L`mjG[7 \8{T"C?G@`Y;j9̒a& ȿ?/5_6^?*nksjġ_#4lk姮4h4/T]j1usfKWÇň>Rl&nb$x4;! lђLaK=Á}Z mg> YSI9QA_{-fs3X xvhMU&0f 6f><-^‡ T$Z{}7MZ~ߛu ';x>40U4U,i+%z6)O#T(Y:GιYDVe~IzZ1J\?r-A6lXnӫ[405UDQ5ۇX.G̟an-EQ5?uRyPaw;D2eISu/!^̟luR:]‹V5,.|esixDџK}4PP"j1m4ղ*M5XKu{}m+B(,^j#VldaPmw |=؄fbM6hb,lزtagU><%"~09\q"D5ALC5uUA_F4"Tۛ&|} hG*i`fV1r|1tR(B#]\Ѻrd뷟I m]S%=׺vġ98jteæ6 nˡ*_5"[bXu۵z?(5;^_\[P0,#ԜG@3Ii`Re !ZEp1gw'1{|&`-4s2٦.V`lXvkF5m>wGsxsŔHݦ{"L'#V*]٠6~?5! ,ܪLvTKȶkUVi`R ΃AŐTT]b\\K&ZҬM,-/Vl8p3c|n.vW D@0D:.#X#6{m:M)}Ы89Q@=_TE{2 Z|}jiQ4Yx_&ԟC[i`DА#fqsH6h?mb.G\hTv}65^,i~,ȶL,*J.GΏJnnRu۵|4_GQo*>`-̤=;/>!a"QT hK4aHhYKSu3s~I)+vt=` 462)B L!V}&*r.z~?̦QI{h*]NL7; ܻZ5UC@BS(louUݦQL@2@3ӧ]^[T|u462.붫Yr0m 뽎V_g3>b`ԧvDH+U&*)6f,BzFz[7,>!s=>3tГV1U&%=ä FzNaϾ+ VMH+aISiKz{9`]i ! ! ̆U&/6]0)GVou:sէh|MH+5\F*rxG>!͓M}!qs$*?{Ti5Kٽh1C>?F3w}n﯐tRy% ,/߿`墪}/C B uM}ћُu궫ܣ3+ؑsYhcv}ܞ)S g %GvU1}f(G.og54+B%V3bgEPg=ˑm6`G~b)끛$  a1'h mRz1n i;E5L/3O> F2B]k[ Wʕ5GyJ 궫`yU4@/uRJ/FV9/°DžY LKHV$Gw:wr7)x.Fg^"`BB`h929mg+:1cw<(GH iGSusWVoIuR~o+M0B` g*5?1ۮ*Ոs|AWkzncG~T] 46E]$Z]l`OAj.xȳO n?lM-`4mrOnoG̟oX`ϙIL,#PSݦӥ=MG)u=4X! SѶ0f 6f,BJFTpC8ڝtRz1z7!lGt<Џ1g손Tvsj9=MG75Z!&/G9<'c7L`rQ SE!rnO-*.G49h]@Va9x>b )\"$z5#[JYJxB%`4,DsjD@ۛ=q7MUE0/B`6bȠtWs^.`~jhc6DO*q**|hEJ` 7~R:}^8tY92 i٪tRz;b}9$9jt5bL8{iwVSu,l!pn{5fL@R@N.RJOF;&Bgw 45UjSꂚ#|{mWiD@s3F@ X\Gs̩0]!B`qі,WMnV hmQI셺Mg);1/`[4ިnLyTԷ6! WbNvf?gNyuB`mzAӑCg]&fGהҁ54^:=G|+]{nIJ95o t&!VnS?{֕VNO= = #0LF2= FP^:D!=aJH4E4o9;ixy4Y+ΟjT}B 4i*T @HP! @B 4i*T @HP! @B 4i*T @HP! @B 4i*T @HP! @B 4i*T @HP! @B 4i*T @HP! @B 4i*T @HP! @B 4i*T @HP! @B 4i*T @HP! @B 4i*T @HP! @B 4i*T @HP! @B 4i*T @HP! @B 4i*T @HP! @ttiOx*G/>-liW_Z{7~s45ɧNr5&j#d}Cd?lloFIq,\?y0;vcxpV둲>\?o&! 0m<Us4Q Xg0ncR#W"%ytvX_-^h2Uf) ^+:UzU֨@zb4^{%>_kڰ\̳+bda=vb rݿ=!ytjڜG +SKg#Vo>U>*e$aO\hjLg8hn9+nҼ :g25Mr1?u޸NnU(s26 W%t5 mЦ3)iAt,dޘߌR=hvNcL#z^eDL9MYBgX! ٢88 5Ҽh?-?-[l2fqnydqѳ@X! YkXqȓgg{p4/d!3Zf:2xTvky/#.:b.#0xk]3'#9c&mtלM$AHæňvk+aM=Ba:ۈah&٧ Rca1x3Sg8ͻ[ kΚ6ikTšJ4/d!ÚkNHw*[ւۨSA<6Lcƚ6ix9Iwkf-Ҽ Ƈ4JA~\sQZUǟ kLu>;HeMB4.UD31RF51?1bd~*EJDze/lY}K~<d#BVd:(;Wob_"R$9Bk+Fz-uP`4P#]:* 7b?ExHBiޕNhT@ĸ`VjBk6n-Q:,>7⽊RskKAHݱ3}Ѻ8z^ IDATW~yvg.V:h>7ϷAngg1 mZӢ.WZ{7r1\|?}~!n,tZPDsPZ7bP &7&ә Hi5f2}Q utRPCUW"XUuëGx@S:e.O[20~ڡ,ዬIhr1?qǧGE";\u}n2jKPBC0 M jvfjó{rw$#~v81rU ::^d̜ ) l6ƚЃrPߋ1!Á{oglZE7M# ʇE HO_屺6fmc/U=cTdĚKyw2N(,)c]N#$;N'v ]<6P AEZ+Rqzgi5IB:i# .a2>vuj?z0Ǹ9wm9sIG>GTk`:{kQaY{<@HBB/ʗOżUPnlB2(6D):mVk6αo&YY7xeJ8s\ϼ:_,w$ZcǺxO:>=~gANce⃂.(xqamć+lqU<_-aɀ ^tsTEd.6g_ h\ yaV-o"0ы4qјϷøofY{X#Gl cSĹob e\BT}4ͯ9]NtN0rk$5k\CzRZ-"4k15㦗ɌʖrnE۹IzT{5Cu傎w6[mWbCdOH)~A:]qۣ_g i IA6O#k3Y *<^j9Y9|m6FuU[ƞ7Q(|)y![;;3}@i W5-: x ܼguxz+6=+X%?sE#1Zl-crmY_rW'Ռo4#dQ820oH@]/ jJЮ4Тؤ܀—d/sLV7s?|VO樫nj4Z:njbu-̠<+|ڗYc/wq#}t5H ivj!#^˝'k8*k-@Gt+]N5Y{hAH-MC˲o~Y t6do[' Gfͥ.,{V#E!m'Ao@07OKiY6ދMa0n1!w`Tθ9%5oG@u7[=#6űm}Y(1wfZ4R+icbϙAwwpmB'm&6!+4@%slU4Y9J䗮ocsUU 0Jmt\yOF@DB'XH3rQ&*l6+< uLgY]4f9??.6WF.0$g tB$:nw!d##P6J"{ rG)kr|߸;bxvh@]Y06_Ȫx{6FH^rmYP7n#6~p8oF@J[cOB+F(xz F$=M4Y!sZ'deB9wxN#YwgT1tv¾JX#JMFؤMg7"*u/0i#{Y<UmT/g\ބ@}Y_L鶬"%d:7]fW/a2@qx|(VT i G(p)dM&^V"/k..sc64PN6)+8OUљhCMuBT=r2 _"6_TȎL%$=ˬfg2;.kܸ3Y 6f&^! w9H9RƩ]4B膌>2[Tt4vң PeA[Ѳ WY)`%|]4B膬 #+ie_H#!3(`%R |!kLgyLY9m`d:+;פ}%! X|2|Y"w-Gb^ۤ_o|C~y|\̯40ln8Y^6%[BϮzstA'>c]4BKnK Ljۓ,s m*0F$Fg:#nh[Ғ fUX# 40.YӋ 94 vd4q }'nPȓMJr5CKݙ P뭬Խ\;{ IH 9hcX;g=/օE 颁PwȻp_cؙLgI Qܗ i`2ǜz i)k$WiU4I> ˎ+˺by!+K^i`|)Wخ.$gUi5|Wńgh<!eILTZv6bge: iSd ]jI/~Q.@/D0;N{/{~ i4=2^% ] FքKx&! ԗfa@&aFݘCrM~7KerQήO3c#C3 i /Ò)rӇxTM*Y.# ^*s <*c2sPhV.~N\cZ'X# O%SVHc} / *dYq\dY.#:%FQd4b`yyM[|V匚 @d(Z_ i.*ݗ@D%uJN~VAgLb SomMR(LgmD%V؇@=#}rz%G秛8ݨ3%wt-k]ܫ$z1b{V%u5Y4,k q? ղ]5ΪN*P( /$ +d<4\ UFUŘˬY7oV^kd::SxD! ȬpE3Yޤ[.+m(exsTiJ)2e2}$>! tZ||c0ح'.h]rMc0Qхpy?DXsl Tԩ /$eVT0 CfGG7ܥۤ_%3D6l\y#O5Sg؇@b܃.#+(8qaCx6N;(i=8?b29 Z81n@{ PZc4[IdHtYϥ\{F@k&n4b.Rqg4o1 Mw *DE %n7Q .61':8s#;@YdfFX1lAjtlO1 7+S95c ByGi`&YTWb'ė8f 3knLVYۉG@EEnN/\{KGr!0Mh@ri3+4N\IJ46Cb!WcfFq\?yxN=3jskYiS~XW\~ #Æi].s_ÑU|9ͺ.W)\`…5 `#b cmW^E6! D^ܔ]MmgCo-xYy6a{KtQt\Mc:F`AW6b{ HA}HSfbHQl@WLg:TΦ /b9434FK6!ؐrt-_`2e\858}_m`@OI\fgmT*v6!i0Yk>LG=m&~P@O*cr1B:4ݴգ1\3\3:K iJ ۭ At"y3lnbqHgxA$Q5Fcin_9`?+ahz[.e臦i~@/+>Ntvgnc y N@3lqS)'혳~,+U3tetr1?^.e]4?%Y5l`4eCf_@3xoEmeMd8oHƨt.bseRtVkh&6d3Cobk=kp2 lM{{'Ƣwټ 3(4a @ i Cr.e] uZW^w9[;v)4PmT̞x F#XDZ֘ s™Fӕv(ƙ5=O/TQfkuϽt{&k'5`6(2>:h'n91~vj;ά.E%*%vcgό..B8~JտlhBkBi3~ =cT\4kۢz2;x]t~kPɽ3mJ`]9X:ǢvZ=:i &mdUkzo=Yȹtvhwѽws+ 8VktڬF !BQF glNy돌Q@ύ,yW:D?e":YnvX5!Ovv0^HQ)WއJhsͮ:{2\O;xsEeb_24r7q ZLgG7nx.+i& ]ܖPj_ya\F`S~vz|1~r^|g iynB'ptzR>n6S40_ R>$Rjy2Tk./gٔOf-"! |ͽ&*hy.ƨH i|݁zmi"1J'MKH;jI5Y&dmߪ~=cǦ'>Clu}cۭ<hO8)>[#_ l:mE@2! cpϱ }^k"!MȪt]$4[ӉJI_^ [q4jCtD!z A"1IDATst A iߓ|/?*6i3g8<^6*˿a25 YsV3.t BV~:4O_qWI]ť36lK{e;@eA ЪʟyEظ̃ulNظF Ky2i(hgk#kۊW 94w{sr5k/lr1/jٸl jjbߡf8^Æi6h2ïw. 'YD0:r1/Wo[U kn+ ;, яGGmL85öl`s27 ')#D794Š`2exXq܁g#؀tV|elD'뼟Y@_=]]5%,y[a{+)ktm/' ]4u.MEPs6%Y.e} 붭qzE't`3Bb ެq!Ɓ@ ޶;->w[*\w,荬siti^(frg=Yad:s6 h)_z,褍+[iz#+6@Hru*[BƄi%T]9]+iA' tM5HړM7^{5ltF7 @?[Lf.e^s4ВؠIv0T9Xi/+ i^&s=ġYUBhQOkhYkgds@eN05^!Lݦiv._zLg^\<0%nƗ sIvTxM.*(짤ͺCc :&iqj2BmOePTŷ!+ibCH-)~&Y-_|?.si)I^[i\l~ɸ3kcZ3/~]ѽ7 1Z8۲4s\ִnd0w9<ˢ%{4۲4BBqھ֮yT&zв؜v9)tY{i"9]Y!QBB'*m׭ug}I1;l7wX.'ɛ[-TTԾW2yu /#x:uD@֖̪'?߹Al%ֺ-;^@HtBzZ 7bAO8 >&$wa\tui.39Yr18 XHa9d[o @mIAd:{eϸ3!ŹKo&=V.>҃~2'{(J'<,sٞLg' $&<2>TP_khTVΟjG^( @ei4_YE-脺KzB膓ĿԔdw`iLgM$]h)6W*h˽t*UbaҎqx! ev2g:`Grj^7ʦH G@B7 T& -M[m7TLgǓ鬄~Wl4=Wdu~tz#sȽ-|?]#?Ml%]pnIPԷ\?N&>8L%FѺUƹLKyz:B· i*lH ig3!+oyT!'{a%C |@f%3 DuA:OzFE'}^ts2r?ƕ+ ޕBBz(Ϥֽyv)ssLpŅX.槉PM$K;Q+A]?nFvOBM\ i[_rKBMú@{iaB?o']kA^bv#ߓ5pQ$4A#Yc OťBEKdƪ[FQ˜ N?d4B@dU4t'K/nx~|f Gr b*=]t<^7 .+!/4PMMEl1|Y!3N*xEr\mɴGH[Qjx\$@@dw#l{6zY#4dv;נָp#t niF :n̙Mt<d uӌW|l']k_;K>{k4a2+t@'6* G/+M3jk ! @Eb~ƱWOB`2oR4#|gL3㕹M3BI|Ba^;P·m%=KG+k|/ $pf8Xnq|o`gq2 Dܳ|LgHHYfMx/󵷙ݔ=PeW8q6΍9S4 :^ŽK9[kB`bC -sU#GYLn7q7㓽f.ƨ3aifZd0Jړ)U5#*V!i`|]v;BgEG)Y O4`o'#y! 0V #iF.:6k*ٟۓ鬍e'&wn&`h|95^߯bSYڬR uxʱ -놬1+B:ic|KHD<@HQf'.V27+t@--$]3Z]T{F Olve(7&:.=1ʾ'<ڭjB`TFy4͊@%=tSt<\'?77tB/[s<i &ֹW:.xeM! 067Qa+U@FAwqӡpdSM Qcn,>w6ͺ^cE}F@GE`sێЌKU[Ԙ cK @@0gw 4+tMIHFT$=;UFIfHދ:ƥn.8;FboDGl8Y)]SKk4袡"GBb~Bj)^Ρ)NWmٞEEf占FHK憶*B(L;y#!TR[5=UmQZV[#Mz"@yJPqkN! 0 UQx$n9 Mqiy&Ѿo;4qFMb2]Ьz)B 5eGڜ 5UIUc :$gT _2b׾Kj d4ƽiJRok-?! 0.“eV,ml6g7Kk7_7[:|L k114dn$.+慎k =ǥHbPZG̈/)n#ŦOƪvJr1! 0hѦ%FZezSRi&|d?i]wr1/g^V"tfTu'/t_"׬r]8Y]4d+YP?ǿ}-}U )FQHgg64O$&sS+ iW6FM`r5W= 8sp{}Glܭm6FaW$6NVu;Жj> 0zjÏɘ}! 0Q|Qeld:L#]\.^5<8mlmә٪3̟bkmˆŌx]f+?8s|__Ob8K6יE#V3żtm I%UmTw¦c-1ۨ$0:{/lz[&,s3I@C-!qFC F^6i~I:Μ}D8}!|s;@Hd:ۍ,"FeU&o;w);ZpO/W1 ܘ+3gVaHV]'YQ|PKلKtAo$?r~ա! @p'@HP! @B 4i*T @HP! @B 4i*T @HP! @B 4i*T @HP! @B 4i*T @HP! @B 4i*T @HP! @B 4i*T @HP! @B 4iۃcA^UdJvIIENDB`parsec-service-1.3.0/doc/images/parsec/ParsecConceptDiagram.png000064400000000000000000015323161046102023000225660ustar 00000000000000PNG  IHDR!g GiCCPICC ProfileHWXS[RIhH "HB U@B1!ؑE."`CWE] k^Tu`CM ϙsS2tyRi @$O"=@ _.eF(ʻ%UB9$t >bTꭧIx<2 R%Tb%NW MBi<,&g3!M]%2|OqCrs'+1d3}ZTB˥9ig;(cA"5þ̞4$1CA,PCRED=jʗs`bW/$bS$9Q}z8 1\!h8] k8ebqdJDH[(JHVQIkC̔gGm0B'FSoP&f4\yy >OMB ;G(_@$$jڥyqҜX=N+V5x@\j~5:6Di4~^N%1B q<ã57= pN5I\$ a0M׌AV<g&#@j9̕ƎJA)(A==X=C\1>N =GKl;bFŽ`MǪU-NO6ܵεz.OX|?d48Sb7ŕa_SoLL~JRl)2LHRTj5:ZAޥҲ%֚U[4}COSӶҎnt;z=G_Lߧfhj gkWi7h_~CѱaL))٫sQKkҭ={CG7\/F/WovzIvbM1053163N2: \,2݆# 31a.aa^g~d6=H8hA7ld$4*5et188xq=dTu&'M \:xۦitML{ͤfk̎u3̳̓W6`XX-VZx2dY9 VereeUU.{Tko -66mfܶzۊlW۞}ogol7߮=׾о!aCUGcZKNȩ3,v^60gdH͐.4KK˃̡QC6}9fXeNpᣆ oɍVv՝>۽F`xSYeUu;{Ol>}=}|H‘G>oo`lh > m zvdgw_˂rfr`!!!ì2º=§ DDF,5Q^f:I|%j5zѶђÍYs/>vJccbTy7wʪ)ݲH9" o3 /U&M[W )0iiO OOoa9c337Bfjm=xvǜ9Rfȵhyy͊?%Yɍ~/.t_fRA2ײϋ:׊_g,n]dRRm[pW4d,]vդUgG_M]X^UѴf5+EתvUV/~Vu֗A 5v5囈7=ٜo޿n1RVmqNzn7ݾSuΐM.w1w={Zzgz?ci0Qޔv`ԁf cA˃U -9L=\|Hᑞң]2=jrWO9z2Saf>rg>x| sgkEM|.5l;|9+!WN]^=-Z7o~Sp٭[n3.n={M_==ypa;^y ޮ~rA_J7o'wK?OɟNL\Kȯwr<O@32xz STϦ*VU @=)뜣nwV=!C# w75 xޘ@jwm_ߗ0[>_*蚑`A ;  pHYs%%IR$iTXtXML:com.adobe.xmp 1044 2198 1 q9|@IDATx ]Wy%4,˚eɖS%p06/~!07d@`-nimψ/]/_{ki\09lqd(XU$!*0r%.6jJG>r5ܿYll?ȩ̿!ֈ"I#WgItwČK?I&BO 8ߎ;voߎp%ƒ*VL!:_,8ee_?2-^aQ9a&clLLp" V?QQGŢi:6:|Pއ7 ɆDK?''QlO a/FCG?j+ ',&3'8r% ]e-̿i=ͿͿ:㯚>(:/54!$/C|8bE̿̿:aeeU/9^gіd&C.4zWZ]X\+L [Vqۉ?{w` ɞhbc s zG,4)-B+La(;pepvIU ?z.~N^,ѹh_K7h ҃W ?A&c:dQP(%/?ɃP w?n:Op [~񷨂'~<;9n˛ZG3>G͌: vY'Jֲ*<]R 4-Or o-P_eMhKllI'5:`rcf9q ʛ8roߌ#v4iH{Y/ʉ/qf=q'ʛF8h; 8o%l=<4ci7oZT`XE34gT> Sm?p[5a^ȘhMmln_ d.[Q/c Ԋb]:X/H_DhJO?nHO̿^PApdI))A8̿̿?y6 'l4$+~Av%B}:tfA`p 'A}f?P("P𿬄߁$ͿCLt`?D9q!(TPj7WCcC@ ߦZw($]-0r9ȝT{j܇ BRq83^`:F= L2JÙ84?ؓb`lh7``k?4E,rAD?_ %_ Q̿04 ݀@wd"̿Ϳ:XgsA0οJ"k68 . 2A'ʙ/Y:6vGCϟyX8W`ȡĢ,(⟸V |j{;{s%|5c`۹K6ZkHTJjX}n-?Lll*i;Mtp Qɦme5q:t@ǟ?w;n%?(ֿlҎ?He.!_ߎ?9"wv?YU6;QLRp7eJG pbdR;qϿ/%o !2h"Toiy[KLbpTb&#H `XNB;DK%cxQ98][ PE@mae C*_?t/ rI7JTPh _^_~8Bm'P, r>Uf_某%boYq7=83g?8FX8X8eW_!#$Jm7$B; .[pFrTQZª1)1hɵH.Oq/ o :"ɶ3, 0?_/JYO%(9JfM6gXJ8 @bO"Aǟ,ٗ`gOj<&Ŝg-16/ǟQk:*%Ct\p b@]fğHk6@z1$;4xYƓp)tޙ /cƫ@+< nżST,JUpPֿ%t?AhPm?̿` ? ]0 [: fj7.8rlp.rMvG8svwߝ90ko` ·4( '61@Ѷ̭,$$rr[g6P'vE;jl-?__M_S:߼oACqEp;l! :ossG_vf@/!d'矨2?962a7SO(%`M0"qM \ꔎbѨn8bm'AE;хWe[$-*X+$' _?!Ng h6PF)_4ߎ? ǟDο8D[ sο*???#?$zf/~"҉Q WJh1Rg36`W,ڵb)X'*yk΅ua%6a58a>ugcKA>"?dȷ̿?Ϳ&8d'p%O*?7l]x?;{N)0E?ϧXTȌ^3?/N?-I 20+#@g]n.5ZM+XʃM^T`:KOY`#hK"ɓ'V(@o*Qlο8$N'Yd'/ο8/?/:V>8q g޼/_)B6e=sͣlI:.ꅵrG6;}\@r$\OekS[x/OPj TXd6lloȡȧȃ'garǛӓOCB"_`f/%}q+_2{p<ĉOǟ1i%;8.ha"UM-~rObOǟml;B] _(!0 뎹i||y97@"d[j^7ݿA7?pt?HpCLOb8,!S!:/ο8B1mpEB08RIA/ο8oοH)Y68B< =,oYll;B_¡d5o{` ,q/p,?U082 ο9Mο{OpQ'>ur*KO4smߊPe"<R`CDp8r=d"[ 8 og8+B@Hp whH] ~A%Uo 0&S0yHZMtC+g\q|6s.0HIApB(À˷Ɍ˿q:j؀7Q W l[`7?[?" ȷZP>SAS-` $V3l)ғabUs/OwV?<G:n!BApE$DCPNXZf.?_vI[o,efь,o:a Nvk/&$t*hEca@0t]j gɰ0?yr@(̿/i'53 Rv";_!?U: C?8@@pA\_*@/ _q7a?|¾:AܥI[KP"Ȗ"Esэmt+A7k5)%弭C jUֱ~q a+!݃Xllt%֘k'+pZDo(pKҐ6@p%N; %ȬP8%ο8Dp7 R;GȂwۋGDm{EMį|T"ZilÔQ|l+ē\\6pE:sl`1!k? #)HзJ*Whevrbe0! oo򥎿(0M~?.߲ iY0ߎ?9̮igpo?(vax/jk6@9#a;sJ#@uta! *2O;uLqTjMmJ7D]8NƒbP0$%I$1܉?A$D@Sd;4ڀbri YV,VHxPGh@$D#,׀ HQ"`5̿^83"ЀlTdtX̿̿$Y+8csOPOo:t  hjo4IXPm$d Ç-1u. -yWx \ ~ba j5 c8XGo4vKVAqOE?0xP8$Gο8m+}?29,8/ο5K'9&m ,R2.&\w$JB.j)ȍ(Տ,;U>`([??`jD)|%r$o 0t)@ oǟ?]S;PA%8ʼOZ?o! 48@/puO_} T&K㫲M!ea%61:C!r? eEQ]nFctiO/̮7?!PQ-) aSG:)VAH(1?+/P27-̿i=ͿͿi8i#8f8Oo 2#8rN3?*8? Qǟ?:13Q+Ɍ60>sGuYrcKcY\ډ?{w` ѨDAk?z :@l+A`?̿( g oXN:t[<*xJG ш^q%MQq+Ld+p/_0~~Yc_$O,AJ1+ki 7|q~եh?׌s;Ĝ:׍yZ<pG}vcFXBsѨ7!3Oo9Ћ8L0tɈ7ePJe_h'% OCt'oQ9 1Nxwr=3j/7o~zg^y}j5u (3|(l OAgmW>Tlth!{\WY__҄fnaaeIhFh/+;YN9rC&:v78#68"2 h1^VprKYO:t G?ڎ?n|}a!zc [|0:8 )X۱VXF=Vc ļl.UETc45?lMl92&ZCd߰?@V&$08 3-0ϰT0tRJʢ_//p 8dCa ai- ?tfwP(A",k'S@miKb_XIPp:|*N"J71jGZK s$e1Q S&.s;l,3C>oL x_̝432 4_t/+Bf"O] Pǟ?:YPq)IЭfߡ'4/+w`!5E,+0XwnN-,$Uȷi֝< @Gq Frj U>ĮaT;t̻صΪ`l-?yp(+ڍ,XMG l.aP'd+|dF3/0B4??C7 Y15oo"\P9̿/_ K8LЉrK' &ߴx8'b$?•r((1'*jμ\ _id蘺9D;0vͶ??Ukfc=b˟@_gi _ߎ?`5C-cATi[Y}:$?:=aOǟ]*O`q/_!(ؘJ6V{,>N:tn|}[J[o,RF4bu 4jubSw#9T—'a} }mfGÒ:?!&5?EY܈f Ђ_#uoŜrv/ο80q?C0-&r~oοc8/R'u^!jt|e\T̳dxp?eN8V[twZGp4;[V#ENɇ叆5`!c"Addpƽ#?O1Rj,eH_ȝ:OEan\wMYqH8@,q2_/? ~I{[Cȅ㯌/|[le%ՇvtiƎ.RmtX"D0^o"bj){b7?EjcwQ te4vAkO&+;i壜p G+gG>r<>Cnl)%B矠οP!~  Hp*j "Lq]D6܄VڞrcC%7 BmARhS,T .9O'd;b XpEp[V_rGF(ο: n@z3@pY4eοο:kepWɿRz[- ,!"ñˡ/",&r"k`jLJs Zr2K퓼v `[$aE-7K5 2t;{ JYM:r88.8ؓHtr',&K%9t:!I1YK̿Ϳpgv avE]'9cY;g4ڴ́%^ o; ^֫$qA wfX&j(%$J8n,h1s87˟R"$+/l O0T__/XCB̿5x=8C?Hͅ :v;EKp矜]tǰο:\"j矝wwGN=-XA"" BM .P-s+l3, ٠ ԥt@юb?ho8˟Wd#xp06N007[P(w!lQ::[qgο8[ܜtW矝/P88&ap' OοcX ,A&8 D y Hb!oS:X#F4XIPjtauI IEEYl:MTQʅ' #/_Dp/?8<;;eϿ? !tbT<•R:0:GTY+ )vXk ։J^bk隻sa]%icXNXOoA{E_-/Oo 2Yp)ĉo $3; ;~?;S cat<)-2̏˥Se`KR Y[K}VxS`nc{7:S,?ڒ(??|@Hd%`ɷ >3w#8re/?S:I8/ο8Oοop#ߤ?Bș7Wm%9P=ƠMYϜF1[ ,zamѩNW5\ :cST-S8 ? r() ?7G=EleIYإ\i$sӐ;pKIʼW:$qbgxLZ ߎ) k rB`vSK9r',b7[hnWʨs@ <ºc{&__^ o HVy;x*;M|xo- @f3/O%\hd50b2F5@>8epGο8 |/οP D οT"`C9/ο8o7Ck οB7_DO y#̙, XjGPV\%,t`i/7_(/1!a@&,C#Ʈ˖0ZES(bckFf"LD^:a?/10hXOͿdC?%oǿ3TSSo044 S='ߕ,5Hv?___TBai/7\"R榥=*utB Ut!5zxë7Nc.|;(TE([C?_Ndp e/ǟ8v!$t z &O ο9Lo?:#yoT@\ ߜ9XԂwgwە*Ǜd;)НK) rg'`%R]7?O?" _ 7E:pIǟBX8&s?q7RGu9hU"ߝwwߝgYPpwGUVm ,aF=-XoQ(lT#"k#SC=Q?g=ڌ:ècP(JeXŠh104fM?o(?qECJP8,?:R(8ZM:O?9a[?js̄rF]pN9O۸%ABs*4'@ƟBBJ˟/[JF" aSCE?̿04v!^TY588 eO*V$?/_P9(P+(8zb]RPI*Ćx`h/'q@1 r҇V'x0W\M 2 mR0m2o8$}vv\ǟNZ:6MrV?eOr1?m:-.`TT X_I*"cEe1 A{  t$da#B"h'h,h;XmU-b\`i8?[P8@8r@5;>"y8:*@36vϗhe^:,ReÀcU vN5v˂`WST6p'BQ3tlŦW@K@ @u[6kJzEi1҅G9]}wͼųqcҶ]]7~1l0`2`qlcV̞.:4iX|1+=t]=5]x4y~;=ikhZd)C.N.J?Bo8?_hh0b/}=cccccc濅ŬȵN0֮0rȹl&&~翎/<;pkPFA ڇm񛤨JD pyFs1_jͲ@u@q]wҥG/ 9_ƴuN s! Ɇ_u3 [fdqll!?۳crOwֶ_m6:FO;wG^`pIߔGlkC5p/J8E@uѕr=:o)[o-9* ,©1~s ך_#;rȵnەM#8`bD[]%DwK]1<"Ɩ(QVN9TldCȵp2կ~Q> ϰt؂ii ՝e{/I&CS& iδ՝z[ڰ9O]cSK_q岧ρ')x{{~=׆d6)->I^غ35#ĕҍu|?T߾#mã#̛61;-8 ;͐?ڇ#m yeǮt/V {uA3Ҵ-^ݞ~jsi'\JC"iy'}JeRixh? xO t?xѿ~usF?pƤ4u|/L˘|6kN(C?_" $3uBځ7 G߹#m-đȕec_n?wG@x@E8}f 6ٓofLN|{,棦OnjI_݁|Ԏ^v;#޴VS'mW@?ȗa㏌ %3'#o;Ff^|cWƭkmGz~"-Iau?I"k7n #Di<~l}g"~|8Ul8~rQm`[ /G)9a%RgTABsӟu]NH+fO31wJx;YM0& {:TZ:sڀ\!Ici1N:oe?7e%;<'ڒDu$Ǭ/3%jINIip( @C46oբ_׉Mp_WƵ4TT%w|a'.|o_N|SfX{7+ p?[mWv>ݝz7uRz_-ǧ/$ OI{NCxt?D*'{:{,Dzx6fcܯ2O[sR Z2W7yUW;WxPJ=6jA^~ NҮ0o^#&5AGVq?C0^}+ӆ%kvSxepm*u!|F5Yx >rE*,?u+;ւDkƿ7?1.0R\7orsڴ;A Uj\ӛ_ XWc眯D$. -,'z% Z6 Io?[k08mHU cn nH\ )IUNf/ߐ\4&_H6`(m-/k1)[|PZ|EҦ[/C 1 9Yʧ[JߝE#qpm|y;mw7jzm-v <z8ǥ+v,d2 ?!$Ow=ENU~,ށWam  0"Zo?SyrA~t5 X#=ZHF> @]1Mm;{6nI˔esVrO#VΙV͛7/Jcx{AOm[>0 x1 n?'DEkOk/ ?y;ZO7{Ĵ?Yx}xp&a :cS}ؖSMɜ¿?SfŸIO'wI+|ނu\oQ @/#u'ߎI0Mc|K8~OvVHƿn 0Bq/'%|^| Nv[u{RyǞWDcœ)I7@>L[?JBe%u9d,CLGETWw5)JfpڽG󥹻Bz 5XwGQ?|$+7=%t )'Y[j1?|fa?6]Ns_,׸ BpH7!o']ylK,bEtEPyî"Nqcu+]1kzyX3LV |(xkA"@Pë% cǦ=ݨ7+??mB:h qϽxmmjp/l/%_f ='P + :\,>r~ɭeY^421K?ۈN p- *4rR Qk[vVR["P}?dqy#BG6?uڱ?MdԼI,l)xf@CBlwuu|?:a>CWݔ!磐u?usa??P`4i?B߽;=`e&=ntdEd/5ڿMy~ƆhvZoA+g~VIX2ZQTaF ?F~FٶQ֧p7U^Eˀ_мҗp z1 .[~עFhK0< ]ݍ㇟Nߵ&Cq酭;a,1ol3=;`Y`Gӷ1bIFqa H/?T E~ $pJ]$|7w䉉<3o=p~#MNM ĨMwP{/|M4 ݋t1'zM]'HEs?_:kʸ4mI+ ^6kJW?] nH|a:\7fG117w>Wڇ<5i\1'M^AEn}vS&═'Sbo;^Wq.8A?4m-%7=V5x5)d/ͭEv_u6v;׽h| ࡙?'-{:K ?:D˯[hl6qμp #?X/L91ᡍ7g*x;x0 [1\o7]7aZrf~.ۑ?/)9W[O*VNK'iydؒc[avB}sc)?{6|їGmg˗09MDdmh׳d4|q~Be+븖gc!| #^ئ+mGڈDy?!ز}GzoG۽p$`՗vq I#u|lm;]qE2{ާc(NG6mRz ~)>Q7phwSH~e,6V;ǀ6_?Y?|9ɓL *no,v^M_yٗ]/G7v=[U`p_+`=qUDݪRoA7 m/I N/ϗ=NuJ'>3"Bv@hVz >!6zlڟaO.=g(x-?!yx\8j يMىmWF!ۧOiw#-"Կl0 Uc@?@|vw.|Cwymz;p^ŷ2 baஓ2wŵdy?ezˑ)0wtfl0]Aul(OK0x\s: ~RZ3O_~\meN(3 95`?9"cs͈o{i{jruo}xK/}:)O_2gJ7*/G|D* g{_t?MGvaSYUN*^e*sSP6Py!cp# <|&U+_&NMa'eLϢ"eaq6U_a Q k{f>a ?o//oqXup%x ?JuKCa{q *"<r;d0|G?ZegZ:7qC__~_6)D$}<ޕn#:c^9*R |Yu ^AF߰faSW ,@^udApңD:d'_L{i3?LOGץ$篻7t=P:7{4m8nC˯izMQ$w0UF ?i-)o꠷?I$z\$tfJtpmNmԫ xy܊40n:CL4C >G ~\ߛ.Nd@#.),տ[VX} <]_[G8N!ͨZ$0]<Ѻ]5oZy8X9{Zn!_|I퐮}{ 8ūj,}m2ҩDes&yh{ze{0xKxq>ЗDȯ\_fRU@G7z/֯_1Y/lQ1,8  V $Ơ}>TA{. ==Wiؿu4W8:<{kdv `ga"]OnJG"@@h_H;pW8\`i=R,ncqSQ3__?b.?{M:n4 Cϼ^ K?]'_]_c,%t`ǁ w>|^y{C GA;qoCov^sWoo#.`w?r?l½貛~ǨXH~7m vKv_Cv3~t_ˍ:BSV٫nɼ 7\$ (J={z/uf3?t8wʄT/=TKwӆ:|Ɖ xG>-(_v0<_?1Vb<]Z5WmhA\6 Gǎ޼WG~㤕G^B{O]Ez|+[\N=_;=8.Mm ?2ڇVoo2|7&A[P?NJܥ:)g<4i8p]㫷? ЙۺM/E? !',9vNkD5&FGZ_UЃ1i; mG@W͙޹n)tmΗ=ʸA,_F=8c70O!w-cEg ]olV: 7,$ϯY8/G__>| ]/ϳҫ*3^λ>qBz;gx ,=؍j_]q귗,5|߈6҆xK|6w5^݊Sݗ/ƀK/Ҷ_wCEu.[PO[y^v鿿맯{|.RT| ^ Bf>J=u9WN]1W>4j._ڄ3/!`';'V8Wo}D-@CŖ9Eؼ ޮtE>{h )S 62ZϞW<)6x0.PN'67SEz5wFiNuA _KWAɽ`-G-VE/ѼouJqlVo1Xԯ& &R~NأK`'r=O>;׀ç<߁}0/Ol7< 8("KVw t\8*b}~sd[Q&q:ߌ+[q~9c L] 3^?ǘ\ߓȓm_Ssz=S[?^E=!ɿ)qnģ޿򎴕̏)g䥈?VN(.zn -VoOؿV0Oǎ/nG"9Ѽs@u|nzS5=+^me LB \X4 <^N‘t+@G1A&XB"gA3+%a[{tZ=i xG#: 1܌CH0qDyNEiąr$4È"Lj|>2cv~,?$aћ?Hvh3),\^q5>@ЀV@VCsjbNm:~Lq` /P>4>8uW[w_)G 7  s@}]ѿwA<\2ڦif6~/gVe:|`psߕ!w?e^]˼rVi\?[_MxX%< uHf^gc8coݰ խ_]0x[cƒZ[kYĶzßcؙeΨNLt p;̙Lx&Y WhxT/WOb ?y@!0S Vx<ܛAWՇ0]uhek 6GEYbM OV*\n-CVb<ޢӟ*i"<C%^5D1ڂH.}A_ a?$Jz*yNL}I6? ܩ?ī繷ѕA+A<}Q&1Q8@`3)BƗ%~w7_M 7 հwxgDMx/da%N.O}s*y +|Qg/y҃_wDEȰ0005f+|C9_?^`ޭ!{Aştl}DZL6{ o7HlK"Fҭ‚DN>O}v=H'M/ k+X)<9AR;Dͧ9"r"|I~6A|$8hSo@M6f;cʢư^Oߌj [؎0G_GP\r4I$ǧyM#yZ/3 )oC  t,sllUs0A9g@lx?c Ĺoڒosgτ? WbƇսRs{ N&_qyoO`, cu1ga+k<]_-OC.\> 8돣!ӳ4.8XZIq0lg"_U:<0O^R,Agf8<A #(h Q?~>PF&aw@ߛ>T/0MnbO[;s\tI_]g]Ñ'Uz\)!Ior 1|{'6|y <vbeg'8 k+gS n&tvl`T%_?1;<$؎πKq`@\? 2o 9r!?@ mӛsr˨BY''b.s#pt0{BAW⇒_IpסٿhN+,In5uj7BWd 9 GJc΢jy\qy"#4'l̛8#W> MT^?^]b>غ֠*:|r~H]ސƺ)'5׏_+%%N 19cx{w|4,Y/0Xu?spqg)"ls!rnSͯďo n/gwuOt:/_t<_'^V9?ߓ 8n O,y!(٨'䠃?8M^37"DS08KOnIJr[wa`+@' )PouPᯠ/clJ$*C\ӤyoUԜ6q*~>.mf[׈97ڰAM> ,@Ϙ^>}ˋH):]d<_!:_b>#+;}oȷ@Kdܧ:G,[ 4Ctb?7ۮlsng.ubɖ*e@&f8_k@Tۀ?gQNOw_D!_e\|cqu~~78 oXke  SҙyP#/Y;1ěz5FI ~\gL_)ާ/<\ D]&@[oklTgv7;nZ{[Vw"a^*HPX@5k7;DӍ%]+HH }z=g}9 3*|X3wWyZ{Ud9*l|O<O[yQ f?;̀|Jmp?/&GW>J/m ?p)(kL]π4{+񝐹ߤɀ:7`!lUYOYӭ_r#];4YbK9]U)@N 措4K8dKX|8Sp .fҏҜhExb֪^h`r~h|TA~!QG۷"PAGɯz3zgi)}˺MMfOT=ŝRٟɳ,3|7{VJ")VyL{E%KEPGkF L>=},{cj=؏pA(? x_ϧ_WnϿb?OXOGƖI{)OlSc{BF yB 'sD?M7K4Y <$)C ̿qΔ 8^}7oN&?q3Ť]Bg7\{w^Vu.Gpr_# _ur}?'\SRNUsP์?(?"~Iz=CꮃŁ}]L%Qz,RO}+ߢN-xD- _)Ghfڒ/(yo_]&V,I=B''&rʁcj؀WxK@UTٱO_m_?F%C<_yb(W뿿K qweKV)sI]P xw]NZVOxdݯI 0'K9%s(Kv=xOb ӧW_ǝ (;DP}(n\cyc_vQI+ bf? efٗĘV'}5^)}?~R2πRzDøkj Yj$FK^ܘ"~L{d_'?rEQ}ޱ$Q8ں sW?cqOy@՟`!Bi%wm+e:W)X8B954I7'p nĥYBwUGp -sq?& x0]<}Q7Ҫ~򑟠_AOB\/GBe>P)/.b-z5\_o;^ols>r}0.l-|⟂,̿1Nﴂ_]|HUNycvr!n}3[eI3_W]߳R쫶I?IPPY$hCWAVc.haGWv۟<ʹm` pJc36Xs5g.-f1ָGyH3.E*,g5v㵘ŇA?+o $1g\T2 ל'51Wcj]iqm|9c< HTb.̆>y\ǟkOaߓPJ5nMnc=U18p|,wOBNZޖ@$*ʗg+ R,}\0 TQafYc\ES"RÝs)HbAX3 }M\,@rS" OxS ߅~pܣAoc[#3iOYPs.w?|Ҥu\t=JXi)x\tMRWW?<]XK X+t.&WO:Wkͻܹ WmsX-\Y@Kדp&nK.C4[LsѠ7qCI74~SLtqGO'ֿ?rѵv}\Y~a,) dTn}οD\(4!\Ns+$R04;yp2?;.OvЮ ٟ tAZE:Qr͘;>_ ^tqsK&;%xlI!iZ٭Bʱa:?v4xp 39KL 3:OQiljv=OtH~ $eHQWoҙ%gW'6u]~\p ޭI$qR0ο g8NjXJ_i14 W?}kng:QZ/K?%jLY;qmtL:-z&*[ AǔAW`Ǣ~M'.Fy9P^ix212kAJ*X`u \9: vIL۶~k8g\k\@Y_p C&@s.5{K^۟=e-_?xAϑ2 j+9oH#ޠEPqK#(E1IJyS[T!I|1Y~ڇ?jd# 꺫vGEyFbR?< ?xbP/jD;,iGπ&yW7ɶ?rje?<`׊ *5_P(qy3_w+y\AALJgEʺ'[>B}*?}FT i%QAqHf\F) (Q 9D90ж;LM]gsTegQSG?-Fc0 j4oF} 8W_v)"Q.Z~QJOD!tB(Siǽw}KTk1k/R[<% WOD6#umw'~~xlo a'j)ZϑoP3[LP?\k/_ɻx79DmskM9m\g4q+b0╇;bbnWbR*YPO})XzҮtCec5fkSJp~qD!)]>+9?ZF+7Tg('@ͻy {09Hv~H+W))\~C?B4;nu!?qN83# I 9Oм=A#we' X($9hǕ!y^ ^q5/Yf5}$VU8#/ˮ)@8L1rug#rFhܓ#:R/mrk6r;j/{!Dp'$Qx6(U:*q"+p<&#R?2sĺF.9Lި3RS,`bM2eɽNLG0A)k˾|]w]d{WNԄu^p>U^U8+K_ *m$kwԽ[IL[! 4Q o1,/]6&LZ_IGAܱ옰wfQj3yKW~p5 ʵ?@28NT'_Ҝw<{7yAd hnz7W *0ZS&˕J|L$=PG>.㪾?pP^pvigO^D)ڟ}%!Z/o*mZVuӽ~$ #_) C :iQrm)0D,FxOnz"iJeHBOϩ_Hj?/U)yD/)1hJ&4mŻ=r^ BeWO%xO %" lϓ.@_c'8ABSv:hU"X+?gW嵇B `a؉L_)Lm.QqHGj7e$k;WϠk|͆FjI&SBj6r@%kn ^nGŇlid~+"otE_/nIwױM ,gix{á={?YЃi{?ʵ~^ Y0lPޫWi%T_T{qu?(7xMDl&mYLN-tO?Ħ=D;ٽ?~0W۲q!O~%Y._?/ZHZY?>JYK}I;ON6z>_QyYg?w쿓ś'^$^bhS!zRseᮜtLVp>N7#; o!ݼ@GymtĝGn)Մ0?$^EK4xh,9J%F~2ʨ0ZO7g]yᝫ#>6wgA\v#-(4i}|L+:WE c#t JG=a/7u%|վ@ ,eY,ݷx)>?.o_TL+ .qi]%>m]4) dyk{#UP:/EW~?*W KC1ǚ$u؉*X? *QH0-7ȦovYzIߜ5VE%.R1E?!;k3v֢@Z4B#ApOBC/ՂǯoT]wut,P3@z?jg.)%uZvzs}I}s[h>ӓ(/DC9'10NEas->:CIZ"%/q|{Ba+'ĻNmWk! ޶'sʣPo7qD}C|^vǰqcެoq_x7}\6ӬAAK5q|V򘏠 -k^cK,HVE՘Z&?/ n`YG\ qZYr?|ʥ?5/ $+- ~FOלGEK)V٨}gցN(9`lzmE|ӣ6[z@cw|_6ztR=/ 1xqyDғqiyҀl?4&IS&$%ߠcI9nf!GG1~jJ@?=N*Y2ƒs&>1 (ƿUlr|D`?O ehH̹!'x8,}ظy~ IʓFE\b%wO^ԜxfxhLbk[m6YX@:aO?5kamُQ"mhTjd(b]QfP4`@~ kӈGwN15"c䭤+&8#dtCźk9/]zp{ɑ["K|Լ;z"$a }n)+3$f9$_Ve фWpo9ZH4}/J?nWo2qghvF?c HE 팍@]R^> L%~r[ej/Bعz_v7^|}s 4h/H.f?UZ!ky?BC7zƤxo2IۿUƅl*\ sukzRYw Zhr$[|VmA[gK:uz.^wNjPs@]k@F\x?g#A7\n*wӃPLj_vv_g"#?_]1()I_]m LBYwʒ88zTז.uVw>gAPVYdJd pT⥷[}3:9B_&;-v'!Ls-G~P sigj_pB)ike[u'[z?͗O /?o|ˢ=7R;O_`i)'ECabM#10܎%>>j]-%XT7.EGVǢ&G_LEIPXOCA48b1@nGz]'8qzKw-ƽc?i+s yң ذݵ~ <~CXwMzg"w-N+ XϠCxmw %~x+oSOSv7M.*cHSX;}c@:¢%q.w6n=Clߟߜm~f0M6;j{ơnM~c7Zϋ#C- +!i:W1E@+F|;?h9KkwYrտ*PJû!kvR󭅋?A&R#XӅHn`KNypϢЕa0 ?_}?ri=f|6Z̴ g0B#^v^)oXH(4_6tD(5hjͺHϨwз/VJWB^[ԓp8hO2UIB5 94Ƣ?&8nбtvPTp8sOJ)aazָzԎjޫTJM: OoVk?bRA29 wu"WiwwqL*m\T>”+MZUOϾ%]k?~G~zV Ȁv{\2Ŀ,ܩo_uE"}ȃcߓo6CO%-G<?$T? Y6j/{҆_g۠l◭ 9wvׇTuk5&}7z_ cJf?[JI" ey('\T}CMcKxw,r8M][dp=iBƘ?֑nZKJh+< 0}l\N$RԀ4-xav[p@ MݭIm=7']vCAd511ƎZR{VvqЯGuOq쟎Ћ5AE^ y&T~r8_nYL6z_??yUOeu|i8}ԃJeˑ85ˀɘ2/jG2%v_v޵OFrQ'#Sؼ/r70on9Q7ec774T :2߷u]gJƭ_!ȥ8T3TB-?}OvjިUfWE_?꛽XK|=]G߼ǕA_m'ݥ?!_Kțf5 ϟ?>3&{dw'!IvD-Ne6§Vj E^QW@C?E%d?;K]nD#{u?y? Ljs!PM|ecq,8)Gr`2,8L# ;z}Z td o&=ut+8ITz F\`.<Թd?F "YdK\ *}&(@n] (X\ss7eF O} 㳴VFӅߪH>!Iָ1eA^9꛼$W |#҃JJMf0>7Oo~<Аo0ܲP#8d'L)!kD >{Ynox LrvXMZex:AZ<}0#KZ=ߓFԑ 7ںvP{Ú$標r|J$Jeut Fm"-ʔ%hOf3?4l7:PPtѯi˗w^K@[߮j_ bP$S~IXݵOy^oDfb VTg`풒B#Sx Cp%V .!7UVQV tp ;ld Ӕ1?{pN*iުOi? KOƾ/wP}W=3YH0/ LfsMZȈG$jw,HDb=҂1w I?*̀HfGcQ5'+L3/lͯSsdwWeOߴǢ}+Igqte]Yv6;/S)m3 1g+&uJcꟳ%ˁϋGۅɖk:G ~QMT3^v1/jTRHߦh|.ODr*/fPz X ymBQMǴ"yq@ |A;{]aVrgsQ${n1?Skoҷ?}WGSQV?7Kow?yAOvP#~M>K\ xa|̂_S&~d5&~~?@P.|Gi#ȷ5ڕs(',<`D%{a+li>w碿OxxrQ |'3؇kl+ރO,K`*B.[!%rqx/Hm*cP䟣R^1_Ӄ 8k{!ud?/6BfEDT>k< aG1R꟢I#9jC L0 k G&ƂhB޹-"gSKAY3'V:ЎB:5⣨Q v󋧨n+TȻiM5K_-JbJ22| @%eՃրL_cQ_3ۿ@`x?]aw)D13?`j;'!]x,1"UoKQO#=G7X(ӭ% =Ҥ>ٸ09!/۞ . X0(XPSk"i)ғSyKF=?WO."s{+v F?r μBqJ[M[hYt΍%wm(?Ӌ3pJݾZv״ *\ElL6-XTx!ߥ6 P$5csP'/K(٪n_mL5{Boճ jwF6Byo/Yfڙ{ .!<,wj7 KyQ FA-|TgѫkGtGDfpK( ?@rS>F흨·qvwɯ!3y|JΛr?1LhAH;uhRbS]1vT}vJkCgA(h<>N<p 8>Q & O#=f) c 8# BOx@9oOOs]g@Րؚ؟dZ\kѯ-m/r-ZJ&xܡ?M>SvH?q$n::ԏR67f0m?p;+o_v\$WsyzvDaq㦥5 r'?/Npl:CHI&]2߫bb`_ R3'uDj4Lfjoiߜܕ?}Y+0{Obz@1 8?I#=(C䡂0JG]x&01Zp<)=vC*(mkbv+u`cU(G/pG;ކ񳀳xxV/|Kx(g@ `Dt1~š:dPeNm"G%,hsw!տ#7Y^?~9m.jGޒ[鸦gY"] -5|K r'*TźMiD%B?/O*uZ9j؜o\:t{, v"wDfb.dL/o2[_Toc"*au:x>Uk,UwZQb0[ dɌJ4Ƃm<!jjGNChnZw:{"|hAOT&_ڎ$cɘ5hX 7ZX:+oV_[^Tt7Mulrmj3^|UG7{lg_hqp;O} gcj:ֺ/l_nV\y6d 5ݞ_J`x"!{x-H?rvdXQ#YK ז-$?qA(JݩFO@[B CW_ :͸MƏo.!<lRIGlk{ 0\_ YAGb!;SweVT\tU$T8<cO?!M-2K9(=?Ve2b O+dw>h3(`V,6Jai׻lOM{oH~u9O%>EO+gâQC Yvd]wdˏ Rll01폏y1~sԣYn#+Kx$1b&;#:h6߫EfxiqjhLOGUsg +}2{l'mD_ =P?~n9@Cv> @ տ"U9@rCUɫ}c,`tv~_/tžQ!YQuVg/vt . "(>.[͉xٵh+iT#-"[»a Hk&? tD^%y~>(k!ǢsXyR*R?I{9%0*ѿy d|QĿ2i-P:!So Bp2q(`"XMlEqXK{HpNX+Z*CTJo3dSo4n#xZ({--.G v߈k!oo'_zGcm~9y3a 2YÂ{fGrsѷ?UiPȪv%);,qwms֞5iLBsԪGΑ#gYsɉBI7_t=:ęW^ZD#!ȬÄ>x`#e9u\vw|'EZgӿ3%PRȟ6Sxp5o ܐI!#ȟ/ckM2sԙ(XX+U?#%cP,`Uc0)hnc=MD9J[Un&/NS)HD*<c+([=+9Pg׏<@Bu^Wγ0>);6UE~0J*)[h 8mv2lxf?Eo\O#_>Kjk!Xx#LerM^yGlaYUa&lvCYځ?٩}GDRm AJ'k`~30"m>=`1Bc -A3u0A'~ֿroTYq6N.Q| o د%88#DFD'}$S vhu`48 AW_qiӤe;8U]T{?]ߤ0vI…OܳbmCٳoX.RQDxf9El#_^[t_̏'/}꧎տ͸"u$tJWa?{6dc ~^kWqpU nj)wG@Ru)HeP_;/كoݳ/$1_Շ_jYzGNV@$4r 9#voӄՁwSQ|z_dv#g`;5V{)eYuvɃqPNe(gWQ9;e]U)o*ц,9 |` ߀?!?{ 򣛴+oɟaY{G5w]!&{j._s Q8#a9Coa ?ua J5!@ϊ?8aaٚzB*!(+[EKƂ_fQwuѢlx8Amv಩ WKvo.]3 Ju_`߅|Hg3AGLjkKV񦀺1Qc3cD}׵ԏ^i?Ve|ܒyܦs=T}tdSxc ; {NEnZ ~| g?(7 ? O.z cҸ<)8&PxT5XKscq{<l ~~H/Hq;wǒ~\$/Uf/FoYz]1\XB9C ϪƁ-Y}EPD'c3|:}q?XuWl(whlqx]OX9'9d qR(gn,<"@IDATFfg%)o`,BN64sQ]JkoFZz7ʀ! Zf$x{s)v -̅mY7 Em;oQD% JZ5:U|s>X{?|6F$b1mY}g7iw:^^-2pK]4#$|i` jCqkwxw%< cSZJ0 W_{ e.Wd{<IfQ!߰ϼdj dqZ7-~~z{ʟ)]boe,&J?[gvf^xMyȪ1* ^w,n^3jF@`۞Gefig0Q7l0;әpõlB6zEU&un_2HlAl7g.ؼ:V@㛓rZ0E $߲xCPSDL7h}ɕ<_rOL+7kӦsu+~cwÈBv Iu;]ѱ}ڰ!mϛ3}:ihNJ7Y0\n^?/lra'u54(O7vtW??d]KP[]T3]2)<ֿMYC^…r?-cUXz伎'Q[n' B~xFr?v6]}ܐĨ)Rt:(Xć |*ǿw5IaG6w ucȱ{SǑVHoEg%G( 0&oVl0XP!ߵ_o)0TGJ7ysyvwF_I@A'O޵zjG6jO~j8mӕn] ?ϦQ綋;]lp7A, nƴ?W"Fߌ(۝;_viC v, +9g]5KEzz?LY4Ul-^%)7T_J,K?Fmqܧ켥l;&Ձ&kzjmmL8Ԍѿ\xZW~j&P%8 Cp@jqlkJi5`X 쏇JaV"/а&(hJ~#m *~&?n3]2vj0$`giQ"2O@ }'YD @ȵAn =R Knf'qQ7xӓd/jO;xyŶWV ԁ4Ѱz_8Fٖn2f0Kz +__&jϴM%=ܡ{ub 3?_]69?r?~;r:VoH-rO-l$cQȱdSj .`EhǢ(-FR6:bq c+/6d&oU?],JL_Wu4˿-nEw:q7X%?#j_?t|?ϒHC7ߥzeփͨdC!Z^\R-$LnlG [6z?ЬEɀu{mce egqo| u,RX*FزlcYѕ\. J9jL&2B]ԟʚ Xֈ owu.PC{ā 3%,ІWwjm\7c?Oؒw3u춽KAb0qaJ.IO{5c[ȟ$7r"@'Y96 t?p1/6'j 賲7k۴(a6)i|+ʦ^@6q]-~m7kbm+h8~ @^>+ Q$P gu7<т[olgvp?#_qS\z~[AvsQAvVGdF}א˽ِD[*[9I(.?,Q60ɮ+v  ulxR^>xq0z``-l'O?[@C~(* Hzn'9 2"X+6Kf 3r8?­ ?:йF, V$hЛ =ZR5P!ғli_RdbNGH[im8\H%^ C1F#lnNaPߋMP b<*o)4-Kjޘ)`_W`!#ƟuAïT0'{K\Ҽٻ(\7mpNh)E#Ac?U'<ŀv?h@?ԿDQW#)ib \,TW;ysk38Sy=6 ?3gR6ͼ?c dç?E9w/mJZ/pWdj^VLGAqlƆ:, |8Ray>1;{cIm҆8>%O&U"3~aI -L;Jr)qMw] YKY)?%;, YMmP:(, _gfj/??_QwO;eO;߿.|-ER/MsULʌbSJAnKRBP>EQ25EeuNfʟ+/*11C0F(dЄ$,IO$FdhP)Q]ki Gؿ_}@tani.'z;kk|/p-wSa:8GwP2F$L4`!Jw?,.~fDy2 n#&ß'%eDadxhOD äo !0=34Okq7wۢy0ϑ?18,8AnYPLh-߷opoȘ^?qlRRx](17Fаi Y7!ʓ\KR2$iaK8Y޲mL E7X۴pM7u^x׌_4 ?ܦ+ |߬T̨i^)'}Xc%-*z_>STgʣ`aGnWS[hbv߲-lnWݴx?p2[^#-R iDSVE DԌw K1K tyW*2ɿuCeBi}UA8 I?  8BKkcOa8 N>'ޑkfEPZL}q.8|=߯uL^wi;7Hc?n&+T"HP?ۄ'y21ܕ(UcBRֿC) EI|)pOƭèLD1or]8P~9ꌘhΰg5\^dR~z(?R b\ve9xRc֞Yx-_10f苮m(7'5`b]Z5>%4f/FQoz'96P]Ӛ*b8Fx^I??1d *JJnS,eg|gav#O6?' `@qJƘ#Bw3|; S;tO (E1wU)%ZnH# ? U[.8'dt 2bx~#kѰQLiA91oZ'H„gdeJaX4(CΠTؼk̕lE)w|g}?|nxRȟ;&*NB,ĕwe?88S}% `EF-R󑳼ZL`e}FMIOAPMTn^>qD_"_%T_\@I W+mkv0o%L M~E qX:?}>vj "c?6M d5ݬB6I!B5e(#b]@?p튃gLR*?;2_a9˃p Kqx&f_+4 ĠsLXr&sdOP]e;`J$ȌhwJn M@xgۏiNmۏߪDz&a(GLm&~vN_?Py %tF$]wVa~OZp%oMdȟqz|ʦoٹq j$_Lcp3?0a-/ϿyƢ̚rq}|WYW \7acEvsm15A4igB=)"[ɟſYWk&lϥhv+ Tqٮ˕EcsOo0NZuz9+?q;T`o]V :Y5 "qC& B3-tekE+FFxnAY?RڶxeȻ?4B3x°b K]EˋZƪ0u!`qK BH[D{GPBG[|Hfkr+2+ċVBXRW|]HZT/qO;iKS:14Zqic/9 /*EKu++h/y.Z{]ہ*o(8mU?Ql w7[ˋM9ZFЩ#>\J⹮Jkŭ/xjq]T_e;5Hx-wI~Om 604yjp5RW7i֟WԔ+y牋|F`PzCO)a&@APZ_IGnA@ԍr[jnU`tLtx -Xf?`K*?j\Xsƥ?Z2q :K3=lS]X,!y<5pCƟoQI=, mW&.5Ys\?;~Qc%PivdN&8 a:\DBM _G ' uJ>?FCv~D+@s7zđc%ry2 1Go" j%QVJ"o Ŭ[ NO   \~T? ^[Kg-\Bmx1&w Dӯo)A(EL&;E^5L} } =&F7BLp$Yu[P87`q$" t%[*+ywly?h_qIL?}7Ծp/?m&O97fG}I޿* 8/vF/}84뛌IX0gc8.=МO{ {bG}h0J6h pLp0T̺!xRl5mޝ`OEҗ B'?*.s~?uf"qiR|IQ߇7vE|I.s YVޠOv_\"7xT tmJ&{'rPz|aRNR;nTO.GFILԈXRܡO+?axl xqRRfGVl`(Mֵ wG Ǩy3@_JLۃv7Mx%/Se|P_Of'R)Uץm6+~ϸ|_uÿ#&φ58 lBu HY"c>~N?!%8UHnsbIpϸƑ7'lua_yI]SpȟP1,x]"~W벤?B=b/o|) &z~A[ߠI1R_yt98UZ~O d,ō KnMg# T0וbI9e5H?.Y %'N7;m:'qTu)Z"QLrZ4ZR'` pqCDDa8l,iZ hivt1嵳cA (dl?u%056xf/W>2I\%~@e,AiXĎOKx\pkkW^);4k̫n!?>So.yg6y?z AJcf_ |?ȫnj[ ޣJ#?y7퟈4t_P?:"xeˍ"#^/gZX¼pEb%,;~+2I^O(I0 ~ }+YjҐa4!Z!b<QbV+ IQ?0F!;1^-p*͎$9UYHzv-Rv{{ڱЃ#y]S-((D?Z;.xmDJ|Ƶk,RV 8d|[700z]+_ PR/Q^Բ&"?P^N(ѝcZy vs2̸o؅΀?l^#  ҐʯJ'$r;?CO[䵄]&?(|;hPLd/l{aQ@tÊhy)C{WU{@@B !![:E|l3;3:2EG޹:ʌإ7E@i!yY9nk=}=F(do 镸vN ؾMvL 9T$&zh,8\JGM`Pu$ < ç%#iP'0 ʿ%پeɪߦiKͩTMnDB8&L1aZ͎6j? ԩk'(i_?ɘw+ū֖EYuB0ɟho|GλY~|0w~㚂CUDŽ#e+ɲgS"٭4% I\l>iZ: b)|tB\yz0d_ȏL | cZ;^?g?:/ #W?v+?V ɚuMc$X]PZ hB5̀+9STo:z̀BMF1S-$8, ǀ(}]&(&H Q.R$>ago;5R?!?!}R=o3:RGfLt/H V> &?z0{O+k} *}cAX"J)1 t>c52Am?Qzɟ?^)8%]5-GpM uvu ֠ ?YNÿשǪN\idzR8rTz+-.pSqjg+!6#yá_j,EG߆-v_= 0%q;6Cūb*E I(UN'⹠?S^Yn5G-B0D?;u.c|XG?%qy/?l S072S;#c3] cCAK|O?~G Qǩ=hNfJhAͶoi+z0]/ӽHK1$,Ho8$1e"]~@(WI$m'ݰ}b GuHGTӎBzgL@8}5ʿWKӅ߄ X!Nq4IhF/RSEKWzO VÄP~;FW/peI,@7LSk5mX,%eO&S'nc̎Yi\g&3{ a'17 k5>ql?y Ϩ տqR3&@*m%&w+(x:κїT>g2X[\B%(oS[:~m 3@o q c RčğCރ`&ab7 8 ?z/M4X0a`7Y7⟈3-Iߺ,G3ɹ#Ss䇑--]]caEu:v#,7pdW׿MݗC{Kߜ6g|fU}~ ) {{a4ګh|**Uߑ;IH:?: uޢ%v+%;izoQ% ;_wU cIx) @Ct?ydM5}K?h,,n;~(!fB.c?YD&#pz;Teӎd;jK5)NohKˁGC 8v H<VDN)tKCPf2?xWO!gp*'z}!~.ЄXƿX|Kʠ$D pFuX@\.8[w0p|B5l oBfrGl5Oh1vOiN<~y.C &i/W?v$2,Qn~#!h!Yj34^Dl u_Տ՞E` ?1popm5x g;*iGս[F?Gɿn\6H\bΥO_:1A9DoM&N 6DkoPYrάtV*QPDn]:uJT=϶Ac|_vQa#4HȱH>I:?*B閸d?_ۋC҅mX6%PWvEgOW_,GLV?xtQZ 440ei 7¦Se~7ϝU㴋28woV7"2{xhbn1Yxt&DHjːջO/(G1 )A5Hݷo;ا/Q /ڊ&8Ӧǎۥ T "9?jrH}2%wkoq/LrI:sy[ #lVg+±`<-4n1LJ~_ 0(-^o:lVSɩ}h - HNO: = {/7os;E{c㽳P$/ZVQj(6ө0JI&.Kd?,d偻 .ߊ_zp.я2*Lv Cکiro.$52&AJH'w웒&%0VPL^;8HIH3T,o+J_t`X7?):BgGo0B{Z ?zMH)E41׿C?X0qv2#Qtԛ࿮Ko+hn*ߔ o{g1]? P=ؙ$^]]ְ7$_mBmj yZcI@(h6mVGWǢ _˿XDZyX> TB(v_:erEnټ!0Tk ĎSS/CQ4mǘd$volu?1CB{=/!/Ah딪P"S#2\U`>h*3#*S'`{O‘7d=vW2$!oc7HRqn BB'IZJn-Re!q{ێBc2;_˕As&KLGxQ |G1: B(&8|ZS_"ʊXݧ??$صX廼cY@xiGۗɶo{lcIGj*3SNbtPz_Nsjk5A*E0vSשc[>ޗb~8wƤzı$y&/}Տ!SG~uob iǴ:U"8JMBc9Euʇ|E?L6wK?tf_%2>OakÿD3 @IDATpXQJmO?2 cWiҋP|Vl:T&]~E؟$`Rǿa/dSD|i" ˪𻝅)J&nm[|C$(*tfe'XRDG&[Ϗ. -YPđ.ۍeXTH'L&vf`; %BʼjСd\< @M p1P.C_V $z9pإ"Q!Dr{='$d}n+MLEH#o3 ,o \SGc-Ptzk4'hd_|l O0ZLN驦R~bOE࿩Ŷk(Eτjm; k[U|0~ HOeQ\Bhe$ۮw>vm; ЮRRm5Q`+z@Q,9[IcNX'ٚ\{ϻU;٣wx~LW^Vh!^K?E3lǎVuOP<ܚV0C!'͉9qcuŶTiSR0fuIeX!.:_sh1NP"!9,\Wt, FU:0 9Hd_يt&"ƣtbc (8oet:v)?_ߌ. اN[ n%#!??7o?Nĥ]#w Hl(V!=գ~oJ㟦Ie|7BV+TB,6BcK+$}kX&\/bBY?wgG< ϸ?s 0<  f ~z`Kcz"O&m?Bb|6A "Ns /G,c}Qc'Շ94oY<^ȩ[,^vm<ީ㥚L[brvHf_"Pi-Ngk?ܻZӥ+of:_,OJ$/Y#V_0NdHۥ5OT_k I&L[kRnV$ f"_?ʸ\*j^!s\n'9NN}9|lŸ5=ԥ )T1=AD;o{?w1amo*74Io8!&{V?;Fe:$= tz.'_A&H-:(~`r9B/D"[꿹uAiD'mvuxr\p/F忱'l=9].j֦C%v.:_;Y_gk=~" !b4ڵUy'sLrBQG/fm,+QwShW)Q^ ,L9mb/4>/ 係}w<sBM6䯔I~4Р;AeU6ld?2_W/8b)a*։/x,nȑKC?\wm/IȒrU_IJ.ϻWiH_/1o*mtM ?^wק* 3R뜷Sc_I[3_A78R[QEҤ: ;ݣ8txa5YM'vLM?_M)(vQK%cLN(tGZslW CC\8CPu?@9>*Tgs[NsotYX?܀4gRJLX첛V{S-ԮS&YZEz]#icx#4!)& o*rdЙCU{:X1A:Lbw qlw B˟HV+eE;V$,NB:<*?VYŵN+VgƤqԫO]w;ǏqLu]lG:=Yq#=W[M,mہ[wi$\ TnB]ߙ)Mۖ&wGqt `qN!u[*X#":Ϭ)YJU9W^pD NV#N_M''O(>zU@|sg?9'qֱfo 0}/ʰwFoUt-8/lxm =z'w8p]s޻jL¨O#*RpʁF(B'~縠neML@"P^7oG$KɿQ<+L#wK3io\b?5}_ЮQ7BnodRÆg_9=Y\ORmuJ'UuoRƿ[]7Ȋu Of 3.fgp.Z0fA_K}Wj<$v~ G=CpJyl?W=4o>[ZhIxt'Z?rG7W,~l[U:PnoD݉X <W?E|Wg/1^(qd??[TYL=ah Z)ϨmR&J5HhI_ <Nf˴;~;pכUxO8MfӦΨ팾ƫkk~-gu'!;M?]?#f,D:b< >+M^Iy?|h%Y,U a7iԄ8=wr=nop2e:&OIc1/FzQwɐѳKLuO(WgS:*qU{$3T GZsEWETǚ0{ϑ^5%R9~Xab;jHgni'H3xZurꔃοʻl^K)ei mb圷[Y69k+qJ_sFz u}>!FZ'ƿ7z|;50~*HKy먎USP:K6*kQSDk'egMk ^VobGJ@k?\3IK謭Ntj秙;Lq]U?t7\JmˈI?GHxݑ t` 9d$գ(Qm#lQQǞҠm S||ʧ99:*j2ԠPphZ (/}?&+j tb"O%?2WGuO!^<@}`(nH`WJ\,?]U?  ĽU\ڀDIdW{(%t~K2(躃$./ \nda#.I<8JvR?@fw{mzdE @;7;]w雿_V]:v νooՙޝ?Um@q)1;" ;LF/&UY %BOI_k޼Z#\aFGL_ۢV}oT6([iW;vǟ6:BvQGmId$'ig[׉67IVsF-]׼6;P@ɟaG/^ugwO$nG+'4?@O@86%|y)#/W ] Dᑻ>]w/ ?T~{OV 9'p_/wrji7n ֭vF~G-]zCJH=+o>I#~_ܡHWD?8>ۢݴ 9R Yx" 7XV4FG52(~?rh3:&`ߡۑ3hR,ES޹t(R;Usm#Dc fhK.c8/GUJ9ʌ K6 c[l#kchpA`9P?4?&M,m1t7L2Q[DÎ:f`}, iPN` ކ4/diUlPeco4IDFh]_G+^c}/Cͮ+ïOG}\@HPYAqہ_@zT!lSn.>N۩ sq3Mٶq%1aP06o,OٺvmV=aj$td ~6";oRC(p6'U ޢ}sY]+֐T8",ߞ؝1Ӂwzn{fyF%4ҳQܩ;^:΀B$%L~Oأ$"|,Cڍ%xOWس{e C1*XKVU1EcKJMȨ$KFv1<_}gd3Gqϗ͗M._q-_:F=t|3{.NK??׎ 4fb@?o -C_bGm\LyFQ,)sB%QܩbS@ m JHg 閐W3MdoCev`+p0S3XV|?1CD/ %R*&WCy`Cj`%Zxޒ jDzbu}Ucg;y|4O1A5O߽FκEB XS4dv PXƣ&a^w~9 1'[)zD.U80쯉?:t0F X 4At_? 8M%! S%.!?oI@P'Y`1?uDߣ$d I_yR֥ck]pۃţOp㡤yc-є?]2_;f$ή6m;ړGi2g,i ,Vh{^k3:-5U22[ >N"d 3LN1?^:vQx.u+pu:mO{ud#}uO>; vH#F/j#i vґQO's+s 74%UE,L'ܯl"Q3kOa~P(iHwPNl1}Q?ώ(ӄONQ(g&~w@v_= -.O {5d |(M9bKqǖ>fJGmO;um-$lk_1|ŏ/6L/? !e1Nto>D}(ͷ_0DD0I}Od>*j7UbL7fȺ`=]MQh*H`^^ǎY.\VRrVOj4a뇚DNOWr䷶ U")BBk?qNCGI:&EJP|Y7D_֖w7+ȑ.ޢ&}hZ kw$&IWgi^.ԟH#89!ƍ,I"yvnkjv@F|o`RHT|~vk5_?U/fj'v$&[W/`Ossƴʂncn~Q>P;zBC=8V=fۤXlS8g{,ޝ-)"_oHM;ⳓy11ZLd\H[,&qSt2ߪtuՏ41I4na2ɢ8&,h1f]E|Ҹ%59~Yf'-f?"BB~N~ ?ylCfՓ&H7zz*rNΐS%?)-_p@&6PHelJI1^mqgN8VFTj,읁1ْJ O9 ȿ(ܮvmcotV FgQ z+tg.Vk5.$=>Vw;8Dp<;yCTvho2EJxM,ˏ_<:Ϛoz3 @5 0y^zENOl(GK]M*I'Z\! Dz6 ?FFVC騌QL-r7k`:ޥ6o(0<_BKTB gXVuI 3{T@įo_Y/u?bA] ~ [iGtLGUkȁjWg倞 y(C٠:EװO{bժE4Τ:\q g;(NU V߄߉,!D&(_wˎa=u]u&pK@ 6 e#8N;@$qH`6}g~dA\!5ۿKScwk({I%HS/Aklŧs5JZq9ܱ^LZL(a.9u_-b]pS\~ƿPVޭ";'W\dq;!?Alz7ٱj՛yc M>?4ߐr蔿c0dY*&Jm/&ĻdkS6+rJ^`tmS)c&ujOR/Rۥ-Yy;1?ԋ,dԨS>@M =o1wLӋ?[_i`.3@UvorTJ{տR$M !%jv[Y$)8IH;iϩOu+e}(*x"p?=7$3HNP?MF!?cdK̴t5W)ɁI%H4ɑ9)a' ws["UK~_w ;o+WKܞQy+_2U gBRn*$@MA %^Ayvq0}'hp+3Yy^oФO'UNÆ)xa'qdgc]LZIPAzI,S?>:OvNgCP)ɲ,-8"}߿'c&p_=~m F~ՏM7 Rc,6$gB9&kWvmҳpO&ަvQW"U/qx2q?7L5ϑ9P"_I%/}#L U\9Rfk+] ^@_7 >et׷G k??J. BV7PxADA2E[SŀO3KŘV&}L OCez +>5y/[))q-w)٧fBSRY1h#YS}c/ck9Uwbk]`g{c5?KQdf#N'`MP$w#քܜͶQwQ4"g4ԼXoW넺gԆ];7[H!ϱr)g?rvp~r)> _΄sMrk;oOz  mZ?ӗ2&}}_kElsݍs;4F;bZ6㯊FK ȉ-k3~:;cXHrgǍnqqBg5@؁;_y4-:x]՝vT$6֗.X沲,(ggѳ_ObʟJބN%]xſ9# h%(@i?% v[sJ'f3@@^#1x" tAGH?|]D_\Ev_oJJNQ6;>:CryOw}Wmp]oQJK)ɠ^v4_x[y$%J\ z'z_?CG;8(_5˴+\\< ÿ1c&ѤYp`i.Y%1L]J𓆒jP) `bK]8+`_Q7 ~G\ΎCmώ˴u]G-n~Ԓ%5Oe/wG7饊"3oo[ LcO + 裵ȧ ^N(nob1OXϧ.DK )fжn-y{KVw2v?zaB"0HIpIp,]뷖 sߠqF? %+3,NFCsl?C?W"<6??KVP?1+~dV9?leEI.OsjA0 ㉫:}}&Sx%iT no [O%%/v -~:?_G_ zO!+7E"M YgXP+Z^xK1)L>< ]u[>߿XcKE< %BYKlcZ"t "?5[O"WN`,ش*tiL&83t)L#KCo)-^`gtt[4ҿ:_؊``6R̈3>&2+Ъ<"r&.SۏUplM߁c ?Vtp=@H,~b׏ttηpVO9y]Y=asDڠ=~e/Ԏ1?p>al"VzSM[8WIoԖcf-:bXX,D:4 #}ߦΥcgh[}jHٹ5"M| s*y"1Flq&u/xb׋h;ѡ?.D=%tD ?:33/o_WZ\o^#e$4GcbO}g3 x]#\T,[Jz֞+s·n\$?nR/tOX=v76h'Pj7 }r% UJ?G~S(&ܶYsFiʫ~d@"bXF|DXRg~J NcbC7 ZKǞxd*|IMT/BlA9*vC?Mb__U/(au[y\G ?r*gΐ9SHb%oI7k[/\z+`p5~EVy 0 -_?S.VB;"I~OqSPK&(c3^BgZxXdDC Vz7Ru̐z!/#STQ3nAHʬSip]uHy֞B@IDATXeB1dZ} ?}P \ihA/M.9 1lbeHMu|}gmtD_v,t^*y>3㍇$BV\mS9yv|l&ei@?S8`'t,k?"ajK:/ұ.GGh_Qw}j3VcX ?Tğ }w38]+vn.vTͮςZug%YK0{{O*M?=QAzW.C34TK.S> +MB;ޯc(,3omUK?ZϕOD^Jb' S?8sgK??uv2ȉ^YOġ>zPGϢ/M^?$؍{j'2\9L"'#oxԿrdF41Qϣj绠MsNr=fO #&} D're8c^Mb_#y<?OyEi癬%?s4Ӛُ%ұc]#Oݧ>ykw=[:/S9Tq "H;b!φёA$5e b7dF[z&]y?H/L2QiGO4^Ы2|[6M$3_irNFA >|ul?;/X}zM'YE ݿKqPr]C*Ԙ; ~V>&VF`##Pi, 8B޴IyQ\ay[47MNPϛꈻDm=uyYJԍP(5Q`-N3V37M 'ɟ jRi\o/%/`>w>HX߸^hlKX 914+8=t>E܁c@$LKkI0~:ƍb]jX>%3Ͱc*f ϙlF *TzylFiPo:JšC̯]50>sZ ,|t3jyO16ma?N[2st*O\w{XEe J5&.{:EB7~Rճ{ T\s|N gMgg*l̀wՙܳT)Qo3j+Vx)`r\ 6f.ͩn筴5X5oc85niޒJ!?8U9 sAX Q;tFuXmQ: @VkXsm$8'@e*lNs;M`BL bpQ5 یİqg|\].W ;C`s:COS< l'FXY!̠ ڠo?U#Pp<Ļ{ ~SXsLyc6g0q#a"?%tReUck)J7ZM]ig5oJ)3ܑN1Z xur(/ѹFjGؚx%x*hgdV<ژq=6/|lu,r<Ud`/c#l`#m~ف)9ӗݦIKt7/؄P֩jp/e.+p#m<}1M~mչ59?k{_b s6?Oʛ*0Q̯b`~?NV}rnPHt`cP o/^$6C]1eףCG,xhx [p\ΐ`緮VGp xd.V@tRPU+g̏(By\[5yḐKL_^Xzzm9ߋ(8 ސV\~K I:8ci$VUжC%p8"$NY@ XVGco|Raޣʍqyi[3]aa;\rY=O-m;;;cO/;}nΟ7!%~l-\vge-uo۔N$@yoC=4S?6*G^~HEص5Dj|W^W(O28wO{jzbv|<$~ڌ'VRxڔ9n뿣UEq+LvNő65r*Xmry?88ocےKsng$^-Yzԛhfiy);hcoӞS$ؕ:UWu@ 0~IOTU&oRf1PϤJ?ucpmMY3YBc|R~xP㕧fi" M/^-K_+FwQ(_ϪOIV&o?`0߸F'Uӽ-X#FPO9??(A~vezlLZhk!6YĩZ\FvDv(-p17c^&x,gZo[~.4'kkeu?TN20H&D^.O]҇7 lbg[.:ԵxV6꿬a[G*[/e ?]ƃڟÄc7va}t jl[oڍ1BXJۉA?qvN#? |oG 1aq'dbg YكٳnA?gX3Tۉ[43\7~=%b$.Y?%bFt#2/,lW/I gcѓne.>u>o\ߔ/-fORSFE'n 0')DBe`yR״ID=O2; Ey+-u/o߰AQ #&Nֱ0.vtd~=x#BYw84#I1:7 YelM"$=8S+]}~,RqmI~$[{M{&;l{n#gY=> |B.z9N,e:G.f07Vٱ&})]HQ&}VMr`PpJljw[}?v֎md5܅!|L?:Sk각:չ/*~R8K6>/d2<2kIIFR\y{{>+?Q -ʟ~gT?Y(dV 忷j+lmm9/ 0Fwp g?6e xRjf6B^HDSEl7(V$`FWNT򐜐.}>ʟYMn.cppfϳϤqWQ/׫|ąڮ]IWaBMxNl[m{]ˆ?Dž *^]HXa:fNc(z}I`KZe>a5Ӭˏߒ350x=twoi}ثj^`cNRDrD\01 HI)Qp(s_r!r$;HU]ċcVd  0V>o{w>]֪VߺQj=z|giWvGキ}>V[.?jEM?tYXM_e?!N_Nv_RO3g_~g?뻏Vڞo>;?=}T>C}O^7iVٛ&[j.??޽͖5Z_?sJ/ӿiuWK4K].vLU3gkjWOunSsOtM4W}5r$y_y_CO;5]mگN;{vexP}y2I_}Nw ld%՚.w_:5SfkfӁ+ο_'W)S_f;O]_O~Wro}kjm~>}~S~폟O~6y?_WZ@C޶ֹ;uwٵ^ ?%u\o_l_8c}Wڷk *u:')o>S?[s=yѷu^*ȩuurrE??+?~6sʷk *)LOIV6ۿkw_ԩ菎q+S< ə/oF+ >_ }Q7~?B?)R \7|xի+$mm/'?4uee⟬z+ >AH_]"*ߟOJ~zR 4|ݮU_>Rm߿;w?S}<g٪b$<>Tu}ue^-4r%Պ~Yկn1r]?nVg{>f_ޠu_'~S/CӺUϺ:?#dR{~W"ЈUwv;Wdǵild~s~u*H1o;R|yoފҳR׻>aʪFusC_v{W۽^|K?O/J?|pz\q~ .ok[c !eZ~T6?V?ߗb6<~"ivwT;x TUwdV>9'Ň/-f^Y()妏'ɄvZ|}k!oD]q>+ʸw8ԵCpݿStݧ>ݏ/ޫQot?|yl_ϿL? ,+/?oTw~7^?j(\$목wZ/װU>\#9|rb>]?o[_o}w>9q4ro=oo;}r>/~wZ?7~ES˷81w_S??|(wQyҧ|:_!R[m5_oY{8K}M'Z׿vB>iUG^z__筗?y=I>S*I*<rV&_߮~w#Oדez_ 9?V[OU[Ui+TT_\njվ _tUɳ*>߿/}]~z.C:A?Iy P3;ڞ|a]?o_2X^d.P?_/.&'OTk! 5:S̺ Rڟe+qRHZ T3y-ڟڟڟڟڟ]}LFjz5F=j&Dyץ$iC ?w?KK: 81}' 1P|M)JgO DH̳*Pf[4K:cWs.73]_˜q~ɅٖGoo//*`//sI몚qՅq-f۞ζڽ"~kˬ!^'_ՓOS !WQC+A&JC+s0yC҃g&P@K.?uMϫйdʋ?s?a,ZEK]~= S ~)L'l,Ef WUHfZ}mˬy&tYv|*ǺzmݾF4$ /F7'Y?߹f)OG[iACKW1lQKLcMV  ?d kAt3a*,]n%DtpGdf@U `"6˚fåѶ"￁Gg'o]kZORC]#\G:J O2)As+0D_;`.^RƠEI[aNK OV?Zۭfw ,3^ so߾4;aUy=UC^Ynv_9vs?$uS; ש.dsQWsBc.j^EjDCabGJ6uIo(UXRF"w_ibUSun}v*W[ԟ& Й)KVyq/?)Q?qPND=:HsYuJNR33iIL;SG?N8\m?5\xHx/ZtiR jN-u k*W6sPxP/ⱷg)wfx>zeϿD҉S?OjD҅w2JiyjFA%KI 'oiCtrZQ㹯EG)#rHJǭ֤UӾW{o~'kMTxJKŹz=IH:ה}+gddʶ/ޟ!dG̢GQR(M2Կ?}3&pumI*R@*uӳISFtKe֤E6~p˦neS2^rJ2UvQi 1>t7k-ny!|IH8Sd{VTop~ɶNTj_idQ8?ןG\1]L(lHt ۿgdGOkL?SX0yA=E/LHku5<rv[`I)ь[- >:z\ɿ~/rꪐt)UL:T6ԵԿ?տW;#5.Z)Կ?2%)T?? a)GWPgZz]B[cʊi%Uh%4g)e(.n??bQ .-'YgRsƅ*y^~JM" ԵKڣ)zU^HCb'u1gUM_sU &PLMbVRg:տ?q,SnEI-lIk|BJ)#S$P&JF____ڟ[٘KmEhjߟ?xwB'%~W\#Ͱ֯;a[Φy]Lp;^?yGi+=;f"*0[gӸeY[vkmixto-elE#UL Ĺb?/FgV\[y_\-T"ڿJ3vճ:]uNpUsW^]"GKK\PTw?CI|j'v*ԕpJU$_z\$yk6uS J@sբ/rʟQXWCӀVJL[CKs?L ZTIәAG%QcMߦ@?%]K oNv g?_ O!TU9g82)?*QL>3b# I'V5AS;J1X2(]\<$sȤEK% /_?Xeb~2?tw , "I-M}G&du)@Ξ5LJVg_kݾ$yG㯣ro/!mWF_{u?ߺ*Rti]^TLZU?5Wge]nt{CCSêm'QhJ?A*ҔjM2z)W7o]LKZ+-L 1no?-n6u9ԝUHT,fzu[뵳zһM3u#'9l'6YOZ\uAr9t&ׅWDDNI'okGI'_W$N]ju"?u_IwI;}ԓN&MS0_ڟh4L:w)?t;[ë=1.DLk3]28RZ=]u(LñY=vw9r>iwOZN>Zj٨S IKIT :dc D$I oRD])ʟ;: WwE=N~9k-+?uAu;tGmTC'IwBmoqmǕyv"Ŭ,a6dm;<=L޿*'?ןGQV AcjG*J4MUl/--T8'o폤Lihmg'I]OO*.S\zT%?VRS39ڟȤ=)A{* R-o`nZZ=70Y_xw$-pK9q b۟@붦7`3I=9]֛SMK\FjDAuaDC5W&YjWT(y6jA_ f\ɺwrOo 3id^CKzw+1Hs]$VpN ]Vg^Ha:"W߮nFC+D]+ꟓMWΩbmS*6Wo:_]NRB]_MWe~rA#ƙޠSDm-nnxܤ7_ uW|eU_g>N8/O ퟷk{ؗqڧߣ_0\[W7?տZE]9ihUHS3-vQT_Y|BSPĢ4e7?TECrR ]Ht!~+wN?ʆo;UYޥmb'V 괚)RtFiږ,ЍkK]r꒦V)\$yGQgKHoI'tU׹4N;kWdho[W҉WױhjjVAy.MGhܹ~h8ӈ%9%ΣCK[zvnTr22mœ%Tp YǡKk&{Y92g#G;ANDn_Q,tjͤIԼ.ퟺF79Կڤ5?:Q|/rLơΤmQW%//)<7l(?+PT+d]~zgeSJ8r/.0\ 5ZLZ/[>\%WRefMW=g,ɮSXgm?[sY+%D}_yǮw,:3<#2ֳlM>" |%JG甓:Կ?տ?Rwg]ݪj SwҊ /_9fg:~@?a҅rJӫw;mw ,Τ4ri$Hn7\&A['I? B89͏w ,:I'\@K Uq[ k]wWe*{}*uXx}sTZz !;x"q/?;'OT]&](?Կ?!kT2HK+ ڟ]?/tvMy=i~~ o~KאET6ӾW6?G?B@*<%׎6_.CZu6)Vz)_n 8ɔmޟS@Le,*9]ńת,hv})LRsYWWy\?ҖE?2~xc?d})52^n L`Nmk8#xM?i,_Tpu!NJ)](E;u ]_)>e$Rk$N?VAkjQzdU3wҁVkFz|fg???+Gc;GT9Oɷ)Cvor9,3.uT2]=B6}yUvH|ޟW׆OdʟsB%:U3SS{?Dڟ]e1 Re/I]mD39',JME/Ɣ]jn+#$5y,KN'^"FVi_  w7hͦy]xg޿48J3m^ ?)d&-2 u6[aZ[z']gK[z?St6q(#Zbt&PneUι[zmyU2K%j4gW=H~RZ W.9|P>[zOU. ٹ~ԿԿԿ: Oxc?$zO?T+TIE;&]Z/?Hjۼ6[kӹY7eN`t4W_-z_!ןGIUN*?Կ: nudoԿ??@[ 뚨9@K?Y7?ޤmj]O}O TSŚ۽m;3_Zr}櫸=ě|&U]tR"{8l<Ԛz^rVޟl9orHGە/*.=_1YmET*sUTrIIwOrS+ /}f~tth^SŬQhGMM;/)OfJ^[zupvkjZ'W׋ord4GIԢW ?տJإDϵ?A[C.?l_t͠ikP1A&_Ӷl/Ű]]I'ooqr==.W\ 72n]iKq{\*:4Oku@,es$V4#VN1G%S?]%UPZg]u~PԥF-IhtE"__$4r 폆(HhiuYe߮PiTRU$SV:Q,J/J ~ERJlQOտK &3QCM͠مG_}S*9)reHɓ3r2.:^2*9/ZQ#Qv! $ZZ'ICo7鲥sɤI)C4z^+qdԿrNޑ.QOѫMI"O$Hޙޫ7eg.OUC#e-BQ BK AJNFd-DGuN2]2+4oow?ON#3gYjɺkΘ&KOU!} z'v߶m9 @ @qL @ @G+kzH&$&_pr;SZo"@ @<%D @ @2I6ֈ$S2%>ҷ^C$$qH @< %4 @ @r$dny@<߆+rc)+L @ @Xr$@ @'d:lq$i$dQJf< @ @.[@`e_ߎ @QG*Vdd OZwǹv @ @\8 @ 5*IH)Q#5"I2I%<_K @ @t|V @ @ $P$$)ka$!% - YR& @ @,y,g$@ @.<&=,I(?daJЛ X( @ X<3{ @ @Ȓ_R$ERd$COq0  @ @% N/C @@Fc"蒌Ti_#MrfR Y> @ @! qgߒ @x$Jev<;Vg @ @ ؾK @.tɚg̒}&8k}8Y4 @ @.^%}A @ @f-Ja&TrtpI==vtQA @xK  @ @; LH(I\IH]2#$[KrH&D @xKy-  @ @gt TI\2S槹-No"K!i5 @ pK.N @ @zĒUdI*fĒd 29ֆ58~; @ @{|r @ @t"[ߜ`w}_'@ @<%$ @ @kX#-H NSL:%,84 @ pOc @ @oF`ߎ'Hzx$`MN`&pf>w!@ @m%o x @ @ $drn:$' @  @ @oJ@`ɛ> @ @HHBIz VGkrI\|: @ @\ @ @ $]n2,I(?d{ͳg6 @ @.^@`şb_ @6IKRdIңГZyڇ @ < @ @wX3Ti_#MrVHɊ'ɭsL @ @" 䱜iߓ @xCH*r$1#/!&uW~L2y @ pK.z @ @ ԴE @ p@F,9^E$dZdrwtA @xK) @ @$^d?H7%֡&3ϭq Yrn%@ @v|\ @ @ d '5yP)=jΫ}kG#@ @{% @ @Hr;w %䔅ؒ^8WP @ @Xr$@ @'paJDR$}ۛ;$=Ni/'V8m @xKy-  @ @g&J28IUI[7Wp%ݱNjd#"uC7W%SgВjH @\ ; @ TВ(-p*ΤNӹ5΋ @ @. 䁟@ @̨%dwLIϟ>z?\e٫  @ @_@`?G>! @ :4R" #=oNUIIroL @ p_ܗ3s @ @oG # $I\IEc-ן^1tI7 @ @Gr}M @ @ 93TIOuh2x$@ @\> @ p@nq[ddtH/h%[dI=KVM @ p!K.D @ @ NMY;tODLI+d˽W @ @w|` @ @U ̠$YH糥JzOX @ @+  @x-1I68=L 0YÔC @ @- mO @U `'QJzx$lMN؂O' @ @7' Y{' @ @> 7 }E֐$5>II6 .gg"@ @^ @ @8UTIES`%e2#2%ٞ ̳ @ 8<[ @ @QJj[D1#P2=tʄ<`V @ @.L@`ɅP_ @x9mIvdAJ:d[roU @ @& 䁝0 @xkTcHJ2kILjyK^p8 @ @Xr?ϋOE @1$J4R2;Y `b"@ @<6w}  @ @AHf5jI7Yw -9 @ @2Xrշ"@ @n)0"ZÓL8ɌRrtpI==V8-n @ @, < @Gf8S OrڥdG2jIanS&woG @ @w% | @ @k8$xdMǓi 4{mkTN @Ky) @ @$з9hbG3bI\68ۦy- @ @M ,yދ @w'?QKF7-$; @ @D@`#9Ѿ& @ 5BIF:$5:I  @ 6HRAҳz3fIMk{G  @ @oP@`V @ @}8Ar:adyz{E& @ @KXrgw#@ @nP$MttH/זTN8D @x<KϹM  @ @+xd?cSQ%+$a$Cͅ<* @ @\  @ @zXS,ٝzy?W@IgY7˹6 @ @.C@`eG߂ @a#9ed gF+5X֓; @ @p|F @ @ tIŊt? @ pGd#I1& 5?}Z5v˜; @ @{|@ @ @u 65jI'C$6N;Ԫ<ɱ  @ @E@`}9> @ HR2.Ia?Θ%6D @x,K˙=  @ @lS)@IDATg t <=/H @<!L @ @S2Iny;d,68I=;ᬈJ @ @ ,s @ @/ p}Drd"L&$C{͵ ]  @ @T@`=q>6 @ jN/rʘ%L I_*}r^͛;  @ @ ,'#@ @^@84%+d V٬  @ @E@`}9> @ {"FKv<0v^Om  @ @oZ@`ɛ~ @ @84rWjxf27Y'Nחi @ @G@`quT @ @MbJ*Ƥ"GfX<7J߯y@I @;z9 @ QRSb%wHɾL<BJ  @ @Xf^A @\cH*F'x%[$ĞtE}s_ @ pl'@ @.Z`1TtI*ɴJ&9=`9dUEr @ @% DR @ @CH*$Կ̎c]qy0iN3 @ @e , @ @7 $-`Yq[_Fk]cmut;Ժc~4}x:Uk,{8G6 p/.?ɵ<9<$_T(?ԿV> @#ܗ3s @1 Q3ZSkG;$QE>H=϶?[ZS0ݼ?o.\7y#:Ӝq刕Gs?k+ @wi  @xH#8ҿqֳ}Sy~hPңn$fdk>?ڼ$S^޿9BK'p<*+?]V(pOTzV ?b"@ @ ,'"@ \3HSͦoS58_wjV7UMbJsO(Gݷmlʿ~oIk__g(}]\ro!]_*˿.B< @ pܳ @I q"~L@HfsǍY:$ $˔yI/{zlԯꝲu~$ Yz_i(\5wO_O_땖s0 @7wy @xZaZd= Vt@Hy"LfzS׮u}ul4۽s.\J\[n5*&G䖡X9Vj @/#ܯ @Qട&#ר#խHg`^??u _r+Y?׺>H潐W>_}>0#5?_~%ʐ4y?_[? @{% ^ @[P gTI~뀏 t\U^_j^}sg#[@(5OIFEz9^a^1s mM7Ȣd$(Լޏ޺nkԝXg1'U['sj#rI֝^+pS+f.J%, @O@`;'> @ rG~<wSA1?/~yR槳2KqO怽f{E{ۮ`ux*+wh HGQzg, @oz5yL @<}sQ;*dE *7ĉdJI~݂Dm۷wZQw@Ǯ^kc'1Z?"]7/,C#U(?uթԿqc1'@ @   @_P@7QH.##r!%3\tW"̈s9"7R" nn ‘)v.`BHH"Jݒ8\]H*YYޜt W/m4ytO3'yݒ8WEE"P@(E\ ,&BE"P@(E%6g)0pDg3/WaqҤPꭼPNJx{dmFdCKg3ao?Α38<} @(E"P@8XrqMR@(E"Pk~{/k#mOxz-Hd]{X)* O%j%a0?HK?;Of:؎_gGuIm\@(E"Pe!jjS@(E"PySЇ"V|A(t?9s10ҜW"Wx}kw`Ԝ9A mf"P@(E C% U"P@(E|rY/0{n;nΛg =P@kAh9 OFbL§ ;H?ţ[(E"P@(@K.EO(E"P@p w9W#FK BisJz~ Tܿ`y|):ro>!j{POK>0K@ ?%V@(E"P!BE"P@(E| ?9"P@(E"P. R"P@(E"Y. R#8bȐ~ O C_ߜ.|—-2rB Y#w*µ2Fˇ"a&bQ Ζd6ɛz7Q =`c#OW~RO/߷3i:f6g?~gc">9yz&e"P@(E"P.Xr)-Q=@(E"P"0 D$paW qEHPnq _쐈HT 'LS_n_&_ʟWw'y$#rLc{+_@DJr \@pK(0Cbt@ e Q#A귔S=ABGxEQ /9H&f/83z)IMsXCz6u*_6s%lD` ` C?'Cfo# pCp1>/K2w+E"P@(Eh`ɥH)E"P@(EO~x9^YDQxbC%͖z!qfF'CD8%%m%')߀ gZ[-k,FZR]JgGmH>*S'̟NX5#zb3#{޼zrɜ lKn}_/Kn3O\u  ="P@(E"pY4ڣ"P@(E#y0A\' |fc>rd#H\U)ȲX#\O]nK\,ס`+?óm|٩"H8Js9 ܏&vm=Fݩoe^N{Z c}S(!lC]??mH4@]7;_nF1K@@\:2:|ݭo?N {amd L"P@(EB.KjS@(E"PGqC>gr| Щl\L ܲ1 Rb),^@.5L>_.O*6wu%@7c|Sgo`e,hb%~ҐvKC{'.9rQ%OҀ}ctʖ,QB2}Y_+9|ty$ۿ7Nq'?Qz(E"P@(Eh`ɥH)E"P@(C A V舃Wy`4L*Vq` Ȗp6Uo~*yVyb3khV|9 ֙9=z̡$rD$k( A&sNPP>t[O)g(G:IzlWl4TڿW?}`&=N{:(fy<^> }Ȃu"[(E"P@(@K.mY(E"P@ 3pr1qM>?hdtS= X?gmu3׸t?l_4y[Rz*E"P@(Eh`Ʌ5H)E"P@(C!puiƫU=ffj7UbUC2 %.|R1>'_nr-T*! 2. P{# ]V2bM?z?p,_)#?p8w%f㯅>ۿ__ߊu.< +p۞,5o$z_+碏ɗ[(E"P@(T"P@(E"!U1|=ʸI\t$8Op Y6B |Kd ʈ:3}|Zdf/>+cF}xuq&(dګIX&?ubya;'ɷU +g 0)Q-ۿAſuuRn+_x@(E"P@4ߒ^fէ"P@(E")1D""z6߮qPȆ ;W~\Od2^< WZ¼28r`zbU)e HLI}tLuz"%Y%tSp &ddGT@W4ζztH@>G ,M<'C%aAz6>Ʊ<^g C}v*P݊Xw;Ġ?G~N-cRؠHQeKg_ALrɂ,lK=a/O) X#E֯)_Tg]v7d[/6ug+9P:O-yg6_>8Cw+E"P@(Eh`eG)E"P@(E@'`^'r ca@ 󅽟¡@@ԋ1[4^Wϟue<կ!e Ow/u!^o)d\pMH|d-BZ 975,6RS]S+vL'M 7_iS2[x|cL/?3w+E"P@(Eh`E5G)E"P@(G4 'g_/u+!wkOݼA8Isd~dUx!E?*1%`?ETâbVlBeʇ@  s7XcI(GYGʳ$"lT짅7E?sb_8ŞO+9u#3Ϛ3:8ivgǽ0s/|"P@(E"pyӫ"P@(E"1t߰8Iф5y  'TqBn'|#1VaM+=PX$J:(D|?q-QY&|VY˕RIב/'T>eoUՊKW~os1|o;:27ustuD]{iffT?Ê`e_9vͿw+E"P@(Eh`I5*E"P@(D`kB*}K[ ^'lDflOxd,>/΅59Y3AUpkcɃaehB(p`ׯ0 v%=; d"_]W3Q[,$A7%7)eg޷6ζT|s3ۿuuy7Jλ{M"P@(E"p1[(E"P@(_7A:xA  8Ys^9gL{Yf-b+g >9r}:,r<u>B=y .QhP GGk_4p^'KOH яA">.wD~t@zUtRk8k='>?DC?<8:<˗o c?$b {A6}6;uvo'bOfB̮uU+rh-@(E"P@8o.N*T@(E"PGxqHdx?>;6/TzW!cϘHX:l %KFro@:PrVD"L ,\{f?If8s'OFi5yhB}&OHF;4>'Lz]k0dwqGg.A9SJ9z[h䰒_rϫ^r$D?S>5fOOHlo?. L|pIC_<"P@(E"pi4Z"P@(E *~Qhb2' O1Wx73 .illT>Lds9AW,YPz[ 5+Ȟ`!jQ|fgB!dwf?N9 P9ᱭVY햲W4x7] lv~bGCNvKʷLO@JR &Ò($m*'ar 9b?mPa/DVP:SFy_[يʶɧ eL3ŋN8Č3'$&::8vw"P@(EKCwY݊@(E"P@cZuUX-+V8׆&XN JNrp׋^)d0H@VJY $'s 5K~dɃYŃp"_ *Ar&@N$CtWyMiфa:rמ $J3FtPA2 P^w%3̆I I-N3ʟ--Adyh9Oz!\)β$%)͚n?Wx񗉦ϚfϿNOޭ"P@(EC%&ը"P@(E|8$ *HFq2K@$26Gi̞]եUbϘpqo9L H0*#J!{B H1F:NIC:,;ͷ8Ȉܢݲ>-cj Y 9`=oЉueճ|h!ȍj`%g[HHʷ c4VFcXbc'uiIR.BN_"L&*XtM3|zwf Y;#n?_q|n4McYE"P@(E\חP)E"P@(ES/<Q38 (ٰ:|2lN*띷ʆQH3O>f۱b!ڜ=~eێ~Uf?"Z%D:mPz-9,AIFNBّH5WMCDCx[B1yOY1JddG>[#FS Dq .\\B(nj4F#˚#C21z..ڨ^6p aK"+xH8[:iSŊ[~Y\g3͑^?Ks$ͳrMi5:bŃ|Oʐ7 **Ɋ5I;D1^3kLv9du0f}_L/P@(E"P!0(/N*T@(E"P 㭍!>9!?$@ڶUM#t\eaaP;*[IŒ ^$cϪ.zŁ@P۪Y:xNyEư{doG2$ U;TiQ,['N1 &ϳ qj.BdL1A++L-n0Hj?o'3@(E"P@$|nV@(E"PW` 0s̯@u:Ւ%`|s!0[/݂BO> ,6ɘ0s$p8%7JT4mάl~,B8ҡEe1@+\Q:y`F:H묞bpt7݁pC%y$ `CeI"}WZʕ9*^ RǟY2PZD*&)I5=m/Z'\ݳ3㔋峏j@=ca_ǟc9"C#uo??O>/?94U@(E"P%!nE"P@(E|U7:7py! .1F\Ѡ[V{Q$1iC )@IA&a.f:7%/5\s/ƹDO ;z}|3ypm181n '){"='O0 uՓ5d&[ϕF @!Q C7N (z>i˼޶ϵ H lN; tۦ|4Tu\?[ा %n\q*ga#)"+)yj~'"/;nwz0Zh"P@(EA`ގ^>դ"P@(E"SVA:]c+ 4IK_|M*J~s0Pe%:szY% DT$($I(I{'V'9%ĕJxd0y'>cC{11¼ . +[bE$r2!m}W2*! >;fpb>w'䰉j "Sf BY  E7%_InN7| l,e+<|?1 r_ N۱>5;# `27I\_>{yg "P@(E"p[KԬ:"P@(E"Qp$!+~r$:a5) _{mڼz9A Ǭ>$#h$uE&7 q" p| ǴA%(}g HD '*xyN'\e1dm:؈YR"S}'2V׮9q`x.-shȗwNPXgRq^E26NY0⟙"ǣ{ q g,/6?,7z yNig|.3_uU?y%Nx{l"P@(E C% U"P@(E|޼/A v@9 "3-Con^p7  9Ya\GhO ?KO(V(zR 3\'D%'h2[DPBɪ?uOtGs~'>ɓ4&fUtOP#5gЇ0*^f"ʖKo<H}=zS &z{-VCbae曖l\#ͲJ \e4LB~zVO y*uuxο̎x/݊@(E"P@4Xri-R}@(E"P0ڃg8eua9a"^اV"T}h백XP -Kp}$(`2HfzI^F޲w@@m+rcOcbIF۬~x$垀 k=;,ODԦːlk0MʜsT/(:F1&3}tc2?"}n/MV@(E"P!k*T@(E"P>@>WûMq{N8$޷TWyNdxE^O(d~%rp6W I`CDBru d·aw}@K=Ls}O`eULK':PN=iH2 ON2>]\B,[NAݩ|=Nfq^w+@ȕ5|?gt+E"P@(EE(11K .,Y2CO] ?_6[[(E"P@(4O(E"P@(@ 8'cA?~![U&h`/G&Iٳ>s?@J\}KDBp&pd \N>"kife\[ߍW}`3;Z ;|W7^yU,蓀*'ʘUO8<W\!<;tg+Mhk%\g%Dh9` 5 yeW sh/Wlk`Ӥmے╾)/^?V^E3:D's~o(q.;±17&c=Ӝg.m~M"P@(E"pQv[(E"P@(_ϞqBr[tTp>ȦGF?vi&+IlźBb|_+n\q63 jn@  @8\IJ # S9$ij (WZVUp<*܇VXр` Y9%ŁwhF=ԙʐnWlLD)~(GۄA"nRȲo^,Ym噿 VW8rer,]z33-2/P?RA*p(_(*3<;>9uLyzXs Ir;7@(E"P@4^4ժO(E"P@(D`_sNA'?&C!a@C 7sbI=P`@+5+3C7ww{wnw{؏+=9% D,U)7p1 :]L` npɵbr} )*)7hn()C0&vj0$DzQmt\tQٺ~&6 ݑo.T.K= j?aPbN/CHJoVV@(E"P!k*T@(E"P>@` [@Vq\΅tZ S<֑S;+jVt=>Z <<qLMW7~nwKP,ab #jp:Rz PE5=J-H8K=Ů}'+1|_I0rV@ 岈ch%VMyZ5T"`a U{n#= $OAmY"bTȑB\UYm[n,#HyHLrX,1VsF-@IDATu6Y ȏ {I'I MT4h'<'}3B"P@(E"P.\^T"P@(E"IJb~?f\5>y.4Cjps%SlA$~ǠP/%|-y )W0)'s׻]qS`1 PYqt=BIS?~JXN|TU~vl1\({q0H~J+"YxZo}_QOɷ 39~#Z]hsk79G>?-|0q^s ۟6-#_6i·h9Qv!jw_fx?.o=y{$r̳砌TY?_nE"P@(E\ ,>E"P@(E3Wmrh&z$L'Gn9,K^?#luy~ٟnJ2 |=${v훤]iDׇ pNW2>:t|.ÑVAy Pry ֹE+jGzP׻:2}6;?*m0tlǡlm$ rZ>#FW~[[y3OࡊW5!6C#X3?$"e_8"5<˞Ѓ&ĐXR,A OzKT:h?Xr!sCdU0vM'7dQYG |~)/OP;ޔ|k726ةHх)~&/Gx$/{_և&Ь_}{;9N>j>u!R(Оm{sũaR֖ESA-%s0ȵNx:2 2bb6x(X e: xê%?ܿ A!:5s7B/n0orw>p~ ޾۽@؁gUSnnowFơ"i?usƿ[>s&7/,jlT*IY;᳹} e6^trxzD1j#oqit6` ~>dMl>D:pV-uQkqi}i`R>DE9+ l|!oiu:L'c( ARX(6v@>J#~lG_ʟdrw3`(+~.\*>rۉ.:F0H6"qG0S?(44lyVr[g,oڿ#Rc16T iݣUL=fbJЙiɗ49Oٜ~KS?%&5f#C俏z~[cm0^mt:/kn__?o?:ۨ?Z'n|_&#?v$lWȐF9ٿ׳3lUg`<|>Ͽ6 uN̿G+)̿W>}_+i?`GbW"P@(E"P.\fT"P@(E"q}\q[99N9}/g'a Zj^jnt$qLx @p7('qS`)IuzEN)p>{t#N|L= [V}aÜIT2NRe+O+ MQ0OAРpN258&L9FO Z08e @?EBgge\:P|XM&Bkiu3v#mAV'H:IV^&+86ȯub!J8ܛ6Uɳ74e!qr)ڨ@1tjnv _COmSro9 &}~)Ohd6ٿ ^Wʹzb% Ls Fe+7̵hjr`X'fb4Ahdmb6ڙF6cExM`h죕+7Ncql#3:O3ZI)JROzqEf[Vg 1y>=zNihU9kl[],/{@yl~/ .+ DW6ukRʷX9\!ˊȁ4}02lrs U<1ϋm)NeN u>Jvi*P^g^w7C̖^A} ;lmxB\m7$iOJ(IY }l kR…}_O8@;laJO6[ގ9RM?\>o^>deZ_2SS"6L,m$ Y1He{V\ͭ?Xna /z/J6: %IÄ-k@։gԅsA]z<~1 ?Xo<>=j`ڟkKRQ}{JC;yIVOf"P@(E"P.\`T"P@(E")|k6/tZuTJw~Ƴ ^H&:YQqJbVT// ZrwW8Xp34GA0$HoCP{?gv5۷pAȥl,:QW q X'7CG>sc<9:tk!?cy6+׉h'>D}?"kl&H|= d&`\woߢ=(wnoۻw Bi2d*7Yrm[fD.0Hi;q%R'V lj'DQD24\yY*AMP | Iy EblR`98j/3)gtvMb'׎8W:#ib"m$re(.<)Vq\I&Z-`^`+ k03NMLv~\ v8AQxS Ur~qVPqRv2HvXìcP֞F2>agE+%Hc@0ӕBa.H#{(>3k7jx x,}+,$C'h%E'%<h,Gl)PUmgK|~Хar,\m.%#[W{g;[&jg~yF0+pg7!eWV9Cc!~>T !(cWB^Mpm'(C*}^!a|b'@IGW|O yiGĜabM}>PglS&\9lJRȁ.@aAH*d"osjB!rG;v,JՎTD\vſM=uN-R؈F#vh7#h)'m#N:qeSo:ۺX::"@}TЩBNqe`,?am? dhz A12 8WHϴ_C3אr_S=a!iYtB=ky"_H_:j_bs hD *Sg9C: zmI6 ^Y̖3󖴫^kqjLQ2nYAZ7鸒N鄴? jQW72mHZ)'V'b y >$`AQ{ӱLyxf@Ъ;Jo+c\ʏK0` `-_`P]#͹Қpx {Ήݼ-ֳsT }&#v ױ_9!'zLdpSIP'mc?.W3)+cxtXq0K=U#~9j:9CY\j6'v('H$6O\,Sh: _kLI9~ǿ43}־ʲ=\Ei β?ؚ GMc6/G^Z#rWAp2bF  (r}H:K?y+y/$_ ]uZ72I3>9dsl1g?3ea$ryDDOPOz0"h+BUVI1d9!T@lH{b0j[GX2Nd3Ɖ`΄s?'1M ײFs` ; Fړ~ Q!^u'T@!2~ȴWAoV[Gx[[DG5tƿO A^'vvRUއCܭ"P@(E Dv+E"P@(EkB_}̟cN_2WYHdO\3a~㨰v[hNpêKN2bY}}S'w*_yׯv۽z| g9Ub\qg_^ȂO`׻o(?'H6ɯ ЁIn~Ϭ8uBpW_G N ,q$J?UqB 0̧\ ^7R`+:z nVp5KVh+".~bPp18i+|H j':si":Vs̖FB'iB,W `9NFmKП:ȇkʰ_D&iN_GlPCN[-v˘q,KW::B qo)ukӲvoǁK8~سVC\XM!'*pɄ5gKbHƎW(W$Îi?czs+@=?7>Ns6&nEh\.LWFnqr߾]i873gX ~nfXSe&(gUqZO[4Z;˘ȴ@@=^K@MibԍQC> Z:YW4V }Vm۴gg" 9LV#LA:Ka/k.=vNtn;lkbcĀ=7x#֑mjԥ<}ZkcNa^ . 41Ѷo\)3vU4F?;濴?:l+s, H;/$#OsG 2ar]JbfB,V<K\^ۉ^p~?>1)`p و'4j䘯&{_w 'eFEr2g[& ԋ!R4y9Ȉ~>i{|3 8D)t|%"?ʀ?5@Tٶ]Ь2*=6Um/p`4b'U `ῗ:+$ rBӴf[8Z6c(`UԌc740I0<+V.u??%TE0R5ܢjXM M;)X~ȅ|%_nE"P@(E\FE"P@(E| x$(~_b_B_'$e{|ð<.q3Kv!:.Ƿt¾őm`$~nݷ !׷9h &XztW#~)-t'{ump X;T%H1_cqdT,I:Xtq`A#WdR g*: /GرMV3P0| ;G ̕t*=P~w$: yWVΞ wD_4i_wu"G?isyJg8ت6)Y娙gNLW)p,S7'LO֑3ȕr"XO۞gh,)BY/iXtf!mߵ+h 2Q@T"MLrVA ɥEm\FgmÍ'2FVST_ē*~"-vL;#5\l6X_dHmk3ŸY9fK;,5Vb$4~[!}lY-cBw@N/?݈^(ȏ19IYu#hF+N u1%ޮyDm)Iʰ O9RՉh#G馿@T-ǿBD}DhמlL;  d.L(A8DfJ5OݝzS\Q7@^-Hڌ5Ujo.Ϟm_;:''$v T9*?"C~5 ڒv6Jο&kSq1/tWGON{dvд*E+{ʷ cƸr*- "x"'C3`<ˇ%/A_3ΜZď(~lPM(2o+ Gls5eœt1e`"l/_t6'tbs"hIݤRxqW,THbɒfR]@jLJʐ8-AK{.E"P@(EȫҨ"P@(E"I }yOZ?A 58F~r[g^3P//d6<~I:*Y/>|;pܲ2-B"@Wdgkp:PߢV.F\|щ'Q| #ܠW` ["HGW7Dži C.4Y^ BBVq$ӽ+4GoqAk'B~' tAB@ J|`ej+Bu]+oT*0\_l,io8^kƩ OʡN2D` qN XW/uP#?A'AثώͬQg+K~I6B,6m^h 1u)8uR:4D8kc&+C{) Z efͶ,7dh35}\>\)_jMqvY4}Mʵ4%|,TPNEu3S7#Ŵ4#l%:h7SvJ`OZNYZFG*#/u)=ći?G_`)S1E1L/0KG:h`92Dpxrʖ\yNQ=dçrlQ|S?3 +3ި>'O e?ܹs`r@Yv䌝^!K+ 1|PQ'i}aJz]l&ojseȣK4IZm?-Y?5z,E"P@(Eh`eG)E"P@(ER^oN7#l>IaɝX <:YjGY:3u =K:\^`E V#9TpXyO9MM~YM}%vc:`%:N|_v딹ݳQ7=G%ݨk]S{'0}9,녮}[8ݳC&`БR@y0/G&_,5=y߇wl:i` )չ5SXA5FRu1ÎF(G];ɞrM"t6vٍ ƙ#ג@=܎Ȋ+~A)qN?ms?Ksn: bn* E8U-@z{7ʟl;txt"U5N=8t:%*maC|t`6e|Y,^Ǹ1սPMjQO:qK.KW`;?\7K?LB*9,KI;K@}W]ƓgM´8SB)vI39hS1hOWNH\E%Oab_Ij | P{P* ?VkU)LPTV|l IMm01~zPi7ih:A7m1*Pyd$מXȱ_LL1Ď`},+SM 皥𲿺YEBCxUQ)jhx92};s̋#@7~/6t5=y}׻׬`}憠ELD!K[gL0bm"䉡:'`Jv U?[6ᆋdϖhl漐{aC/wG oTٗwnjaA`54cG#3l,N7ɵ9%|eO y]n}]OŚ]C3Dt ͱ*J﹔8m'Y=Ggs߀{>NWV=~3Jʈ ǷoӟHAgL]ꫳ z^uM+kB}Ӵ}x?2QaOirQE+;s9;jR҅?4BA&lv6RIl?BV%LeHYSK݊@(E"P@P\*E"P@(EWy?->϶7&=Ƒ#NWV)_~JѸ8Qts_A(7wu Xq8JpAũ8Rq0iv8u(Yj8QHη%RDwl{YgDH}(Z]Cg޲k=ĊT'nNW8p^-?{c N ݻO\9D(S޿ioTossV|c,ѝ4IDMtA8ac ڲ+u^@q0q浲kLuJ>*?s{Û?ӘO@CmacL^q<+{聎qҟE-%N*[p F:[mW?34N>3J 8ӦoS>4\>/+zN8y GVQYB"ioJ`_C-hMtŧ1yiv3/f?6GH?{S!111i?jBww8o 3e=Ik'qj,sTsu)ɑzOPi%w 'D&p!+`pIxBk;XFؑz?9d@bǮ)XfihB̌)@rXW|8S9NdW1+'KY:b9_Ў2 XbLx6ϥC,k5Nd !PF(=#AubAp AplE<8H6`M 7s@]BnG??vӟBwpP EW_گZ/ZiCOhq(s0M.qL>Gb.t"Vrcr>۶7?l.?|flNt1B,PG 2K| x;U>ERXyCgd|abnEs3̃}?^)XdH!ztWy3'wƔ]6B飴S2gxB PS}MRpZjQ9o{d@}&0)>lRO {Mfͷwo=(c%nn՟MzxfGǕ.S &(vm6fϚ}c?S?ZB%|n2'U$98 ^Ȕǒor/y"P@(E"P.\^T"P@(E"I4n%-A:&D G /ǟfA`: 3c~<sqIrp58q Oǩy`i7p$>t 8yG=p,v6jL|_FљUu(~5޾űuMP KǩyV;{cO|5 T|y )gGA䋗T$? w?$h ߝp;Vz:zn|۲uHI2A Lu'(dZ#z_8 ģ40Π8+,Z,uZ A%v_qA ,)q opOνHlq"hDiv&HWr! N[UV7S߳;)v2_BQ) (LDc2Eiڕ]I(⬛Wri,m5ZFVm (Cur }ow fQqpqݟU^}Olݞ̕qy]0"pC2֠0Ǖ:Ş?p8rﰂ.M)sr:٠ }m!AO8im QsJS4󑫇^mAFj0+.2~K4玀< :uDE/T:{hrMR4#=W?sf> mН`v>T|*sVw ,nwu@{?Xb|8 s'Ǻs???%? 7҇C(ysG= Gxmm3B`5n;hgl(v ~Ͷ۷ovٽI? kqMb$OM{)D]X g5>ypE2"dPvEpv޼% `tp=쑇 QlkVV3AQ7O̷#y|@Wy^#qdۼ+L,Yϧϵϕ:-Y[1OԂ<&YDt\85.-%$smڙ!hYZk9߷9el_4Ls..ON}鶶GSa~;ǘ2ȏ&%&a? CA pږ8!!#ocO6Iӳ)>s|f Tpbzӟ&&{V9P?!?q.n%KA&qa˚_u9ԿV]N=Pܳ$ 9g?9=ؿnkIncyq[][enh̐U78K8՟:`lAD5sLEnQA[)̧ 9,gw%@Nؼkt !MԮe_KNz8FG1v 9U یH"EK4ggTrIUm[;ԳClrf]/ثvXs.a"NEmľ@QτsBiB!U%۽c^?oH"}8\Z0,')[e5I]%<@xIfgzno_{< !Sʱ7{=QBF[F PX7<,,Z4(d.N:^ni˵r 3ڵ=:~ ۚ 5E2m)+2mxfy%)8׷ﺏ?ax9\Umtڟ-HI4U1Z1c=X!QԺRK25GY}hǾ?CnGM!_ʇ-vdOYWqLxeGU5x0r L( {<<:nSI%IKoP丰ǡmBhKS6O< )jmmWlzbXz\Ha=jA-ҵTn3f8>j*SڊsmDIz_o՗\ﺡ-;H:mG@@@@@@@@@רKFњLMMMMMMMMMMb/QS/}uDobA*jӾI}8ƅ(G@IDATNuஇEB"3=5 nۙ=pKʤ=B$B@֐J ^B{5J'< 8 x"j/=W/-ΐ!{4Ŋҿc47en }#e-gI{@h6Pyvl _\0>ív;;_wfQƞ/dH ?MH(i۷ps4!!r$2 1Ru$)i}syx^ bey0P*@449}C ګ!D۵18j'AMV?i_|jP9C6rxnb !vB0FFU9sя<8eO!Mllu>nOu֡<<$ /N.0G4Vݢ K 3'- tF<zS7zT*6閲go c-95HF0wdcr}/!)q@޿};ݏ` KrsozIf٫'(ڰAykA,}[]G=s( {``׿D1%Ϟ=֟s~g~Yųnc}[A@!#EE:oBAaƓ0"[J6ݚ,kgH/Oҏvjhhhhhhhhh4Ј%_ل4qCPϯ5P7Hyo ЂD( ?g\Hf :\h&&pa(n9 I뼙.4dCw3j"Z !&7o} b|%ڍ LP'4Xy '="+V0"7-~h)p0CM Jy"\&[º&%m_7c7(#xd['{ D;.4 ry([3ObW/nTU"th9DdnqI[ _~{]H5cHm+їD#Ќq+ $""'"H c8r"*dT:D,V :~O^w+0G YalSELؿ&Z)a˕Ktf"Wڋ3+H0>͕JCf̃DmsnBah#/x[f5U_V]$1V@B^:*Vcf3i\:=ZY'-ѱ{R_a.h~ e?^j=Kr8{V]Bf''~@3v@{i]#ƒq~'$t$d//_ qҊqLEgx똱b Uƪ.}_o *ծIw<}]CǏ?}wU.9*AAx=;Qm۹t\Swm?Sͷ 'BОCO[*D7 ]G6VO`I;)]O$c ;ig !TG1DPR7gA {rrΧOO!A:< }qSwHX"&bS<+DA}h틿Q$HX+n.Ϝ'I6|!$͢Gה YqA;ׂz@k-)mqʤ LJ0NDr2\WJ0ukΤm۵u[L_. 4b5M4-PXQߟX3oo`у9gR \@p#TEtl!Dg L{-X`@_"C#Am :1cI\*e"W2AQJl….HGL]`}y;35G*#:V^r& 12kwe`Ct\Z=XM*_!Yّהs] &fWcʾ((zl֕Uf>CWIy'MlA")ۄ!G@rZeؐ =BemXbJĹF$6Ano-0CS#I)|%DxZ%m;9?O$+z|۠PoUϺe} M3$!`o?a;̝RF/.~ItFg_:˞Ikqޱ)p?fTzL8kB` Q&kڴeSmB*Ŗ/4#MF&׳P.y0cgy)c/2צ+Ojһ'>tN75x|$*)ڛ F!MV h Ϯ v—ah]n|Wpk$n$J<3e>`9~c|*B"\g&:;RIhcٿ_ڿskOKA=6?h女MXHiiiiiiiiii-*~EYl'\(O|SV"=O^[O"zuf^T47U Vo c." N LOX;|RAT 4:+$ xALXHgNy1Aj!*3 8$@$YZ6-CJzMS4lb;t$@>(CWw xG*3@8N5x~- {.P()*Z6Lя9vƜM:HdM/pS~0Y!or :KF`)z1 7  7?8}> #C]PyEzuEa|}j9b*簡X92/]T|; WX^q..EOdt S$j:BYSl (ri7 sOsEDV3ל'l3GRG**Dq+d y$~Oh`z8;tsr{g YVCF//X=t>ڰk;D( eԶM0G]?Ar`ChINh{y+¹MOK 7KzpiT=k36%"Fb$k@(w4x0`T^Ph?v?KoLb(&O%DHh1P=8Ņxҙ Ls/ Dr7x~c<dn#w9oA$7/B0YEד$6]!S٬sW46R&K;nhOs~D,t^:' >[oط|HH#1˞dXOug/WB!0_X€X^<2yο64DZu糈=bx|^AC?ʦ_TdwiY1>mYҌJl9Fۆȱ 5)-L=b5vzړG<:}޾m߽NGK0TAF6͚ xC 4b7'M?Ԁ8Qߋ !-) 11z/ O)X2~[=I;BME zW0=V@\ d-c<* l>!oԌ- H 4ry\¦ONYzQJFA~E w|E|n8 (:NC 'Hl#/sX2tdpZ]ڶOz97ׄ[A[LM  ai3BNӽ IB0q΃Jq( 0Z`^kTAZJ=e+2?=?7ٔe u#FCh[`1!$5EPGޢGK>*'ݷ hKzZ;YLV"@ڌ@ܓC};5wL僡Gkv4 #gWfۤpm>ڽwv"7!]n}S[e8b 558א-z,ֳ3MQ6 Yڲ Y|8"P3Qϻ'\N{ҝI7hz~q>B.JnHO$ԛ P2t |WI3:ݓ7.ɱFP?eS뛑?-#Wh5pFQM;QJWYy,MҐD^^J d 8H.BlXm]! ) Zb-ׄ9ŋ5$b@ 4[O HWz4[  rƉ>a BcUKLe3A/џ6j]rR
?ՙeeߜ'5}sn6lfWj #< z^H(z%ZRe3%|I8SV;9%4'I%u!C}CI:T67҃x zYH3F|<Ef 5#@ s<\+$1au 8هu|Ĩnkkv'B :xml螯C d=` _kB:dCzrKj{4dDhC E6mNO,6yoAP $m*e / 9T n^@6-7&.:`H2«Nb7o hR o3c(^?' @ǦΜ_} :rLF, !X2A P;I=[^,<^H$d<˘~ucAi͛neq%ff8y_ekaJc@hɱ'{MC$HB\"O2>`k{;!:$n%νV '0)=q!sEgd&=^dUkdh^SJ|cݱsҴ*=9ٓuԋ *f0d=EC"9!"&0 dc<>Ada7k@eT": hYBy7F-\~/E )@*uqN;w+7ð^^}A7)44Ok$,τZZVV7t˟/_t r IcyA? 倝O,!K[=h!ac񜱵 v8`(s9Ã=RH&q^VX/ы_'zdJ9#S64 b5!{,v&X!X3UA7SK7?u?~ { yD$/_^x=y4ăe,.a95<eǑ}qI4|zJ'lT!){%KZٿ͋^v-[]̾,q/Ȑ,/^~W  ӱK%L;qaem͉. :zғEkQ9SoG #kۖMTHBI&V")cyq!+G“c4M *m >ӞҮ;Y!]sBe(cx o/%g>?u䞧 'Y+!={Rϩg ғ~f#T2ڠM5eh:mUsrv{/DO" XG/٭u]Q% $?tJ??ѹk&2zG-Z13mjcZB͙s[|skru/"֮&5OౄMK#7̷f&6;/6w>c?* yO b?#cx ]RJ}@U\ A/I1+cR67 P湶H;Rq ?S׿">4+N"[/" u6p6=fڇ 1_z?3x7{-}; xQ!`/ !K:OvU㈶b꺞 c[w1:aKf}m ؒ ]#FƻH=|ݷn4L#\n~])u:ƣ$)= ;t$c+} EsW{Q]B&ԛ"i I䲃Fy)O%6T|xVZסH ѧr}&|" #Qy9ې:$$OV)9ڢGmW뗯א ߼z=p@?!At\i3 ox0yO>f^߭t:x.6]a$=Y{G³a 1kZqZ Vs(c?KFet՜4-6gܦ^JG9+5h ˕\}v4 4 4 4 4 4 4 4 4 4 |mhĒmF^lG ?@[ LΏ}w~G2ICJ"a:DC)CP!y=aʝRQv(2|{W܃4x쭠(-igbu̟e?z Rx*.*S}$+0|(-zָb!C+(cz$hꖐ7 <QZJc xR IXSa _f@4 &t:Pu`Xtjm#چmT-)ŗAr?=:ssؔ*z^U^<{ybj%% )Bdc|=&*@IӜWn,&wǒŹnv~  ΐ˂x=8 D ?6 0x= sa(6OyS>\[PCz173Ӣ{9\M^&k.F<0szv|qv}6}y׆#(ÑN 5X_Zt h&":?v&#c2zgaWW^,3؟͠O$ih9V*ȕE,^XKm@RJ vDT.΁bd7IH%=B<9w!a "Bav`FfY!d˗/sjd.5 m}`A@7NFSk!Qb T#r\R6>@&1>o..BSl ??d }#akzM'5?ݢov֟jO0g(5!?W*V^::_X pSK*h2PI^/'e*2.0dA@ɯ=/3WM]0Κ^W <7$7} !C5Jc8t85d2a u \s_ڟsPk|SѴ<7B So4@浺5Sw~ pUڄ$O9G>ݷB6/_yOH({Vm<`/Fӄ/46PGsc.Sf gxт4W-6%my0Lns^Ru**MȌPV?ZU-ZyU}7 4 4 4 4 4 4 4 4 4 |MhĒi6,MMMMMMMMMw/U# Ur4AA ڪG}MWtNKGcaA13`DR0_|wP!-uބƝ${C, DJ.ūlA \Td*UV!4Mg*/ߌQ'Y/V<#L&-dn Pq=@̸wՑVΨKWYuw: 21e\HYȹ4cK].= Au,R"Hu؞zȧo=6hfS)#xs"Ӽ>343z?ŕ$t.|ޔ>O (zipD,_nXwvj=y=][ul^_twuj-?=5 m@#re%*WDŽ.sgztԿEҎkJdp\__8# Ao,W1,2^^3޵ӆ8?;@8g|] H/)`VO-Q2ioj)I!{%6Њcڴ_=veZ.q<` ̩QS@x>22K[H7% ^H|sC{H:ءR)*~%#xl7# d$ Z iD66z&!46勗 H,XyYGE2Ĵr*u!A6WNt[d/s_B=qzvZ9Kgã-v^ 軀֐\:r+":Y95(=TOCb|s @!l^۷o ٻ 473_~Q.:+BrV|Glڋ-ڣ4RSFYǹo׸D"M&ɇA3EF7K!?K|o<ӫZṄ%Qξ;;6 )qms"~IF3^-/Z4T k!;177:L;BiUFM^P. |]5iUM P}~)@@? |n ikտot/$ࡓS&FDEofir 9:`,}C\$lō5A4P:dDt@0`RAu~"PrXȮlr#bݓHUQS^irsC>$(+䠏;?U}HR ۲}5LdH”Io#N+GSi&7gH|Jy@wٌǒ3B[ k=: tJ Jx9@cnv;L &dg]B4}!p'KrPIwkGBP<`^ 4ž'!8[U_'9:d5B-Jf|kQ>ˮGIխz GߘmؕXdRϦLf^۵GG{Sj| |4444444444iK)i5 4 4 4 4 4 4 4 4 gG~P(zoUP> AE8`iEHp j~=-nAe  tݷ@E!ܦɂ:g(sxw k é ?hfx7E^{8&ESDlx]WY%^4cgg:z5)&6YFp7 )'zA' .U]m9f&V6f/ACP2 nl+M٦|9 Y#Z6|8cS n8*²E %oMwS-^ƜJؕ2 K JXLRƧ,禈X=fO<nϱ 9Z.SvA$%S$)7O]iKz)YmAp_ٻ77-%ې sv&kuwu?|}Sac5t#F7d&Ť#cHl8!",)ob*}K2b~ ؋ $?qOS ,!Hbzc3!e^@yJbE AAP%"9 xI8Fߦ8rӎ8QfB^oK5 槻EB*H0Q[02' GQWu)'%b!tDA$@c@qqI KC\7*lS/)e#b fHW/,K+kJ GE?  >B&l<+C}lsi9zI Ms.Ֆmq?9>=SAROz;`?\?ywKJN>fVN1v- GbAg3EEBllw}<.;XrNȔC@?Ch4D2M;op`>ա bYCUA5CuҪY7"=kMfPz1||$:@"o XKdEqJ N p~~qP1zGP6-pY[+@@^nn4.o D=FH49Cѩb_>ރi&W+bz\XxҢ DЎȼGqB]ڋc8suyzxH8F)!UB?}r‹ $}©,txƃB 1KND+BLC™tu(L+!@q.@7ёVqWvjZzp$7 S^-5ui#mFO:˻(s=y@o!"Z^P;POwr>˔( 6CD'`CA>3yx^4H$ӧ|%LzpN\"| QSЊ9=4O 5ΠsxTO1Z?̑HgX$gg 9s˞=ɚ_#̈ yזדrލ| 5af/LwcO El|o)'ǯ.uq^l5!qKsGN a<.L-h<ƮmmHK2)~Y@扳vA<+Lg YQ#DFH%!8$vy>~%[ݮ gB:ܽ|IB4}COWjGS''MJ0d})_[wHSae3> "D ƼfCGIg6ƘI̝v"9;FTE1wgGY+scxV nRvIC&uIyov4 4 4 4 4 4 4 4 4 4 |}hĒoNDMMMMMMMMM/oX(Fg򃿿S ~n{ !B@mPI)h *pL@pG0Exö($OP&rU_$|auj^ao(l(I z @@FQ4qda|%uԏ'4@~VU=`oA?T][0Q.EHte?uRl ?S_*^v`'^|2L%ɡm3֣L>%cy !S: $V"MVHtv55?dQwK$]b7Wc% Ē?hk-.nIm.>eoe"x٘$8/Vcա󝃆]O5+kݛWWBrh_kyȈ}gm0kOc _'k$Zg 7g. Ĕ"1#B`ң{{gGshy.SuqCCs_%ߺؿ˒߹H*#=9+S[z X͕r^YGYF.J2r{ڰ:}CSUxXX<S2$fMfX_x: h08 ~Wnei%ױF&urqDzז=Α1dܹ zpqRCo' v3ي(F{Ga|ڊ*Jsa[st,{ҙ{u)lQJvƒv/ɦ4}~(G}6ܖ ]V/x;> 4b7'M?ր?sC:/o\} _@*XV>v7vt̠#@߄{`$} Pɺ؂{XTKIp![ܔԙ1AZ{qz _BH+N_ @i' \ $=&F:=" ҿXV(̸hHPY[$(.^P`lQնmTE.h>W+EvIr6XTQ8o[#%2ѓR\007(QҀnW۪<xKi2F OSOyUS,8+x:1>9+`l,/j{.@P$ۗe-rS@̙p!Lyݓ!υ=>rj?ӹ?MMMMMMMMMM_䫛&P@@@@@@@@k ?[ !Fp$ÿyK4HAQR&P k?eM>Af[t Bo11UH7Y@.@KE Af=ӫ`g@IKtEBP-_HՙI~mAKGzS@U QC,ėc= :"I  j芆RhO'?aK5-˘!X(-{3L9ڞS Ea:ݔE@= 'm=9xfп0LS#B#7A"=;ԗ6-zJa)ePsIDwC!\Qo>76d 0U~W睟3BG pۓ;a D&Kgc]3.Q6Ee4YZ;C^tc9[< `-8M$_}[ 6*E\<ڛGH%u<Eiz[e$I[>EY9FĔӮ˛3Py uE-8g =},A2 6vrB@1tTQԈ#H@b)dU)qr2>$!&P%zn</.ضƴV鵉3%AK[qL sJ긾T%8dA{HfBw)Y;Gߎ$1eq?cKZW"e^m2ʔQpo$5Yn\h&*$]aJBAԹ3 }*9O{Dac˄$ĵ(;ie* F&g A*\ϴym?ю{e19ץh#Oi)EYuQbU2Hy"ߥKN}& ~HSxq"4ՋWy=pa%`lz'S(J5SI硢y/NJ95.d. vR6a /'а{R=ҤUsO=7$DvՓGiE>ʖͭ3ȐN RO){Z=ٱ6)gmAU}7 4 4 4 4 4 4 4 4 4 |mhĒmFi `:$/Dfx9aR uǷOz'DXg}J 岇"S6ɥ]tG, %b0.!g>Um,g֎uoy)k\صg׿X6(KJ1~.fв\pd<=A:q $?=W!F} M gp)EoAG=J'Xf}:ԩv%2zOR5B pqkYIfftI(ߞu7_HʁRJD[aԵDW:^.1{QʛF=[gܒI{ٙ ^gpݪ_AMd[U[:nCzȗZ\;Gс #[@wcܤpo4!:^ H<99!ɝ<ϊ)rKn MzZī,^u$h7E|pWOc|f!Iwq$I $AQ{gf" Z<,]ӽ;9Ȍf#ICVP8<[f&%<b>cN%[Zf eO[J63?;>_0lMK邊4!޲sťEYVV޹c7CY>\ }Z#K̳/|pbĘ+̍ G (9LdP:g ۨa!eԙVyK拷褹J=掀d S (nJ/آ2?b8K[4mmX=lmnw\/e|a4иFK-Vózfe4֪V;2U`=my:m?eys,\ds N?zq:wAƟ?Ot:@G#fbfyn|C{?z S@+L5QKr$p+}^-,3YbYcE\BɄfD=6[>p,"SNktsU X,AaG%T &2u~v1G3\rs;%y3hBbb X?H8\3?gvK *sm9,?; @/9@ y>.Nr~-u' 5do^6Skdt$ 'OXfrq:'ۓYc]/AT%r hY2'@-s-4!,>U= $E4 9UKBTU]vIQ&ihBF -5 ?0U,>cSуH懲NҬȈL;6qԕ#D㱹bUdy*N9ԑENY{_Yڅ.% NoI4_mM{ML =q%.+Jr{ 0s<$搯 n$@$hS`s_?hڂWH! np|2|p3K$%WV ,<}|\o0XBg϶ ,[7g>:>drd-A&J_˱.k!0 EH,cZܓ 0cOˏXa|V7+ؒ:$H!~ߗ:ͮ +8Xɮv".௝oԌ/1FYc;;dנuuo`;:<ܶD f2L׶k$ ϶&#tz#B-.>N_LuV4튼w9|@ kal'bVQl%޽޽;'`9 0gR,vUJZ:2KH:Ƶ?[Wf:G6}p}{mU#8~ŷQ,`$GWJN?ڦoG6Ev= F93_9߬Fud|ɳ5g$J^ڤm~48,/Dcp1q_4,?utupQ:UeЂҴJk;{R|nǬ(9ʮ6|`83-]]24[[[=662mΥקJ M8^9c/&6 Nhd yD? >JԾtokVF~t:@G#t:O H#t:@G#Hɏwi|3Ro!]OxP *FTF|jN:SYHjAxGWJ&rT=ll.asEx$ @Ui)2 KrkT0 Hn=&1c4BbStw^S02Q%h"N'm<+A(\'X ϙAimhvV%V(1U?vy6 鞛xo?*Ff0ld5 mFj )m$ ^ŷ4: ӆO,[oC#3Xt$c{ؓA F1[8K@}H| -x|g7$K%{R'('pI_eye j}[qx|4Cӑagx9]y>(!U ~HՕ K>$x`ૃqj,-1e5gb$El[B8s!D90o:eew??e5 dtL3M;A0X/b$quu=\^^&S[,K?< ȥO fRQ38})L {)nkΘql)֍fY>LËGoHu3!JyIZm/8/5~bɲJncEdr0xey Q2&LlްrɈL+<촴2ɺP6K=z,DfalqW@f4a-dRwґ5G$K(8Ao=grR4_\(DcB6ZNm_\[ڬGγs`pr ^yV]q:iL␜ b63Oب=b̻ewEr g xrXOzRmV8U>Uf* s+l 맂*֜(GNdƜ㣣lvva,ld˦g{;ܔaD#h4yFSvMJmYQ6ْTGύТєz)`G4<N׎~t:@G#t:OX䦤t:@G#uBj@R>DB@0P7a.l_D tyٽ(ֿ{gCZrjeILRʉ6Gx?FWEI%2 "@Ral,* AĕUi,w=T^[vN"6Yb|;sHC!ҧ)@) VEx5뭘G:Wh)4ǛWAQW_rc5R#a(W$iGd`,-A KfX`^XX =.d+d{!v9aL ʑ\ULmd' b^҃A27אA,̷‚&bLA1Lf0$@A7ȓܴpztxd62(6ʸNCJrxD6~A^![gË/q 3wܰ¢ yN;d7" 0[X<}KF)Hf/N/ ,:\ œi5HzVEE$`Sߣ^̕ H,$P]>5J`"2%@28$$J!5u% @cAVM\x?dq}-dn9?#Nنs,LE^"R1m Ԁ }ɺ<01ϖXU0x0!"d8loog 29d2;qݯ3/emiF:؉ֽSly|leq!P0K4eETIT8?u[Hx@__ }l&eb4}~KE!ijLoqL q9en+bmmϸ `gdk|hW +P4M٬o; I;MtqQQk L@IDAT`#BBce|> plfːtshcČB r:hwsZ1+t=9g0/A?דY6[إKYD#z:feYL_8f>oZF 2gy~$d1T,ok8n!me޽{7k:*=:p$4Cxf0P GP s~7@e >.͝r$c!8*'g'd8lUsLN1&˗/ݗ/ 7M29 d^L@- `px l{|aچ% @팘ωAlj09r/Lqg#qƹ^OθHt1i Y=M}q dtW~iq-tDpjHEL$ FJv!wٮE>v*&q/ɪO2?\շb#R?:5@G#t:F OQD"KT#"ABH{=@rvòri m6r(ZQ-Bٜ[NX1GLI]12)E()J>giϴg.m(^sB. yҠ*8LVh)?̘ry_-'{S x=~;ZJܩtOS%q(I=CRn6Q[HNmE oNsB‘"G,F o0)/O M~씻|@GtE=D쬙S& HªAuLx᛿~3|;HS7ٽ7!7*[)N|DY͸6{my^0 ~Ÿm`g%Vr<{Gpl%|q!o^d#,THK6CIcͰ][ ${\]_~8 3ʇ,ȶ9NqKYO)$^Pׄ{T1q \JXAWR$ڇÍbWm]a !@9L6@;tdO$aVM#t~9&OFCCL2(ڜ(&mͰO A8%s{x-dރJ&lYJlZ zlڎb R.vf[6O!ktK.خɬGπއ:F|;l̴Ido*2կX58ܱuPUmҜ"93-8e;$ۭ.ÎY=]T3O/*! \זw>筷\GNh>y|5 w;3(>ACG ]^yw[~1~\&S%`(_eV`SEF\;V#kᎳS#JK:ޑ1>e+RU 2go[|^]*r&;c4Y֏S~~QcGG#t:@G#%OnJA@G#t:_G@z 0S}pqC] i[CfTk'7%]lR#׼?2U@u AGBt@RYRLJmdUmwEq%IKy"_*"Va޷,M-=/͒*+3t`6iÔ@Lu2,E:M#WS#E?(A(GG]{ޱd*$vVǨŻO!y!Yubs(~&  SO#rĀI*V^H XOdPL_9aqD.*t},5DR#|#c>zL!676ٶcBmeYmR$ssW|3+x,v|]4V*n/rptH-W ̒9b5[zjxnyRkOF\ђnV&+~_,l`$x,Ifx5[( 5DZ4dk22bVCn5fB0I2t r@]y>8'%5͘ p2h\U@d CgIcfe} s58;uϟv5A0[\^^d{{ߒN͠>v 4!$)fad{!k2peLRlOH {۷^4|zpmTp Σ`ZY^Mݗ 0S1ok?wD#gl 9n6:ϟ.70,Uj? jo61L̵8R( r]~6q|m\G% y7}_@9M +Zbv Js1y&/AR__e'+J@7F p[ڎ_\<KxFYF_cD2JY^Qq8sb e jm0^9@F, ph戡{? (Щfe?/9E]|zj*`_w+~vŠ@G#t:SC<t:@G#t~>>'ɊZT2B"J5޵TjtΑCB$Dd6!2Mv~ȢA, UY&*"dNյv H[iHJʅ:H"PN7^Sǩ}Y?Dr;Dw8ަ P,bh[Eg kg/YvDs'G2ʰGRjΏ}f1L{r˕{m?(Mv )Z\-PEPZVkfk<` !$I$f# 3-c\!Lv:M b9M@݌zm\FB泍̻o|CK>~ Ė/URm}#d {6I.%p2'" ~Dq|5$С[41Z9='[Tn}2ŀuy-I*ʸY=W?@ y 3ˎ7WTbϞ6]ߐ@d-? E8mFS1`DGi rk"U<*n۪RTuϳg`drF3%B(?VȂVM&2- bb^:sAa.5Gq&6g9GS|/ pN&Ͽ|3˿9v+y$L4޼v3o懺|f}4D;QNjGM AvkqF_㹣29dp ,b>/v "1А{/ fW<n;ߡ&i,%۬-`2ɀ(&v%l 3Ѳrْ- q]cyM'; >V{\1TPu:͘\p)mcEd(>Os+ϣ*' :lFb}9eY#s;m5A M N>WกyR*/0&qPd;,7η6%,A΃q8-10q['#E-Պj#tPRgإ,u}Va\}ԵU> *YbVWa?y mr^n{P`>6 W):}<~a)J_:MئdzpWqԐ뺿w:@G#t:O Տ@G#t:@G Za܀x#eE]H{O,aQ- 2RpȄxh8CQH:x${FZΦW+#HRc Up5M(KT˽@ޅCRrV56ţ^om~!(+"BF[ȶ&>82Dz";[8FK9$"eXcQ/Y@yQyqF"Eؚ > d\[!hciyqX(hL` Y BH$ÀGsPcr$uN 489W3!kˑl'?2N֒M$!NMf;[;\ľľq"&QKf0;&HJL ƎyBgz3HYC V8dTVŋl#;2:)a뙐 D==!We}1?KnsEfciv8l5q9@.Az?blEW ͏6q_wĄd)KggT ōAfJ $낌hM |xIg_,`3Gpxag8vڐFvCu%*?y(lo^6? />'t6c@Jpaf[H3/-|b&YVdpb=HUНW&l6<EdIlA53G6LJnt|Ʋ>lҳ:%딯'$Z]3FY̲d-`~[2n\y 1@C pܟ.oNESQA?3#l)T#_{Bԩ?gߦ3RfrAĤ`۫<# Ixa?ObX IYaVRn’U65~ύ*+)i3'^펪hJ_~eY_Я ?ɧGG#t:@G# Mt:@G#t~S󇨝o 7XX}r)Cd{9Pq#|%N)":acl U܊e6+_`tF$XVJo$VJuE(t\u{-1q<~E駯R!gh\Xʛ$yZn -dL'fdr%sC:QmUtx]gmՌ<ٵF.GeԢ-ګUsQt8:ogb(2$q% "0 oA}<8>|Os]$CȢFR[r5yAGƎlhsP宙q^ v%EF{|WS٫4HB~&k`?:yIO <`bnǬחYc'\ 8_^#9N$VU%Ap_D@7W[[Ͱ.J#]%l>!@#XspoMb>z$Òv;d0ߵ3)貶BF,`Y>p 1dA$ LTOi˼'|v9l5Y\. 0:4;q=dnQLsdlLjS<\0ڦi㠧__%k|?_Qa}P_cSb?J'^Tbw.iZ؏@G#t:@G!Kڌt{:@G#t:@YE'!!~.x`K>V j c7KBHxǹFܤ"KxQT)idu-m貕F~AIžJUT񲝕(i(lB=)d&eCl~Ch)tgMfmFhGSb夿U/ۀeZTj2{y-+ɱ8~‘zҔ }LAv̱%ˢ8iϭcO=K߀g/&b<5}~Ͼ_-tql¶36D*CbnJ!OJg8|!fQ1ǃcH÷l{ Jۿ~3r2w@zz}݈d2|Iqv0|zd{l"T(b Ij!* CEqd4 )M A0ȐttLsN$?ˀ{e^ni"Ihp+c28拋d+913!ۜ+=\4Ea[o#t`픥EWW*>K&Irw~͑BҞ.~&9A3GGGdd9Hf ˵m Ōf*7`fww-&Sɰo_+[ %o>J|>|×_~1͑[ǘ -7?mefH?\HrLʸ(빗Q`xѓ$ 蛟q:^?GmE [&K Zf=* {d}ATA(f9aܲ a8,NgKf|9/,;}sɹ(?}6UEPs4kH;vNlKǶIAspW*cJb`6\b A<l C %fGAiSxKWWnu8]o3polcdf oqXӂt,[f[\׈>rv:F>EiGEWD ~t:@G#t=MH7#t:@G#4`6<'0 H*A6n8{oӱ6nv IҢrB`HvERD %ےK}k2%䕜buppSWEdecjA" !qh5rGH|.8Wm|R?6MkBr3,E+Wy HTnkW; 啌mƶE̅X-J5rKQM!-ƥWYTbGV. Ek.r|1> |o ~$  KF$G%G'w|1YDJ7=% -R%1E"o_%, 򱿿O߿?UQ16>{-Gö#Gqe[>Qor揙sodV^
l/_2yW Xy28MmWT['ෂ Qf@JFۗVTfy[}O'&.dY5;(~Z}v&~ TYڰK֔W@LYz fa&mgdq>$mpʳ~Ďh)1jq.4V<{DQ,lc+2O&j~!e}u:@G#t:OX䦤t:@G#uFEE8A?L׮#?e 0hs aIk)H61ԉ15&83"Z5]!rkт㷤R$TuS(SnbG?ֳr"ZRѝ1׽ uhxGז~bG+i:mԷ&ښ{46EAE""K`%ٗW o1XC-ȕ~=t [ÛW,rxq @^I<3lpõd<&';ERu_Y#Η,1|^yn}zNZ ɻJl1@B]/Y5Ln[RsϷdjG 5dnm%28.[ޅ`6;Ą~VnUsA;ri0:f*ml@Hn[+ 0hr A_g*sޭV&j :-@ $ַd+1@6hrHP[eŝ\5?(-d&*} ٍ+*/u }G>U=I^y=ͼlav=q]8-_A; 008}~I̍>pJ5'u#~IyA;29AOIa-̑Umj&0)vȮs@@[|;3]fߩm6L,>GA+,3_d*lx,p;Ab-@d'3|2 nr<S{᳓'8Q(hdY@Sgn GLZll[t LZy}˳Ѡ9,s@3:.sIU2R C,ֿ 3>,Mgs'χϿ 嬁b n9V% QkA e?KD|Pf_.?qsM|ԙA> Lr}3g+TY\92H>afyomm}3WF3e3eZU% $X,os!] >?[ޏ@G#t:@G!Kܔt:@G#t:@Aa>7 &7,)1󒰖G z7Gc5 IacTv5cGy(-cb^lՖqZn={EYpQ25N"S-(sF_k$^:oi4,ln,f$%ȸrR"x2CH.p~v =3~_'mlPȃ7.nևH4Ĭ)HAf8?a1@Lf8d8#`CLf%yL%|˯>yJVDҒYW y'yVgI2/RHV v#A3MJ92m8&|Lk||#k.:Ny.qk'ӇduL> kc}o_fy,/mQ6ݕ\3dg56&cw]6Ar8-y*H2w'Ϻ67m!2|kH;ڨ߶YSvV,zŜ!Wyv&dvVgλk<# n?3ՠd5܀"<^80cuVGZ^m%V\k ?r6&<$^9f**3󧂆7]'2h!YSF[c[wzfW^3mb?_ ALߩ?@G#t:@G!Kܔt:@G#t:Dj'b@H&Gp@.{ޥ%]K&g&ݭHֶ\[)'vo OFk p:6^#k;Qhy0I5FfH<%㏈?}i^ԁȐ0ms5A^VU?J fDHj䈉n9apBʭ)m:)ʸ%)">Y6!32(/sh_u(ZY-"nGLJqH&,B`Bd D| M0zхr"(RѠ}t0fƠa?no A\@軅9$QnoݒD=nÖlAmn-$HgVk[)#E BCjMʂI9bx̣-u#@ &i>7# @&3|hsj_G7WL{mVCnt9v"\ŗɨJb! Mo؆髯?fnƫkloM}@~'h"5z]p+Zɼ[7:J a%B1*IӉJ;hO 2hx/'˼_:B6^o$t?otvzR_0\ng وZ>&@`;2E $;6lѳC66i HQL]͉i9wER|Q}9n\4mzeYg#tsӀ a2(Y2ЕP`;g{>] :|6Xى ?O.I]fH#l7\[!NdY" ٢*#m۞?}ε3qV턅{Oڅ%\6xmva[ۚ3I# Hʭs$+Eeqb>j)I>K9ljbO}+;\'@V| 6t=}FGՏF[~?:@G#t:@,yzs-t:@G#*AErFH#Y4BSP*B} Q?b0)GQ-S҄!FXӂX鋺Pu2+'cIlj79a$I1\d곥dXZcl3Eo"myI랆IEnQP"tȑ?Wg_,TRUVDZ_Y+kcæJ10LL`@|@ZLDCHɪP?QY-+Nd+c\Pi8%B3geُtE#@H'X;&oL#AhJ ݂ )nq{s1 >Y`[eI֖,&nsZv;c29AWfpۜ#Lj vX#acfm$`>.^7o Y`58>,ΣpobB@G m`pCЮ4YwG,A#̓0ZLP qb'2;Yv&JObcXfvR[7$;5>7%ꙭ#20Flnm xNgMt9n0ՠOowq]S.J'S'[ESŲd,x^tӬw KEU'UIQN-=p$=?E&GTq0S~ ڑ/+-#Q?@G#t:B`D+UuڒF ˆ|X%Kj?1mP4 կ f/8H{eEGk*sh]\ђΙ] 8"s:::봭8;PWk.b] ȬY͎C$G 񒗭%Sz2䎬M *q}~"M0D)L!o dqul$[slAܻ+KTeMb٫O"P`CH~@!q̯W 1K7#`&@cp܎nKsOυTdt9bYWk MƑjb%cmɠl$bc͍l!c(v8/w?&<*p.Fp>`h`Tq)ppXHr8(f3[Wt`P"3viI\`6 U;/"2ov<iC[3? Y0 ` xXe;O@IDATͭU̬; C>|<_MOv0>x,uGǸ5J Q2^1 q XwkF0.qeNLA)f+ֿkB%D90!JNY\(dKpzUYHJrMc\rxt0ttPf32Tc+h:9a VqK9d+3d "y,Ff=9<#0ٰF,mC|l*ƶK.[On_޲ Qb~ciW$-~6YG ap|,oNA#^fr b׽?αMaz6MdZߤl5 }Eg ͱr8Ҟ֙tL_/L@6sL x4X(Q?Վ{m}YsTĮLjpN[)(ʳlJ5 M+.mZ[~f`m??O[m0:@G#t:@G!Kܔt:@G#t:/b$wA^Tb+e-<,bӟ&!|df Y#Q"! }oZso_^ҍ'!X}Nѣb/W'5yvR:Z;MHlo[X!Jhw}"$[e~\mĨȭ 0SKGVv6KklQ`yIb[j$P&]Dw`mV֫ nS꜊T:e #i1J=L\rն +Cbd8HDJƯ%C"߈#I>t%Dmڂ ~66]na!1y1`cVPŷȱ# xdv=ľUHD%Ⱦl\܆" SP9iJoM 3, $jMha@0V2m> G2Y3 kTbƋu?b3[!gʢ$}D̤c0zLd:_$d ܋]!blN`o<+]yrP^rHUXC7wK-|6՟_|9Ts{;ڲjke?:@G#t:@,yzs-t:@G#E HтOG V)"u Xsm^X9ո֥J!Ip I*{MEQj+c}kn.C5ƌ9Mem\ŵq~Emnfgyx?],N#d\td97Gh3uU)4kOu*ĖF?=klYP08ɖ*C0!we׀t넗dY]][n0 ..@ZpA|~ ?*f\@Jߐ)C2oߠ˭_eĂԗE"./)Bz1c [:߾_Y-rIpܨ%5 0d&l' 61w)跭Oc# [l<3^GrEpÏá9n@Kn$-S\P=g Tc ,oAj/RS,亽י~ WyH)>8VTM 5^Xh; Vs(ʷ2_XO.>M@ӯ(gWƿM_Я yaq?οXGt:@G#t$@G#t:0;|}oտb.j" Ϝ1z'Ǵp#h |3Q0=DܟY.h8:&ہBTryqi|k `x *y(jMQG#$%ʟlMڱ"I8[\b@ecTΎ .9e @N8OU:'T[Ba?=%,gĬ/c&q qn` RLv қ7oϾ"˔_] (Q  $K tȶ,}@Jp#IE|PY#/>>karv:hmF~2b60v! kKxӞ@/U++GRf'ț>;1ɴădl0؋ kg2r<7ol+?/f4054<\g7d1lkt|q9PY&' &dq-9c ,0[CX(ro=?aM#I,kG [`M^I_MHLYK;bA;;Pa-+[pw[Ԯ#㪫e\gRz|t.T뺘Tc;gt'ڈ ώ~#6*|v@zsgVIoȤ##LE#a-unsOcñtҋ#k-N}z8orSwJAˡiKٟS?K3zpw;vI&u~3'2(XuΫ*$W#QၾTx++5 WFu sOV{,F a:bIMiZLоUr_oMh4 @C!h|W4ǒj82 @C!h4 JSP7Z 4\ 5q¶LMڒ,J!;%[).O2hlOYL%%´{- n!n!. Z(7h8"@үP@`.M,_w 9beI>Zt%̩7lS/I=HPiw蟽l;eh;,Oؖ&$Z-gj$CċDlx6.Ϻ]h[h$ڰo>c![HbF.D($ 7&B_ ߆d Q͕Y16f 0:<Q5Z(?EؓۗX6qrrFd/DYp DP6[5?y7h!3~Z353&> G~rlY!XV+Iy8?D\0!d}^Ѓ$ Iċ{ڂ#gֵh:eذ"%]l4/_=Moq>i$ZQ=pBpgz:Lsf06Jnrlt{~/b0o#s]G,YP2^Fy1%]p9<:m?sˋ3dM/):Gq`HX,rbѡEkAtMsݬؠ:N89RSr:8Q7ש]"5.(IT<7r%Юu<sh+;me9UU 4<"(y:8_ea{F`q 3keg#Ė#hc9ԋ6(@^\(:W5yFY9ߪ/ڷ=Xᵔv1*_:MzgV=j7ځW}R(blyqNJR/ m&$ϖz륇}<|N#oE۩!h4 @C!Nh%@45 @C!h4m$5m>_@&h}ͯ|@RBfJHluMsߎ 12M.2:(Uc]s1:@  y(lSs>Wȿ-!Tw`>:xOo[ HYc zD+XtW,Ad! p{eٟ'Bj '̭ě5+oISsƑ.+B/ xy5T2^f-YsN=: H ImypNt]3E4/XQVW i8q RB!5cSorͷėsl=o?g" Cj`{0ęcEh]$o{XN8+(뒟1!9ՆD$X䇎cbg7ICˠ9@ ݐX[L%Cuh.vO_?pu8]͛w[xMw8&X#r99*ʤ#_2QeFhom\^|Ŗop"m%NκKK>>˯fn=T >$#nI9Y/6TNѦGQ>bw8u6זu&4s̀qqpGs06ўk[Qm/u/ZDТwVrFFVHtn~x2f?t<'a_~~嗌O8-FѲA7DH|T&:;0׿`h!5M^ D@$c( s>6: uLdT L' ?5 cTw(9!i/*NmOhcccf"<{vQkU3clYtpfb/٦5i@+OL`U: O+X)(ոGN%*.\1.!NA>鬰/Lj rP% bjA3ИK?We g"vs$@ L!^"Β'qB&qMXK{VكF=KiBA>P&zFa>yAB^!|q}[\u7PR !n*6ɦ!8H#(L!$sЈ2+o~wIѿN-2:&PRn]& B0~dB}rX9P \DR =BX!-X?# zOﲭXQXmit $g  t8~{%l9rrvݲ>CڿArp|'"s58 <8Hr0w׈Èpޑ/kV#?:}񣮉@AB]rٱ~LY Pd}"iorK>-,Gȣm}iN;?Amprɗݗ^'>3yQxaI(QBd@Ʊ[cv4emP:A z*O8؎?s>:tg4, "[>=tA 3͐}Lt. 1L`|Q8\9k Z=Ϻ #-W;&vM}6Nop*yYw9dEqUnH@?Njl3aٺ>ڮ!щh}nx=,eJ eLR$4yNԡqr 8 TƎ2ggj1/؆$>|K[[#U5caokR2#02'_=aYE*OED|Ҫה`ş+c'1 s-F,TJ/>ǬvI6G:=>0;5hY#(O:iZsH 5MZsΗ/ϰOry%Vu߬/m#HyѦs1`y8;O2JkKoڧh3P.Եae\Z7ۿ?QNeSSnp.h4 @C!h|O4ǒi4. @C!h4 P>_>,_}П _B>__S CxS/SV~~ӟYA:d,L+Dߤ}rAZKfnZ@Y࡮%JF4SWg iUHCmq)<_df=euK@%,$K Dh@2TP2Y|W*;Vjb*PtpwO)d̺%[׬/q+BѧӶoO2Km2;(ɇ_Q'bIu~*`JLÌ!ťۢ3/m$cLS2!7C(_?jnog;t%P~;'sHvH}X뵎!90bg;)RNZ.N"@zH~ YNRVAbh /6D^ha.D㹽]uw[^B*}2_N$[<λ'zx,~XAT¡!^'879/t0:NpnފÊQ\-XQc"qΧ8Jmtˋ;;;e+ y/ ڤ.:]ý&-DpPJD~TVJ Ʃ5}䃵{mUt͉ǩ%2y_>c8flBNDyd5|8:6ROyơcnnoK\ 'ҝFCwp6G>C$;;l!d0v=zmfl%rw?QFW}5C#N8"p$[l֦bӷE Ƥ_~G[5"C :э6IUTƖ=ӇhN%q0TEzHJ*rGG R`]w˪VǍOi>/*bߑ9~θU;:g\X>q2hX ?]1g9HCOOeЎ@C!h4 @C{C9|o#i4 @C!hS=?_-}o|fC2rC$ <*B{:@sV,AX.`^!Pޡ ooN&aLbxi!v%7\/fFRH7ZcTf/ԀAG8e KRnO] qI')N$ B<_cH+!!!'>&2 ~;HPfw7k|fl0/CFR MUvF%hSL8Qg!) m78>yMp uQHD5;zB̪9TGH8Fk؍2AvAIHUd doT(CeCvIzLP+JѭKf~ ϛJΘS!Ϲt rJ`v\~iv$#m{烶ea@э;} zŨ!77-v89 V6"8-|9ݻ#)$a"HB& s8]yBڿ\uM|wa988 QJآ~;@n31p8pFf8\^t'')&:Iayc;81ݖ{7M<#k E vdhobe?5y-,mS?WT4:VQK$;F:G)\CVa꫎a^|E~xݲ\_~{ crvS"pBV9_ fch5:`{,#hجNjBtA5<*:lk#.u=rО݆K(lSc1v,2* ːݒ5{^T)3>t/q QoGyҗ܌- U8L%KY Kǒ t]vpyr}}<:>|@\{ppSyMl<_+7Ͻ:`a.7okCa])?6B.'t?9 걙Վ~ !)[A<UH[QN}!GEg>յ1Bn I7h%7׳vS 6S9/R\h N_~MlH$7%}ndEZ#@JJZ2LS!۲~չڟm--B!Ktr:5[✡ .D!U6lᜏ"&lWbCP,d(n]cq%3iߐ=9kaV#R*AWu=:ilk:N8M~##+_ѷ^|CPEGq p{Q:hبqU p cnp+@6rfi+k}r[+#"+qV(S:">mGI%aʼnE ).lmelQuyul~ܫglƩ!̿ܛoⴳ>G. Pv8l8Mtӕ;!#!56d>D/'?eco-%uuScJ'DI#c\rmCx́%v;V͂ѹ)oK9$S*U0)3].F2Y@ yPOg<+%!AAmRactZCΞ:ЮTvW"GP S=, PG>*g(ΩqJ(. K.E9 J;%/7l3Z?y2@Lk2lBJ\QdG)t*eKႛWT+ _ާJ+vόQrZ)f_ĖBqL4v^o_# OjP,}I\hRkG_}ԫ[w;8gK;&q"ɠbc/Զ F ŹG^D__B?FG NrD+&fLI;֊e=hلZr޻ kWg{yT 6Q/<}d7vm'Y_+Nvq9#D`WtT]UVr=` ̥ g8$7gd$(6Z`i;B.\o',L(I:Gkӊ7 z16f= NڼUx[2cdkC=U-YD˘Jl(ܘFiy[ƶSuM?՝r:׾得GM!h4 @C!.h%4 @C!h4~I28|s,[| HyU}}()S*$w!<"nFyWBH0؂Qt,tZ0oKh&=,[h\%UL߂YdeKnmmrֺ)d@8c+9DD oDւI-ٚHBJ~D&<eBO1y@lWaQ>KNCk ~h:&0d[mI~8&{l/7b(0g{ە1p [F&|A^#7Mc $TT'ҏ9Ó*S0㭄O8Fy4l|e^L,[^T4,t߁^sscw@g?\f r|( d0RiB?mɭtbck$fNQOG7Q[q5l:xCvƩ y[>"<X_S6tC"ȿHY7SْpRTyL.>uR եsǺTNn?ݞ"r4U`UcWĩ~_ @C!h4 X}GӦ!h4 @C!Oȗ 9R=ӗ!J\Uc9S|UʤM!E"LI%UfA8r{҉l2`KDg[ˋ8}L&[ݔW(;4}v7Iw id9('߷&n0%n )\2H!vE6CZnRLD>lb!어{s^ &D< Lg[B0lxa+ >蔲=;<;[_v7W/1hH1ueɘ'82 s=6: L.\SPpXQ0+O{>qNVzU4#\mzK,i!H9 a5d`Egfl-"ELG j Δd<%-)#$K)'mp堧1EY$`͢S"FsرHdknpcɗ_pJD 4Ŗ4̏lE#\oزm1*j^ms-?n% $ I;H;蟳ΒB:B-BVbMO-sq,ѹdʠt!k+p,CupUH CnjƼg3PcGA4CciEǶz4ր&v,Ir,9`CWz"[B1tlȤsYW\t޾-f<8Xloiju_*WVo"@Gu4~Yym4LmUZ$g}qhMq\SuurC'{O\rbFA}mYBg&s<2QvDɰ-Pmcw+@IDAT8Fl3E=?d;#GtYyG~2%]=QiAՕ{Eiq.:0k#`D]- fvXgcgrKŹ8ձ'R#z_4z1Y%i5QzVyln9I7bܔ_JQɾ}ij$άیAw]S(f}a4g'gsN8k+Zq!Ͽ۠1ZñskLʶ-(k482DE1o[cu>6z)3DVS3؟VTRm :0+_ˢ'W!#1[FA߲{E d}$q|;k+Ƃ0{z%KCȫ+0xBmG'Y T.b۵Ť،[ە$GkP?+##3 {P"=]c.[pm &\Y`2N=Eur+P؋c]G>6[hSGTb gtxiKS6cW_I<<(P>E3YJ>FiV\ˆ%tH9=S%^g&xpVkuHM{OBo.ͭvwq#HSŧ?TZQ0h<1]qh`^~ekW{8`j ,Cyp{'F(LW8u;8^̞,yh8dqUM ܂g1MCR)s!G:H+ö$1*DoK( *^K\˻SY2Lс3mϖC'E8)ƥyNeK3(gYpB@[k\y%CA;0#6&%bDҸD&g/C@%G_a֡= cJ<(p'Zxl#7[U;V BXFBz6ѫi3??tnE^6ĩpRGCI޾{۽z{M-Wf!77c9$Wn3CEx&3l^B}hDaw?p~,8zA"?Pv Ǯ][lyet` Jh߾}c)QF,YےĞkE6]: 2b##xYCrHj^iZ=rqt~+-cmoTY(F4dMQ Wwym?@s>EJ>}z}{6-۬un bA#,f[ H).qtOG4r4gkdg^S 'K|pp=,څX80Ųْ̱W \U1G #-Q^UQL=Q2?Rg5g,a=wK/}X2 \TБgXfE@DhEǙΐSkeܧL][&1X9ؐehȶ}dqnOkX/όa^qB8PRO^~]lq Q/iW$l2ڤ?eV]"T("ַ @ƦI;T#I*СG5z%)Np>kzYw{ug>mWg4"[ۓnuq*fvcHIfl#Ƀ*ll-6H%7PZl N!Ѕ6T:F;QgE% )b\ N%T7mu ^6甾[,'q%C%Ia#$ %ԃFݎGrWa4cur|UTiS ׫La҇ԦOFl67`ȥ يEM(9SXSHtziWqlZi-Iv:gb] %?NN?uW"^|˟?vӟlJFYe GWm?ңF6rnUb9>~0mDW\Z4bO"ȋϻW^g{/_>AֲrNb{^u吭(qXcI=XF(jks+ :DHfVQ@:Y&mԝ%a,|Ҿ'6I2fS.dY]tH6e=)B%cAWo~~_E3:|;?>~8Lnx4ۈцB IXfmݾi_b22k42H'{%u`7bvc5:Gtv{I{[Ho-6YFSDVFw:!oӹIo8c4mUZ=Q;)}OXgm\r$QmDɊcV}7(Qg2% sJR;F)YiW5:ࢣqs%l:YsS>tjYˈ?'YH2PcV?!* j|kKEӨ-62YC Ni+ӵ~Q 看c NNNٲKC589G&xZ}/YOXs^YG,yO塚 ( c<UW uuk=3b8'q(!8ڄu-hOa΅Vz[M `ƿWZt黎'w<ŏ,Κubю@C!h4 @C{D9|tj4 @C!h#_K ȁ|\EC^s"+RACDҀZʄDJʗ°gIAfAn/LRDt8&dGv2kxH".|+YF@LlClEO7-dH %z|HrB,%HUؔHg 9rɄ] ;~} 8@0p <}K tn7vGTM^aoZ&%|" }2=6z儐EvFnrzHpl9%30Ve6%%sx y11ۥ` mi{f:' K"_,9fRJu_V'IB#CٺUsn+$~M)ecjf)SmR9wqسH{&u/ئTba /?_u/_;w&[IՊc5[}"5fjUlN$THӛG P165L;.f6܂W7a1 >1ۀMq1 #yЦi]F|lyO;]G;(4g# {(#1 V`GgD@XˀpVr gӮ$#Pzع%wd!2ID TSg7q*}nE`y%GU$3Y{2E,tϾq7rcʳ u>wFXJYG=blߎ̿K}""D[:%b sI=<g8aD:a;o8u]iɨPD RPl5kFt%_q*mR"D^9*8c O}{6HxS g"mKhBXu813cc;\۝oڎ}?\ҥ 9oƟsM'R?RXSiGC!h4 @C!"םh4 @C!h4! IR%Bt=*~wW/^:jT&}6!}h'CP `þ+!7E>YWȷH!ѮSxc' $Δ6!E!Fu09[ȇOCve}1;t;n3CWY)6x }g[wI"t72r/U) iHIld-\u|)H9vyM)0U u)QO,D97|`KȎY#֑YO::4ܡt6ib["]{6?m눱$7z'~;>ֱ2=G1 Q?Ũf|гlȖ2_swaάIztt3̡Æ@}8;̅8fI#;y=fU&%3sCD .`#pL1[>^ly 8_|>ҽ~us-ID&yV:y؋Fᩴ.>10G9\[4)?rbi4xTsEM}% c 3Yh{MwC=>}( 믿2q$:<ı%3"Dg;8=)T+;`vvXPp\Bڲ>7R69w\ Fd.W "3:^8FA瑀w+]Im^, nV]gXs/d{{/asڗ)y<_Tr(Z;!3u?}Mq ʨXqrE8X8Qꤨ"ElΪSbF9~v*{2.0S6g76km!ﴭwLQKgLLߵoh4 @C!h4;cw7$M@C!h4 ?F >_Kח}xI })CB"5*7 enRI#%o-HeXX AVDezY7u3CƉ- q-_8]ضf_ qfCh 7n[M 9nKBqK' I`N%cHdoo Q6!h͛eޓ'NmI-h݀A% zpn.ͼ;v_-gB? ݪgm}vggM#!}wMԊK֧[:mBneFr evRj\a\b? S\(eKy2E%*q "(¯6~tްRIoKuΆspz2OEC}y\ nS>ױITtKkʘ?vNxɣ"-JMJw]rΔ8*GM!6Tb;LW, ^<,JqY_Iv̆\#ȚutC}R0UE}^{:bD[u{=xgJ=;q.FgiwTD-9v]tw#W:+q!֋Bht:]XX//Fu/cwۉZu27X?E.po{UQсjzE0y# Ҙ䦿YnDƕЯ8EFۺ<g3l߶v4 @C!h4ͱMC!h4 @C"~/8oB?_[/ŽCVH[CDE0QRY>#D"rmqBF,F,qXA b_oBV< $!f ",hs3|+@!qz]T÷㠲ܢ,NHK#y92gtS rFv[אCh.^zK%ge-G8T.VY#z]|.8pn݈1&l9B"d{ok7w8^B!tq #Hर&6z66ٔF#6 j>Nr[$*RYC%F(@R@[riR-$]S*USC•m[Ƥ/'m97U]):렀<˨% KxnE5%~1}I #(ݲ=%Ng $. VrYmyoYR(l#L )ъ=.=f|f`TH'I璕aI!יq"x=gKg;VoדG9>3~tx|9/:U d2se)'qq anY?~R[ zbi_Kt8c}muXT<(n!1|ZT6Hg'gVH /_CJ:e.k$u~Vx1 mOtW9ؿe#3JƒS8mx 7bwoΈq]5|.l3fr1x>+u(Y|nyVd.9d}ܒI{ڨ&7^"ˈ=ڭ*egLD(j`lmoq(t.aޤKq u7=]8ж@UIJz Kl4KJW0VY۪u6N\WML3t} y'Hٖm,1VAe!HC|PּǬ.G7ʕR1׽M+QgO?XoK<=GFb7y&jJh4 @C!h4Ccw8(M@C!h4 ?BHr|7~i__B/{*  yBVN5)K>9)E6p섄F􁈁ŰbbI V$$Lo9ϖD_H-dIjI,+ч%\@!a$ol95"RF#YͻT$$%glI"68l1^P5u!I,bO%%L|{~r: N6$U-nVr{%9<4jy7ώdN0ډ2f nJ2K|J@oH$QG*cTQIFON1J,\% ^x5BBH&ALP״kNՋn1MJ:JP1yYo԰1-Zr~jDxOm?GrP@_[!;\ Ewtd#\kJNOp2Įe3j ytyG1`HH_PpdyF}1lI"͢Ftr{0)tl3c6O:H(OdMKs?5l]\]QU)bbe&FD`P7 ajXY䡝ű.}5Ӯ,BsuͥL}]HH5 tQyq^P㜿\_.V/u\iu5::^gJc.IڸN/&,ԍu(YwOma>edSM @)l([]J@r8&^HlGC!h4 @C!!K1i5 @C!h4!|5>tAԗH\1"RZ&!Ae&$Hc-GtUW^^%(KqM^OBnŷte$QWlY@x#o O$ !]$&Q^7JnنkH޾ Ɩ4l- lC[H~= ]\nA2jm 9#GW $ln dl",Ӝu` K諺@vBmXb0A\L{ ig779KT!~ Jg2. 9?dG_ YDc4IwdB/.lYFI6<6y ?Y[).\#=ٝI7tmC`¶UyluQՂԤ:5/0B2M Kn} Q$K7( D+CE#Hݜ-Z%?{Kd/yTx#.:5 e78%y91h qde򼂽-Kܺ/tmP $n]o%nC NUGlܶuVFԪa!O]! ";q8nH-~L:|R46Zm܃a?!%Gv'&E)lM{aߟvo޾I%Pt~%{87NE/_m' Pj@DPw՜qo$`rFi*t7OY[=Mn5;ۻ/p(:KDԳgG؇8D^dbeep@3O:kwN}MdôV:f-wtQ!t=~pymG?F ‘G+3e %6g ) 2+B2r I_½b\ *)ŒݶԳtNZ,v{cSϹ|d@ъB>KڢGt%s_³zD:c:u2pJg}q( w]# k:-v<ߞ7 ?#QWƎ}&N] G8$dq<5q:SbzikKFtbYU-+ @C!h4 @s,Fh4 @C!'=}}))I|YdB 2[B=%6E.,&!q#h i -a}0yBnaF?&BF B=Jd1QsJpԸfn mAD&5?>̚/C߬ˀFѱd gH yrk~ YztBeC:H!sKVbđ%sIe$%D*lI:# 3[QJ$܎BxޠH,Cut1Sb"sGdXߎd g: oS E lk)%l?,kB^4BH4}Ђؿm9{i&(O PSǔ44K!Fj`O?KoO wo"b[= EoH")h?Q"N=.l!lBR_/{*q `?X/ K>ښs6y-'9[L\dk%D 2gz[;'KlsUޅMWa;zi~@^4<#"$*#u/]`ҕ5xʱv|\X9^]\3nVwwl84G8h|Hr?u^f[ሇtn4)6!aUmNctHࡗu>zup;vG?{oF wZ()Ꞟs眾tגYJ)%.IZEeR}|=ǃ"ssss ".̻l13sf/Ӌ"[lC0qKhF3)2#(o5oq;7(!NSD?#N|aaFszóyEzC"_z+Al_ޔ7O-CL,gH,Y{o 6 a؂|ˡ66!]ڔyKϮKVkF ohd`D^YjdQ(*T R(Qe6l?z C^~ʖ\5HUlCz 'N _Bs ^f_fvWDDb6`=Yш>Tymtα?ystAC=WD ӧ?tOD*?>ʼ`nmu אuHjY_=+\zM aoO{nzZs,H#jųi^#{mɨZUv4 4 4 4 4 4 4 4 4 4 : 4bɭP@@@@@@@@o[@~LW|= c8g2C-ӅOp73Y&%4w>V2Q/ f䐽@ 99.!/Ol/"|N+ O"IpQܫȥuv<]wqC_e;9,^18k(?%&QX,!;yl۴g?4@IDATmr\[  kđ)>XѤ^*$ Dd7WDZ~AP:$*wo߇L) Sdn@p\GKBT@ĭCum{\R.c4\Uu0%IIpugpo$sqHpqJS wJdˣ?ս TK7rgqVבQu~HҳŚl8)SkkG faOʦ=8*rqos/a}Rq;̗ؖQ|I.Y@c`k$I[[lQd Կ;(K%xPfꚵ10UG7gjMdv6yDMIc|\S%nBy2Q/maJOfj1`mЧܣ@dڼ\ #Q7ooQ)U:dVj&1׼yuޛn䶍HӧYYYYYYYYYw,p':<$_`?ŏxܸ`lZeQRO&t2z\"?b 8$QwN(z\i2OSqQcp˭/fI狐9tqrz@'#:01&I&J^ߗ+ 9bfǁXr1gAA#ZFZuT&c`[x\\2O O .<|IG<T"b3j (#s@S BbS*IܶfS 6ڛI\V!, Q+N}~EUhBT [s{"A [Axl@5R8 Yw<J@̾t)Cٌ°slprNͷ-FWz9 +A.K n.h>{O_l<`Fհkrt 4Kn9z>m¨ ERD zIWp^5˗8`@.=Q@$) $15 &x0"5Tڏ2BE`uƘl}랭0s"`GA1$.ܹ{"ef?O#v8#% ,E' yQw?]iR,Z ݓ??2sjsլkK3[u/,@ v+֫>N1_|}WY^BƙC =}w3#w5n+8QIwKaXMqneV len{aL׼i\}'1NOOh" әJIr] ) iTՍˆRإ4[;G[0}eBTpBYIq(2g;?[y ꪤ{TbՓ, ue"s=?B2H@oIV̽6T'lJwus`ÑlEH5ӌDiqk >?~"k\#>o'Dr;sLr&-ʸZ1Ng.ƖQX/wׯ_wKKCD }رmom M&S_|"ڨ: {k6НQKT&LfݍG̙|:j{Xiڿ>lZ_T}@Gl_ݸ*ޛnvGӦYYYYYYYYYw-@R_ ?eA3>C/_ ]/CyYOl I 1fL Glؚ ~ Yh: FHԸr8'z "مT @ %q@v(rH12QFgL q\%@)ޤP>O 0rS3s@5ЏK1OCI >1Hl~ѝ^]Cxq+u*A#ed[]J[[\Tc0u`g{nGmOC^0CϸWion7 -Y6 Url^ `U|-E!v(-C<#nwGFi!uHO2}rdE>!I\}[uޜȻ?:HNAC"9=߽^=7`b O$"$A@w3#$WG'%t.G[cT yv6ᕞ'M-Ã!UA&sK9k޽}! A-Rn/ʴ/l mF댿)($k,o9{[!LSO𢼮bw.2 +ka=ʤ4&gEо sHIی v3eYn)B7~M$3C$}{D)&҉Q?Vd.W zMi/|R?YwX5idž83  è9Ѥ^<+뺾zo0k%[-X%MY]6B#6r-ijQ͚P>=-,!NݳϺW"ߞ&̜2=y}7уnmp^! h"ojI5Ӯ_?_2/G:R׫MXI)% a)DIH#/.ϻѹc'Q!)s>hJR-jg_UWBM{e\&b]<%(:fJʾj/I1!EWX?]m'|t\ ׵N}Ù՗_vrL~Vdg̳7o^'>zܭv ßwKbA̡%kj+'K î ] 2+o=%kTɭ$lģiT\b4l Ʌ}l8k[r?Ϯn%N?Ͽ)=b(s@@@@@@@@@@~]*5m~"9KRAc &Y-Q  I% +\W@DG!FV<ti v~Aəƀ$CJ01?h_)/;<% E"lE E$@yws~:`Ba> o01mQ^ iH nӳ\Bl\-6O&$GSEdzE-yλ9{ZD1OZ0E^9w"Mun3>`; &u[2 u\'*KĭX 6aDIS} c0\C?0ʦLZ.`!#G(NŔM2U" FJ=\J~EQ T)[/HM_+7,sEIx*PU`Ч?)Fh/s-z.SWJD o$U{x 'J-pv!Kbr5ҜCFĨ6HNE{p\%6hm}o+KG2/)#aa x>0f.C8LtC0mFiqm"Aݸ":j8!/ϹZ2Amj\rj%W_ekΡԕ EC`Y¸ߋK|s] 7@M?E -ul$U;Ymlv`  9nq lTD08ƒJ3?F"A1[Ю>ґ㥞o5gG.ڔ_w5/:K_u4g6Я|Z,v{c"y y'A Go_?t>$6InxSV֨KIM(=/%ͼ,%1(?˜Ip$ _Hx?3J;f8A?j Ab32!@eҝOLsX'aCl_ZzikdK k] RڨpJ&_)EQXa ϵ_Ӑ.;_bt%v{zFqYۑ=H(9u_",u2_\<6F$۟E叐~?D#څ8f{nz!ۈωDb,xbo& XFi@'7Pg^eSAAo#2< d^)ޚn߁nbMffffffffff_S?Ox_A %dE{Hd%2IU*H$E!`,î_"EV^M<@Zp>! I'!4SKH6F-!:͂Py蕠4s9c-Bڻ [&\Oxxkk[Pʜ ׁ[BoLOڃdCj#3@=7Ty*?pu$( u$B \k1 Hl"&b0nDa%D}A'Z@rיYq4܏)=dn*^K%jWHN$5`!aKF\ 6d_o݀sDFkت|mB9*] `LFYU>T:f/W7x9p!%s=l]bR + @/8iDh  MIF63ױtv~!!Es3av9ujizM}*_ 8Mw[6D+obtZ *Y}˹-.d}Bq\!m—5G%7ޥwWc'߲iqƱJ'9U6Tq\t=RvE^ag?R=QH벯(Ő%>_?v='Jk"Hpgr =a.*r$ 9".lQ/ۊm~|SqtY$2GP^ݬ7akM{DY0wLF÷_9>Cy.==Pw/z=$d$" K0!RH vZpJ !L?,iG2BeԜW^~Uӫ3%&R>mmﲍ] ?s k_.聵D[i-/Ґ#__%=.KnSk3Ey=!8'l1=F~HFQ$`IapM=*!Y4dicVݦi[Z- @X"s){!''%e\\K|@Z;a\ܦ][Wc/#3|Do{zA8z[8'$G]%\b$*$[?mEHO'/^ sH%ϺOEOͬ?D/'_<2_\5 1rʁ\╟w+V5zZHIyRKO}w*Wv4 4 4 4 4 4 4 4 4 4 : 4bɭP@@@@@@@@o[ `E `?+~=mυ_X0k?Q(pJ!XmA"ÓÀ H<.u.虀PF-B:O d^E::ֳ ;t{%ls`jS.{qcJ4nݚD`XZN]ү9ӛc;7| M?w@wF]W1{c5onoՎHܫה~na'0#2b'عCBF@ ,7K//|Tᕅ8[>ER쓤axu(;HަkJ wB:~-c1"ӈCd"Q]u.|##IX2"kGou.klGnC01r,۫mC9qTv1{?vOKHC@`9z/mW_v?>xX', ߩ7y$f`:vFrNyЗ+H$.2u$(wX?-}J ?m7OB{khhhhhhhhhuhĒ[7$Mfffffffff߱o`y?|q~/8 mO.m,JY/*p0bfy&_0r!b`O0N } !ddIr z8GIڜFSlC c-uF݌'|Ooonu#蔧 -eVa:VB2̘Q9'" 2 `?tH;:`N h_˘U_ xgj86=d/cɴFv^l'EQjC"\Qt H׊j?ER޶>}\ ^NY Xctq""«Wu_]j/lӱuq& 4 c؊2щ<$[~. -ۘXyYmNGT4tHԘHRn$n6#K~?>= #%Q ZMlAi9vsh)J+ak)+0mǽcݗD?J%v TۘCgo؋7 3&RwLM~`x BF<2WSaĜ"I.Y} '_e8JW#NB-h 0D~b_Lژ,nZ͵\qo2/$uI`߻3Mr iM9L39\4o:1zSf]%۰Vkw%gDD^ApKm-CX:&MҋaK*H=FUۅz&NVEV?!}'߾}(޽ϖW'у~H)=-p$=Gb߼7ӱFFw*es=rT]*_iN1zCCWCy_jv4 4 4 4 4 4 4 4 4 4 > 4bQ@@@@@@@@oZ@*C~~?+?6=`pR^ z6 q Q&lQ <3JZ @>b'7F{ _/<|җG;#/Z| OOo2F7̃Tr9vgj@^h–#n;kcy}ޜv;{?!2F/G5 sv9z>HQMvbB_RsS\K6Y9ՇI2G>xҭ8 JP8/ebԭB;ͅ#.ruR*yr[mk X| d#Is'{aq9o̳m|Z{8A Xims5gebyR񶅺sKsBg}˒Ҽ+0hQ -Iv:MG$a'-mZȶj?t d#ڟNy"֏B.锭Gh{tY8Gw%Jh[IUKZ4n%R* v_&q%Ra %c)Id+GAtF0H?ޅ@D"煸e ?A|W.l2z+gmc{ hoƨ & 2ٲ54|$0;?Yd6qY\S r8gLBӕQ>lk vsҟ,Q@GdҍM֚M4"4="7%ٳ!9N/ђ6!渶SM ʽ3()I5 vқxY]2n-dk\L',:$MrOX_):F_qz idUs@@@@@@@@@hG@@@@@@@@@, Aϯ( 8ЇGQQ >_qk O< .DL [\=@5M+>orhycطqh6Cd#|p|{:vz7w`Sb mq ẻ22b{9OrT0dOmq2Α;0a RN[D.1|Q5MΚq47I 46O+t }ؔ  қO-q?<RjNdh[,uİhtyRYFyr[u,`8YnZ"*M/䙯({Kzu;{OB"<}NBY tQr/.fgY27ilv^>rot- (WuMKj(ՁL y4moOF%Xe[lpиc@KuQѮ D[ -噧vB_^dFdrQ"bD&FBd 2oF-)Q C?O@Wْ3H=<xEbʔpb]X@cOgm4$Ņ*+re\(/$.F_$J\юewԂ,"О_՜%ؔ:-^Ij\P:Uh>d_J[IRn+"1@́sa4}Fܵ5b "iy](6؋b;QmNF\4*b`?}vatrsӇjL/Ɋ?|v R>QDr!U}OtHo٢ FҐajh`KԈmP^̈%Fў;:Ycq?cu,7ۖ\ .O"[|%m{?9DӸb7f1 } aAW<УgNc:?cF6ɔȊI >đ+LHx֞2~`oi6 /q% AP6ջҺDhX֠ts/Eg1Qu{LT e&&7DpHJ;;zQhEc"%S놕}C65Ӯp[A"\;Ι&q;D.:oFTVNNӧ~drhcni#i)s#r[;L:7W|[6MgJZ_X&cu“_>_cs<uqNyԖf$"T=&[u>}Y&- $ptFmn?iR*\WHJ7&zޚn ISYYYYYYYYY, X_|'A"ý`E,5_5(J `bͬK* >[ OK^XR/DEhZ@n?'oi:h:m p-dfl]756~-- bJ4U^@~@M((0r. -Kڿ$rg^@!cVp{ĭs=}\`qzc6$@"\dngo(G`_7/*xܔWb)G0GÚdoVR e'#E-Or5Q$@bq'D|Br%[7% $2J'[&QSW8|rZoNGJu o}"ͮ[$LOr3/q`I X@]pd0Q&2,.2禨YT1[ yCy !)Mqhz+iܥbA{n3>omC2Oψ2w=lईFBq F,xEj:{:E ʕz­_ڑl`D 'j XnGa!7+X3iWҔ]}]bO_yOV#rVK4ޓ/!-_Z#;{H1ҁ̉!}K9ћ$iTU_ٟꈹj\VpIRV'Z-m*\"&F2B$nZVO/X^~CBPA%@pǢT *Qt..0F?ᚸ%pzj;\TU_}WD-y ۙ!k|i"~y_Lu_(\ cC+y1I-H7)H;ٮgS I|ۏm.d$]8~>NBDr$U,,/8oŤOᜅT\~hc7۔?CnC=Hr#~|,ɸIl9g+"B»{p/?o;"bqi(R`.}g:i5=Ԯꐚ:?7Oym[><+ߌq}}kkD?Ov4 4 4 4 4 4 4 4 4 4 B 4b-R@@@@@@@@o[@xß9|""]E sU$Q.O_wԧN ӑP'lﱔcu3-FsnH0}-Je ӍUC;:&g{H{lKATL/BO@a<N_זp- 9+H{-Ku]VMj_Se>NU$ZC)Jm7rCl ;lȶ!?Ku(j4%8ȰLٟ4Z-a4/xB($E=K5'DYd+𮵅6~ \B墐F[뗒HCyqǨG # [ 6;l+QuLkJ-{^ThhY :ĬXz-,mŭ;#rQv%{FDLK#16؍pDuqvLgxmq$t0e)_.ھf;!FlA"Lv9˗Dyսu nC$da |%l~5MzkTD}{{Wa.y=Z[G̱֟ h4q˚Ho^?Bu k0٪˵c[A׻DJፀe4 jk{kE"~4[ɓ\$'.CB4 īHEkny2R\fH9?j5_JR;DVHΧB2ۦE{OI*! ,W\*_6( M]٩uuѭ ]3 QC@g:i|vCVc49zu? '˭&S*mȬ=%.`H,!aϽC=\۞'ۖ\Ҟa߃h)kboC+Ҷ::c1搷֗ jp PL\l?iZreL^a< ǡa&y~Y}5YYYYYYYYYYvYKnx4m! D`;­%~N @@ ?HSnͼ9R[ Qf0Y@xBTda%D vY^_].G/W .$1<=H8خ[<g[i99u?ucsܙC.1r9oU@ B ۸t9!'( ـTN[SS ,! h1]v[M[K6b~0j'U#x!8+PDmbu#>i9Q3'˔f>7oGM i_x%@/ vu٢'l=\)~D|nsP'e$ ?$ G(G?XN-}^ߴ_Zy 8 Bn@"-誟 h~ŗ<$ ۾~a<Abi*x vm17s8@`l"?JH2 0f[G}}qAC8<˜`>Pv6;e x?$(67 zAzz653 %9ؾૄ:,% TMX/ܞQH%ʖv(O|G3.\7u wOzk&-s^[IYD܀! kāSxL H>V F1 ֹ[L]J]3SE?G&'?=znc϶Klmc$lq @wJvo޽޽E$wvEɩ[vq$`/!I 5TQJ;D؇ĵq-%qݽ=McRxڢ_ >Ιft`iˢZaݺZ $(kE˒Di z xI[dف<2c˕l:R.Rr/Ws!o' eWJS|7w`eW1az潤.&1bHCHs͕,͒_5I.]1S8xYI5omOR k(jR|Dkн_zSLn)[a~z)ѓ6![Ӕ6Y]C Q[p sJJ{ohhhhhhhhhehĒ[6 Mfffffffff߷ c|8_gq{JB~@bA2(/@-y]$_Cm VWś' |(fFyʛHXP0mth$xd\w zIX g8$3x@0'}b}Tg &8.P=.%v<#:WBl dI,FBPz*#d>IԞ7sXPnM$[W !meg" 8 @IDAT&@KEBj nFb[Z$lm|#i0ʩ<흍i+$/{y5*^$F:$ۯg_Z@t' DPuկ(H}~bFi6d]3kI!RUl{9~9XB =W2}Qb :^xpy.Da)z)Gg%7 4 4 4 4 4 4 4 4 4 . 4bM@@@@@@@@[k~5 I~)X&( &xx8P@C \LU6ĝ"F@ eFC a A1dhx勵O_c'/C$1QE@xmtgHs)4.Dl4@'9t@5܂B:/8gsd/~^KW'в]tK" #{kJ7z)F@z>^[Ah벮c>!`99r򼸔0{S< YE"V߾J'MV'$%)˭$H<3q+# ,肿jm`B7YK=NIH1{ۄՈdW{]܆!íD$ռs~~Tֵmջ42C@( s Ipۨ>D5!VjW=}p@8â&5KI{>tg֥O,D"IˋFz0DŽe In94Q!>&Zglsʶ.I’2`5j$qj( bn#o1܇`"='>v& ɘBڐXbu; f6B%[\D4-DZd4P2yw4u > ($@6)@1:lGtcэcu''`sǍ؟pQh/+k5G#v 3^foF`=|_ O _ֈ3A;{L%l!r@e;<-A]WMoi_s-oG`?}Zڼ }~yQg&64p{ 0XW@a;Y^M d8%$dޮ2yF=8 sV2O]eYXX󺮬{N7#ӈ%3f | $^ ήt\HPH 5*fQў[8X8|__PJI *q.!hgmdNH:2NLh>5D/!@Q0f5D rPt!LtDan>,ٞ˧CH*9d\݆ca֔mo$NSn#a'`\װVm_8E/V53:ToϺi ˋDǐPuvV}%BX@6?\co>w-Evm1dCR3@eMq=" !_ZǷ$Q5M{UDu!& W+i_kt 8^Јlq@6E'%\I$p?EץfVIy%5!i7 37fF4b7t3`2%Z1ĒyAi9 fps݊r;D 6&`_0F~R]؏NMۿl2#_#Exzv>OOCj9Xr-Wg[ -WK"ЏMO@+rN Pg:̖_n@ԟLnzc#`Rin_`I8q^4q['WGC¤1vm5?ķ:_0za|ӎ?d'>nCdۉxRg-PF&W-bfQ.MEHX4]>rTZ6?Я1"#ԏh,|0fԺGZvm0oXWm_c>[U^?>)tXس%C%ez$-o`%M YJƇ?؎>Qsٱ#+ʯmIpZ6+Su&T2YEQ{e?|?WKK caY gH\r{/u63z{QS1LLjGNg?Cyuk(#xAnv3=6CCOmR]Si{9ڼ$d.8J ʸ7S}%d d" uz`=HJ22jͱ,8Umѹ8Lt>iqlHk} T̴v\(~ 3 jrN1eꥐy_RBT&­U6ڐ K#4:(+#2FR䒥LӫEI\'blPִM|$hۏI$JRr#J,u!(U{&rS$IV~Yn*sBRH{n}G_[ZeݥCZ˪1`hL;T"l߶ }Oѫq},,,,,,,,,,p,wk5 4 4 4 4 4 4 4 4 4 #p>}6/V<>6AI bg Lg* f kcSV,J87F^@ul@UhQA)s +xܳ[X]] 2 /er/@&;z¾>i{ɽRUvǿeU rS-CK¡ncRkX6Ї=W~VB"=i&v`.{EHW/}_ǫ_xyTO=<_ǀFpf=H͹M!zrB%H2 pt(o o* "D%KH5\{h3Y땯!zHorqU/?3+:*2vŕk!I0]%>4vv}pʭmg0'_ȌΥ[r&"f9[=䫫5gvUH=sp =OTICYV(YiHw_0Q>%)/6:Jh@8S}}Z_]zSUmz_6̡~Mglb$8STUs;k S-_J:}ėW!P?@8a(cHmo_?J I#5/RmP~),HĞ[C?˼S6#!cdͲd[FWfɡ@XKE:к8ۖʘO?LJ:Z" qXz Ҧk y&1.Z¶:1_guN9ښԁ`5ٔ$O^%SQAG~q!6SKPoG@@@@@@@@@@#ܺ!i 5 4 4 4 4 4 4 4 4 EQZH%5AڴLި%;!*)۬b*4MR"@k|]=$yD 0D%'!h42G/l3Kj9|X?Aު;^tU[mAPIPn}<^#%p\6M`@/oRqcmKJFkr&#H0dUncHތbɥq-jRbFY878qxM)c=k폤Ρ~r4̔$glj؀9x}\yŨ}fn>8VgJh G>쯃;oW E)@ 68!l;a ʻKGld. }' O??61wqLS28)~q6+qa4N}]οЮs-w4]2;7d'X%uD6wy#?{Zvs [9m gfްMˬkpN%ޞ}#ei1/b n2TgB|s>m2;^OGGOן'r rP%ڹȊ^"+:A).!rdM ri$Н1"3Νk ޾hAmϸwۑ 6Wh=C7B91x;Y1Nk(sW0GS6;ub07~s'\#{0Vс_<1%m `hy ʎ>ꜚDwJk;d.?i,̜Nނj_~nc1I$Trb& AA%1vʛ1 ZFtNjW3H""icZNޚN=HJv9Ɏ(xחv5sɫ~{qZѹ2`$5%Ő:QG_|c*WS2s.Ӝ#itvR Ozz5ؽg~'TUX~rS䓠8NA5cּ$GdaD/7ʖ8q8ki?67g>x&_glM8gN :TyL)\]lFL*9D8?FͿ}γMƟcϿ~`Dņ̸e]OCl~{uGh(${݇kĝkܯIydfșycH];}^4>b?wDwy ΐZ_6佢,00Guro A/oG.c?8jkuE"?=¾yJen)?kJ("GZ ; 琊H{4kEj6{-;}JV5l'Xhš=wEOeaЇ\R˰6ygtG#5% 󘀥-k'*ƞYJ=R{?cJ/yB2z]߫<:Ǧc'{1rN&mGL?#=>۳߬ͷwc_u?;T;Y=7 }~9(Gt{?<dG̭mecM[m?)5kBtVդ57?2B GlΧM'=P! H@$  %KrX4J$  H@_ ,'u*$)=}0>N|0⤊d֏*zG7П:(qL":AhKG&b / Q8C8i34S^Kd>{]Ht.y7a$!yd͛Ó~z:"O]Q^sN9ՓDqTDҩ ?9`\tLlJ)`<RЁK?'.MS>*{s:@8i\ؓy c 5ÁD]c޺_rFu&5fP%2i3i69~R0DNmDr&{E+?c\v怏mlkI;kp6*s'%T=S v7(! ހSm,c! 1wG(Hq` ?kwNUJ΃AS':-xmvegyDH%Ƶ#[(dҶ:xX"4uj0Uɧ>DK^}azXgmcp5.@O2Q6kx7s#ƥc 7j>>$Zo(3 @N%%$LrvS<چ|Ir{J2cMڭ- /3=" S=) ӎ dSyRvDXM2w֘[\M2&ɴOu9Ux^~=&Њ7wɻ}üM}A!kmyw*jcCT8رFm2m0ltCIsχ+A)^z>#& + 0'"ήqs_G1gy ZO?kqRI:˲}mg.Iaen2LuW XA{.v;Gb<:xA?EZLP"T4'+m"366qnW9v˾Ђ&%x'?uZEߟKnE" H@$  Kj84F$  H@@>CQ@?auk)^xY@Ʃ@M zdǑ~}!wzԁoA2  ø}Yq%'n8qAJ}|ģ8n/8%)n>;g>A%8߼}/}Dpy? >^?sOJcɛ<2#n|{gzKI]dגqbmm$$K:gQDooP r8qU- 4-Iaj4mS)1/=iFDq$Ȥ3@"Әd3A+8tVQyYdN@Dl) (ܝ.}P~`ú e/8cOmctI T-^=IÉ;pF6y8@g,sgvԅGUg]8yDHˢgYQii hj=sse{=Is8"yhF c8 COw 7`cvFq}t\8Y7)/i'no25m5@ 0DZk=DR#s+ ɼ;-~<;U5&Cu+MF9 ,#hBf9G1RJ0V5 4k2h*41_zf|ޜ JVϣx0߽#H, vG= lAm:wXÉf С*;U5{12hCpÛYF#+6V mǜT×HX>}>U=]h H|`J6ސf`q^IҰ~֢E[s0ydk{T ὑ?^n_lI%dľsf=LPwю5;ejQ:GbN?}N̸p':2{pc&wt>?'E5<|$  H@΍%6"# H@$ Dq[x =,i1~dOC}թ"\:cH1*BGGp ^RU>p(QgP'bm8d#E) #M8#@K|{lؽ$A'Mvxo-%'>$zGJ~}5%f {q| f6ՁVGd,ݭy)Jяȝ ;rG*RT%*'M#is1b g[q3˜߱Mpg׆'5& +NZ܌ݽw8s#Ssܸ}GCw]ybso9#|؍bG $}trDG%a=rmaQ13Y5-2MkgA\:3?' "H)G0sӇ ix'{fYF~2 S@ȝ1 cI` NP?(n%{E:}@1;>0dd[+ Y8fML({scA=H &awu`d4{ swt=d 6//XBfg!K@*75kAP27u4GזsN&G"s1o4O~NBcl,Ts/UGj2:V\|S q-1؄~L9=:i̴ؕCQ.yΩr2\GOtJ94kFZҌ r暝PON@IDAT;$9}A:r_Z/G^{#A!ڙ)*?Id.Y+|芥?쁾9\.rg})ܼ$@lOOY*o;_$  H@$ s#``ɹH@$  H+-|"4||~}3MUrW~˴iVorE[ՇGoΒ%8:U?/I~5L=^WARDoZ5NxaRy|E7eN'7<42y/kӿ~$o~xv=D-L"ߌnPW/,ImvG0vi#g.Vyo8q>%RrIA2(T F~% ΞUFS:rޜVT.39}KZV/FЌ>b9 qu+}LC?VK8䪟Jݽ 뼋m2#>ƀ$sa9oӺɣ /i:UN}cHi)!+-"lyMSpHP[5Gf0ԯOפ,}LOq L̬nA>//^Yݓ-IjɞBW)Һۤ;T5,\J-KybLI|djMDzy}{4?Nwͽ^yy57*XFFzgca bt4:k ^mrmN ~Bf<(BH'T'/L48!!0z  vp,>斘<= v,xK_Nl/qiPe=kd16<)m(Kfsku *`T OidžmƮWYc,eߟDexO&8\}M93f"!#M">`dDVϑ-L|嚹:gO&8b8jv^h$ב6]0h}?A73N76~Q6ƐzlyjMOG$  H@$p^ ,9 H@$  |N>ׇu">㕏q@~)#nGS>D~4}@ ;mG/M\ )U>#%ohƩ'JA1.yGiXo80U~oX='śݳ׿o~@wݭN>и۽||W $yJk2/"<2MCQVK`K s$R%Cc܀pاOM7+vO:8SFA*tB;~4'1Mg+D-HCN/SK`NN[1xBP*9y5!u?lFnk nۡX~#RT.ݺO>:9?f*YbGNH!sm%޽a^$`ٻcqϾ7hG*~`G0%ઌxM̟F:̡}# #W4"s=OF2qDԩ2l9~hAuC"@L[4?OV< @ 7`<`0BjwS}ٴ+?ν\ *_]ok_da9Ҕ]Kse* D{'gv9dM)O)zc:4pEa|:"v=##ZLJwHYrԀM8&FP8@Dฎ$&-|Gۛ5LX`:Fo[>i27 Yi41Kթv33M)sч /D:X`p1KY*^C%F $s6%I9R?+٩s aDlu%D]\}~{qgl$XDWlg>m)SXux21*'EƸ''FQ[YA}ZX$/co#GUO-ȧFa~H6'<NSA]w?;ZzIVtif4XrT&3v!kJg`D3=%Wo^Oy}$  H@0D$  H@$E}kSyN}'y BZwҠrd?+/8&# qid[LeoNhZǖ6văɀ$҆rBK:d9p6gq\i#~N=M*eh69q8F<0'}v?VM8L Xu#.AOSãXy2.?yo2NМ>uԒ5sw1=.cv-ȨQ5;4/Wߣ+s+iIPt/fd`26G^T߂N v\k{GQ,^?9N BCھC?;8$X@a"6djDwZJB.k8p9q?_{Z ӌ.+hHe2v`ο±gE tC5Zeɝ1e첰c5< bntd'GnCi!(G2Җ8hd3wRg&3iԥؔ5--~7/fLM7g>s ~q0'0ZCcmʩVJd$Ag;alFA~}:{icOed~%6Gvw=67dBV\@#Ɯ)&\ETwX^iü aj`HR1sXێ.`vMi&j6A ^4aXuT6mcGcj^lSH3rh3}1a B粆|UjzH@$  H@gH3M$  H@ ɇ}84&|:nR:V*^R҄["!(8c(z,㰾fRPs㵲q c]){*;\ׄM 0cI|se=8;=adžd`4c?F'v߁ő%ɧUJeZ9iviP)>$F8 w y\$I }`4% F&N̼97ߢ]'xc̳䦬AGBD5&~)' 8nS5@(}βچuWn`v8BF^- v!ևEk\^[B%ңA8#B \} Oԡ,1/m*> iʉ#b?S&r$Ϣ8sC%}mDa&{//e8҅z*H|~1.\0, XU05rY=iN't5ANly㤢aQcvqK &aNp͜O~q ޥ.ٙoo0I%|>G A8 @)L_& G8p>SQ"?'8iiO a\y ަ]w%۩w,6t~P~=eHp۲KgRcznA!4)şmFc$񯼇$kkA~ XOz=ӍԝzA.<.m 3?~8͒:&c֪?z :9lֿ9Ӯ'w \A]=V?yif}gêA% z?Hi:}EgU(d0$_B ;lFk_iژ-.mP%y؆؉UKpO@Ւn= I7{P?KZA32wY554ZG?+zsO:-ؐqf呎KیBdcjm9wMǬ=QysљJUс dVY}jv lkA/!!EzO-즬|Z~41$  H@$p^ ,9 H@$  |χDc{TP~*,ܝ$?S LQ2|,?\m#(̙Ia\2q{iuq:}=C?M{K5NvlkiD8M f~o&uEJ<Y}o HdCoӗ78{qI^oeoqo}) kCor8;~/6$GĖ?eB ^#oFX,mOy^712ű6=ۜbe >ԧo~#y0Ke$;XujQ*MX>~cj;0Z"_)csч*bztSs%9*Uj՞?ZxC{YI!XFO{K%BzֹW/6E1\dmI."׺ߢ '.DE)U{$ʔ^,$ȩJ#VOӅJi"! H@$  Kk[}2ؿvZg>'Y@6A)iRH/aHnݴobDoߎکGU~i'jqYX0PGqdv)_)D? (|xtqJƞyyzu||&%e*;L~Mqe=K;!#~MIJ]<'q"Ge$G+4hOu %}CK:Z?6jQ өrs=G]ۡ[mQ1݌S p"?W8-\f8Թ)O_I><C8U`k5"VspBn/dCٷ&^7$h>l`V3a3zUn&O%(w@mC"27Ry- џyr?M*Fl0rڌlf xi;F`:. zbȚ7U )SR]CtUst^-+Q]cS-3L^eEpkMaWs)cJŜyL~-t4ҝ3}&)uz/M(M#;ʪ3S/gS[8:–)#8+aIWIJۀ $2!*gG1ֆB/7G̤]}iQ29Pv`ᬿMS.<*2mv[: LLƵ?9ܿ_Ϭ pr R+;Q4s`yDAkܕDbQJD]rDoӥ;`mR{mF)ĨPsl–rՖBey+OJX&mK؝DyT{^SD[ړ 3zfMbo(ckJh97smdض}Haz63֙~N3X" H@$  KnH4H$  H@_&#.93 |^!?Ա|Ak%kӛǣOG~y;M}$idCt6Ku$`*Lə+I79pMNVw4A;1L` r౭R ٥$ęq|N7Hݳ=˷GȺ |Ӛ+8b |Hp6||?A:s.s: 9mQ[=p=ڴ-JIsVvIYRTaIE_ñ4p3YX#3VLI 7k ɏPl~>*E_. gt?B107*bGydߒZJG:19}iGM?x\Z,uNo;[a$r'cPB^4}^̔~C?3;R'ש#?))-"qsMӓ-s]%'|jHp䢟Y?FG$NDX0'z݄臂AS+'h7?#_T#hA'FM_?p:{*LJޥٌmˌk3W`M0n3yTI0\6XL="TNw')N&q}Lw{A?!'+aV/ǮH)}kcz|$/?9Kf3m៹~;vrsO[fai"$; jdd>ǣl&Rv0[?xzԠ9͹Oރ~\I8Dh., ۾A:DHZI8pvpk;S7'WSߒq$om)5R}/:ho,ܜ$ĘL=HnAp<[5/؂ dmE9c0wQ 8c,S:NQr>/I'kqLS$&?߯iE9>57-Yz1K']$]gc_ɏ QY|bf_ߜ˝e?~6s:]n~3oZ_迧?Yccg~%~>{5< ._r ?ST۽01?_r7|sÿ? "(}Ŀk3,f,8$  H@$ s"pi4E$  H@@(ze]5 Kق|\.xrj@:I"PHȿsd9^&o{*^9-db׿(}$  H@Ή;hh$  H@Ǚql|DZ yl qTP/33t^~))/ >~6iK%wÁgT 7UH@$  H@gG $  H@ ]N <3! =u83V~H҆Kx|Fzum"o>s]ϼfed]wp5?}ߟy|$  H@ΎzH@$  H@O."g_ҁ`BOy|ru>?˴H$  H@ 8>W\rT^񏮚#r? =CiЪCa?9ui,ٺ.}?yU$  H@8/xh$  H@N`9@WԏW.O듀FV΄wȈ9򼇛O3qk0"Ǵ *$οRIqaqg\9zW?zqwfL3 H@$  HXrn#=$  H@ n:~\y'~~N iYrZ[ aD.OW>io| hAqy^8g-y//= Y:M_<$  H@$ #ϒ H@$  H~>!D o}VHeLHMlLecd$󯷆OWEvӒݖUkN[?{i{JQ[^Q-^H@$  H@A $  H@<'9#99˩Ҽͩ&՜T:d@V+Vskȿ zm]"֟=_v|, H@$  Kk>H<#jo$MH)lrGjcN*b)Զ9+/ꗿo>{?6τZCLH@$  H@gE_geH@$  H@#o8Ț-E>g wV8x l^J ,/8WPzS0鈚iRY~pq֒' ,cd7׿|$  H@΍%6"# H@$  #>qHs ۦE+Sm<( 9Yۜrb/k/{>?ixH@$  H@gG $  H@ 4n*nm{D𛣁#dG7Li7* [xewP37:1(0^s>>34G$  H@$  t>qA|HHV||rNPmR$  H@$  H@8Xr.#$  H@$  -xzzXK49;>BwڶY$  H@$  H@0GI@$  H@$E}KejyEw-QɁ-H@$  H@$ '``$  H@$  Hk<xڱ$-lrz޶6@+H@$  H@$ ``e$  H@$  HG $ndI"`mHrڗ1% *N%}Ώ객$  H@$  H@x ,y$  H@$  I7BDɑGݬMJٵHƖLzY$  H@$  \Ka$  H@$  H@'uCȾT%㐘˦ٵC$  H@$  \Ki$  H@$  H@h,IJ9n@I^A$ݮd;Lŏڛ! H@$  H@.%wξI@$  H@$ H A#tw*!ؒL$  H@$  H@2Xrenw%  H@$  H@x#s$#pNM4dey$  H@$  H@Bki) H@$  H@v-I>gmPmTB_c$  H@$  H@0\؀ H@$  H@#DŽ\"LI8'dŚ|kK@$  H@$ 'L'&n 9;IHةcJ+M$! H@$  H@%W3vT$  H@$ O A_N㡱$y*NC$  H@$  \k$  H@$  H@xoGZ JTF&6Z$  H@$  \4w,s$  H@$  |ċ4$[%!4$Y. H@$  H@.%5vF$  H@$ %ؑlOrqɄk %\̃qW%  H@$  H@&``ɓ>$  H@$  HHH{3R7DLpZm/ H@$  H@K8i$  H@$  H@?{%\oxxdrro/$  H@$  H XrCl%  H@$  H@*aZ29v,Im&_h H@$  H@$p ,q$  H@$  (EțX%!A0Iv*p~T$  H@$  H@%``8͖$  H@$  HO"Ч8yݜ$IؒI?2% H@$  H@.%?P$  H@$ /X!xdt*HqN&Ğf H@$  H@$pM ,Ѷ$  H@$  |D$*ov7 8yv%a*~ H@$  H@$p.sM$  H@$  |@H瘗S %ݪDL<$  H@$  H@pǒ+p+ H@$  H@VH2PؑN|XI H@$  H@$p ,aS$  H@$  |+ a'6,9`MIHqzN$=$  H@$  H@0K@$  H@$!GW Qn@IDAT$"L-ö^K@$  H@$ K&``%}$  H@$  H] a$}([H$TleM$ H@$  H@.ݥt~H@$  H@$ &@ q% 0ɹ%-1bW\* H@$  H@;\8K H@$  H@>G ɑ8٠$[h!$  H@$  H@BOCe$  H@$  H@%#ox $lLGeEI Vg%Y  H@$  H@.%5F$  H@$  =Kv7d"L&DH$  H@$  H@x ,y$  H@$  Q 9gJ$v%ݨdQu$  H@$  H@O%OiU$  H@$ Ff#9it`qO@$  H@$  HXr~cE$  H@$  $OIKI'p0c{LN3>uI@$  H@$ %#|UK@$  H@$pyNFDɇM$  H@$  H@MK.zx$  H@$  H@_#0" -${ m98 .5qK@$  H@$ "``E $  H@$  H{ $v$A#+$ۓw|\2a$ZB!7`o} H@$  H@$ XO%  H@$  H@;ތy &+c8-VK@$  H@$ A1NZ) H@$  H@O"$Cd ^9[$  H@$  H@x\A H@$  H@J`X̣oKr۩WZA$  H@$  \K.c$  H@$  H@?Jx>&ֆ$}IHLJ8a; H@$  H@$D XDN%  H@$  H@)8<A^7'ɮ%G4dLI@$  H@$ '``叱=$  H@$  HK֣n7x 'ٵC$  H@$  \Ki$  H@$  H@h,IJěMHxN~3u]nw7C$  H@$  \2K}$  H@$  H@_'F9%TBtI*! H@$  H@$peܱJ@$  H@$>64va%VƘ<䚒$  H@$  H@WAf;) H@$  H@ G JJ?˒,$  H@$  H@DKM" H@$  H@w ~䘐SIC<>&+u@$  H@$  J@$  H@$Y<&a%MFr8GSA%=fR$  H@$  \$K.rX$  H@$  H@J=H؉ KtSRI H@$  H@$pu ,!$  H@$  |HUBɁ_& 5$  H@$  H Xrɣk$  H@$  H@&lWBIÖ%$@8 ;w?[|<+I@$  H@$ K!pw)$  H@$  H HB\ILrnlaKU>J@$  H@$ %1R$  H@$ n$|@r$$i6(V%=&E~(e* H@$  H@Ӑki) H@$  H@g tC Q8yo%`҂qYIH@$  H@$ "``e$  H@$  HgϒM?) 05%sm" H@$  H@6Ki$  H@$  H@@FY;vI~]I7*:T%  H@$  H@S"``S-m$  H@$  HHD18ݦd4dm\,P$  H@$  H@8?ߘh$  H@$  H@%IxRIӌ-䯴O]$  H@$  Ho$``_$  H@$  a>&7ۓ8KVX $  H@$  H/#``_ZE$  H@$  %JBLIbL]2ےzHPN$  H@$  H@.\x[ H@$  H@> ]JנnVN 2᚟|K H@$  H@$p ,s$  H@$  |+Ɣr%$WEIK8yH@$  H@$ +"`` ]$  H@$  Hc<$J8١XIR$  H@$  HjXr5CmG%  H@$  H@4BI!I@ $t8K L<$  H@$  H@_ H@$  H@$!v$%٠$Ͽ!OšldaS%  H@$  H@Epǒ^;' H@$  H@LHCK5^BryGuN$  H@$  fwLnWG͐$  H@$  H@L;g$  H@$  H@ $~yI;]ҭJH C$  H@$  \w,$  H@$  O`$3%iXCU1&$  H@$  H@U0*NJ@$  H@$e+l$A$đ18km-$K%  H@$  H@%0FӾH@$  H@$9&dG.aҧOOɊ5n6$  H@$  H@O%Ou[$  H@$ ?H2rDe6~o*ISKZ H@$  H@$p  ,Q$  H@$  |IXn\4QThPɇe$  H@$  H@IV;% H@$  H@`v"aÒ#&ݔGDC$  H@$  \Kn$  H@$  H@ |{Er „׺BM>lqz I *!DDTHw'98v|r|/qIpv 81f JS$P~5<ϮvzvsO5W@@@@@@@3L,έK@@@@@@@&+4(,D/tT@.e2       KE1Hy%1D[22z?O؂+L-A@@@@@@C@A2y$:%Z$BD-ސ'7ҍ'xa6mfW0uɸ)q@@@@@@@L,^Im@@@@@@@\ $go5$O eKvQzE & {͒2+x"{+J2f        0`bTj-        !0 {>˔ &1.\򦕀A@@@@@@@`bI %x+D|X$'/39qM>y+G^        `b~A@@@@@@& h`wy}'b:JL@@@@@@@@-C%od       ЗhBxNhvI.K$X0joj        0 XDbH,VV'dsQs        &Lr       E (rKI^IILbvw8`bT@@@@@@@`g $@JBp=vz#00ij*        06J)$P$y74<sIU8&88@0X$V-%Wq@.d2QA@@@@@@@`Z#%ӺyD|ZIr?+M/'Ѫ%>-n,@@@@@@@@`#0009jF>gUGqMTy13sԽlp3.nꚾ)VYS-O_i5<'a\ -C"l!D9 k٩ `~9?XD?z{~?.䶩ERWWxп+?% b t-4\-7@S~[V,hs `^ `ʭiN'5ڠSb">2ԡe};~ex^(=> ?_KПп?c/bY^R^{ oeX?Oٮk;^{=カآcO*C V1???X+n`wƟk<0crbC.&8D]lfw1C:Ӷp8tog7 4 6̢1 !&RD@BD\ԟ,,?A0 ,2e/ _GqXz`cc_`  AߢLP%c'i)x?YaEoXUF;X^y }ͪE[echyϴx Su-k`2-h@ M_.?zN A4뢊’?/z3tm+brLCR'GCZ?ѿѿ!??LMWP5/_I_og ͦ`bI?De$Sߨ!+f❙kK>0=mH$[邯9c 7& O  N/HQIw+yyC2 +y ӊU"8xm>b JLO ~؟aw`_H΁}}#i"ogf϶ǥh#&߀&V +"؞6{"oќ}"#7`ZLs4/wWL?7:C359ye3y99Ŷt~nڝ?hK!l@ ?Bkrw@B 0,?'z 'ozWÎ;w$#aE!:ƟdTo@lp:OO#XqbҙIΓ{0D9SF]85C@BП B!("?W!+P_蟡1XF4U? Y.: [:8 a%0?Nic4F%}o88!0B AIUj&+9|P . o쏐)_obbcE +0;??& K_[[!0GWmpO+a8~QIY]Q/ SLbK o6/y'7bUghCm L&j"|R? iu _$9hƿ$wKc/S ay*|hG8<=A|NF?1DZ5IS6J""!j=oE_%8:C#"$`0_؟&W*BYSt oߌGb-G!'1E%6,ȘnhAmW>|irIgzF.ݧTz)֕PR&Dn/tTa_@ѳ L2CmT9wsa_+䠞#_D`0D ɂ]/WjXZ4zIճ.W¡mL P#d' PS3 _ôiF3.W¡mX&wYƂ=j$bΝRxo0SjYz"%sls?sz2( MT_GXhW?FD4xk@ '(h SSb 3d ]48Ddt0Exg a )4+ o:[U1L,h|:X ӂ_Z %5cz O,F,H57"khd$_?AJ@jHyJS՛пI'۱T||(8o_?1 7ijyvd?fOc] `P?he5Mu!)HRH !}N\Tǩ*uؑ?C?1Ofq?_ci%!֢[7GN/Oo)&$ ! ª 7ojm\>E%}0*D,{+X4ֵ;ȦDsVDuoX@s;?a #П{ c y#W8 DfG o'7GhSe"$ϱG؟؟؟؟l{OOBhac5Coo'[2_%6}hwk_U:Q-8RM*kᅴQ7s'"Ʊt]a:DDR\$b 9"llB!:V‡п FXuT͆ f掕?5&GLUi6JGn8y߹ݑ-&s@DD'5l;C[iRg?+"=|%!)8m itRQ' Myjd 3MOQEF쐿 M⋵he&b?MFuHe"I#L쐿I/ȟ.+AFhP[&пj@ %hu?BLViBT;']H/&xS;!-L,ۦ`j)j\;j{ e#S`d/mߊC|ߎij]#f?xc$ "L! Q@22KOO 搵[`$B l2g/eߦ KOTSX!%D*O'7vFR.Vtq518T ݻݝ3X,/_s87=Ӆ!9& B 뎑?ԥп',?7FFScúfhm@*W{ޝ= ˤcпSBL@vI֧LAlœ;[=cDoSƬ.%g8aBS; XU:-z?fFE=!PhBƂc _@c`I Z#`P!:acco`abcǐ8d u +&7X3f, =(b#bڐm?Fz)MhV;yQ+3aTkЃ<k9 -:0[_f ؟M^b?E01s }Sh 3TH*%/'+L,זLlDs!F5c;M_6yM*6-9]q_ ct?T2P@TDQd[ɢw_UzQC8A?Ϫk@#A<WZ\K?U!-)KE&[= ےҠq? ӱ{5+gip2hz.?C?OrT/'\1k?'$=&[@ ' bɲЙO[2%?TP1$ 77SXҧ b ӔpxuHL.UGZխiY+"jNGk\On:+?}W IOXt"BgFHZs<'F09?4"`abccKJ_?_vg ID;`9'?%70hyyM& ӵC} 2g;]^ 5 ;Q!o#8:3> .:&y~` EBj ?пRBD6_7sB_E 9 O56IX}+d0gyZIKOPu aH-oL+ҡtk+6ZUY{-JR^*玂A"ĒmI2A!ۿh3"ް?罯Py<0. @L$}M=?"ds o QaABT@<#W6Ya]!m!mƈeaY Н?]C+P%8C%&Ⱥ獴oSiݏI՘{BھA/0&o(C<ՉFz!_:3"BSK$Mp?&R@ Ua@`I@NEca2S%'` >;HX]؟ߢsJ#~`Z`b- A[oP@k"R߀&U /Bz17M-kzXӓglP(Wө|?QX`/N?CpW1_Inwm*躅|Zڙ%@ 9jMVؿ_b7Ub`$M A oJ77wty"E%}4(wA+퇥Z0zSJ]iWE}XKYj\;tc I~)2)W'0@R2|?/N@6_tУ;䥰; 777dgOzI;Gbw 0Օ?ź `bI_7Ϯ 7ZoN[?&#=:2bC}1?}MgkziL^NA9u~n_h6F?_K #&w/O0 ;yrO+w5HmgA2_aA@_( #Ŋ+$:q< Oğ;ntѵS3$$D* "bORX mo/O#[]uɪJ.G?ڞp`V?AkX3v0L_C<\BL[d13򚶾D"tR=L9<<-o X>oOH!v- U ut h #H/?)AB2owAIښ0?Arm`!c]# GMƟ jh&qW4^*֌:s75d`DZJtKsڋ{q3wlfVL?C?3c E@BD67ERGE5_:$g`I87Ǧ/_Xҿm3n_(`{>sq^IWԷ[juxnskŃ?'Y$8X@/GӰD_U\hDveC`+iCR o3N/D_rAA<C _L_4 S``6veT?Df0gє*Ҳ^?`b#D$euGY%['CT#yB0ZO1Eds%1f.5IJ _ Ua؟9_10NI3\+5-O&|/Ma߿#L1Y/K%[|LE8|Ĩfzi$uK.(t(]vE}ȟ+rʎ8nWAc`aV_4&Se O-3?!Lvж?F&oی[#c &m\<q%]tSmupp IkXި4ŷ#1W/+!Q(MUoB2&MlǺRO`Pп?Z 0`c'_=!Qؑվ`L=Վu+ARd4ׅp#He Y'į9+mg&S<4Uc󜂙3TsEu*[3p3rK#Gs#C /a"tޠ+_ACӻ{na>5Gb؟&qJ !3Rt _#u //쯽BkX3N$=m1~L},<[ ;q!Rw}p7bG ?m E ?Wh[go;:??p 'ObR. *6iry&iXcاSZ8cD#:qOYսab%HL1 /;W?C1?c_2&G J`#,A_MYH?>i|bbbbb7?-?M = ?$"n_I.*Pd5i}]V WȞFbH55FE̝vهqIes5*IS U/ZY# F@2aQ5Nw.'1;V֘T<2ق#WR(U}"wG0J>??Eп:<soRְ[S+nBmJ}(h;TnzfdD~$ IEqccl &C?7I)3,:4? DCo4U":/~=B4]8ite*"& J2C& kA!n!@]+Wc蟢O!2YuaRYf??t":=N8з0of"i`q|Munf4PAXN9S_}+FOUgu ;n #Tzwu?_C$5M6,1=5 Fˌ,??ѿ/Cnl s////؟)FSbc6 .?SNa \tϳЧDECF${E(Xoɽ8Q]L_M@ ;${Dj(пrkeI?ш+#G8???+hZ&gU???-Ԟ?H52sa6}Ky/܀yq$'0`J ̡OtMk‰j8FDMUr*b0\CO?C ?O_ȟ&b_WV~G)п&gz}  ?3=jORȀ)Лt}T%)oIo`ܝm"`K0%03ۗB6z E䱕AߪwRl^]Czihu G=?G3/? rYHoPkP{c3`a.]BTjaL!z6gz'_u ` !T?Cd`bK`bN 3>E%}0:lL a_XXzE@ P#BPTڛ rVt "M 7_"'laC@`-`6ymz`SS SO14P/" _Я0_[f2K\Ռ!l+hR4"|5][شt=$nƱrv~-U^S9v>@SkEs/Gm%ߡV鑊&G[?<;쏠?__U_#77wh r%/aRTtOS [($ IsȀygGeӱ9[=T4+I# J'X f9D.?BE… ˪UgR6__i-9aI9~e#K*7n*>\yW7c`3a s£˚իK'Q֯PNjcU];eeeYem/O?(OO?"w K},݀f y&:ebk '87qׅ5^Kʦokq2{ 3"y-  /|Kgw_y77k\UV^ j%5ÙgU.8qkQv(7Z~俄?GyT7C'8nmeޖCb(@|2RW:T?'GWbx ߙem"tѿR2oΜ2p&Ll.= C/99dN9hƌ}Pdmc?w/KUh:$9`2ʖ[SO>Un{cA(&s-;˯R{(`zQxʌ&7d?w3';71R 0Jl&sS[:РlPN?%uZTG[䝡^,>X=?/>cNHY^6uZUI%AI]rtmʎː??=W/8?Hws-r{ek N٧Gn- =u:c=?׫<6J}?=}*<;lYfOk_Z/||;-?xya]+N/{̃٫п):䷲TDl;l%m=ݴ2SV;o肯GHi  +V, )|vee3 fh@!&Sy |wcdɒO}˗\|q/ZI2w*ki+ڋ֖3g!ϖw+Uk† _BN"QݞФ[ъa?՟ C+?c+Nt5f]&6?Q^O F'Mf?k6At0{&(gX\vDSϛwx1VWgebI?xenKjT Ǫ~,_B9|5ytלK\1 x?_'g?߳WmA#㳘M%vߣlxNٶm[y镗m{^}l޼9>wm+s-0տܗFzS5X%eǔEFdCcYWy6]Yk: Johyyd XtT'qV9xǾO=YJ{"ԮZNЄcS3&LwXn-Zô]8b>WGud;{*?5^3j`D'7ix' }`C8a .6BxMDдؿ =eaW細?$/ +w]j57zσ pjUNC |D7t}XoEH G3S%%?1KM;{SzvkV9攓O>9%ϢN8o,;o7|FsE`` ,>Vn[>ZXU֬Y]͛S{+|W7FSĤJMr@e7i#|u5@yƩ?g`zhҁL5c9oN/7X]  6S쯹sOIi/(2iZ5&}͟rA|mg xGtfQ& D/r+oݶeo}"гDߖFAA)4 OԼZlTuQ#9\x('G?pGtyhL3K@=3D%\DTn%=7(?}y !u(o+6/?,W_~&⟺rE|gL+F#vnj~MhENY뿻P}#C_VOLwFh|oMWCTOoݺxOf >A/ ITk7::W+*9.Vƿ 7ݾc[~Yr/~QkRI>"uJi'7~FG=~ߝ o}OBQ]lmn~+O=-O7ӥ\{$[d\7owD"kX&= 7+ԶĄF%}<.P})ƕ!M4ԀoZrզO ޖJkvCfxOd2ͼ̹Nho I!Sp?!>K̪^.η;b=],}o |E/Ɨ_Rٺ|GH+x5/;r#3k欖H5& Qw>/~绝7Q{ҧZ?ZN2zYµu!tqz9=+cmyzoǓx N 纪MԩXUB|Q}!7g v%2{&S3FMWr?xf?7>ľS8&ȺSQ q5]v}(}Fo؄[q$dئe9? L:?_O82-f-o-?`~9 U?C=TIy\=OrO)YHC?8cF9#Z^D/;vh$u0a GWN? aiXXpx|Օ$>ɧR^хwPoϛwhwr!5ʋ:y|G"uBHg#wƦGEϸ$]߬ٳs΍_߲kW^-^Y`]E$y{[5kf9ze!sʖ7ZoBzfWg͞Y:2wkK93V8#zG?SdM5 <i>8bC\2CfqneZ4?C#*$֗l4ZpTYlѸ_{mA`夓O*kmg)^,~z<.&$ȣDZ_/6n*_߬M&ZF:3$>ve^~]SYw\{f0.)ҟy?6GoHtӦM1gw^09uؼmDQ)bsjS_ޛO.Mad:-fj%K%~}jK=%;g^9й!}jnR?y}|ɪŋC&6wym|ܺ|J|e8(вnݺcxr.h%=Β+Eߊ\6[f2!sCCˋ/nKoogC[7~}b&`ϟr{E+6ۭw{'clIac&nxu3Q-q`Ȳ9l\z7.zK'3B>xX)% [7@?OwYg)=(E7$ilΝw-}`rUw]Yf͜)GIOǂ]X82{V]BurV>[nQȣSN>x0`-n-o~'+.-3g,gP ' ?a?iFy_O=tN,F_WO-ax9I\.]Rf$̛3&]/nxsw-l5DV=ߢc)}{ %(7}:%|淾u(M&Ї?Os.҈"??$Ix@tVF4 +'œY~4k̓O.>q@hvx}]c_zu a3>O/xܸ֪\oX߻ _]R/+K_bDZM_O`Z5?`=%0*V}MB_k9'&4Lbמ' a1 W{9;LD{oE:>Tޙ1 5Q5P+/[!~ե{([Ƌ_X#υ>ڞJ Z:>H6N<~ƙ[ՍgWﱪ# 'G,[L^63/:Xy5ٱD\'oea3.WBDO5cJIB{&$ PX\ty7h5e9p1?|ȉ%]Hj4$i|&?i@۫7p(k]NF"Bē{dN=vF??#] Ƚ(7pN_r%Cʨ KooIU QIo\=֧̜NJʳϭ54j{wjaX|-˗-d7zJM,~mZ/PU.׀(Mr4i~i?۾Ѷ~DxR֮]y<+՛)N9T:î_8%Zaee& }K,޳H2e'oʿџB+C~Fd"=7&)\>~9īV,ՠp^ W]rś1I`U MI7!Rlu?X|t<= zoPho }< GńGtկ}MSWFt|_үop,;|U du1o9'l̿Eg^Ịr0"&&ި_9'&q+Q{/~{.-o.wߛt%빒s?.U7opƀx&E. lى}sw楶s?oNS:JM <}q3DL?R^C%AYYDMC`Ѓk,24«&LVZz]Vȭv7 .Լx" mHt=eb? `_d47[L@o[[F+lѧJ\`DZlO'?vfgGσ:xG?T/_3Uz8ߵ*qQ;e]V4ں-?ē>(T񄉉_|/?fktu?CIkSzO &i0j.U],=XCu?>%B]^ޭU%{yVOw34Yk9B򯶗[Xf`DVVGGV>O@cK \~ӈ!yiD8q( :W^qY؟l䤒+K3'rrg>]&S-Nm&8V'wjE V.7#OrП߇?|Hhn?*?wcTTvU65ȫRZH^:OkM/QyZ ptH^;ndO)d[K 2TЛ\_b?ɲꑎ0zb/=;=A H5Dӿ{ 6qAUVɴ'<_W#I^gD=4̼WHR)6fCpY|gOC? Mv?{#uY='*OkCmDŽ'Fw˖9Q?NTie uڽ+ fn׿!G>6^9ɡ&?a.gˏS{8CժZ+FtWq\||.Rb ˖N{c3XmЕ.cis9phKQ5!ORf{1M_# 0HTKmfkdoIbt1w%-򦷶߸uH5ү+?:qde'ElC'2өhP@mRR3hP#mV\˖\~&P 'O),?rɥ?֭{=L8Y=奔ːG*g$y5Oߟ]?sPģň%Ow-eM&k,\_t+ޢ2Y,[}>?iOפVזV ƹNut'Vޣ7Qzߦ6g<2d}#4fuk+u🖿x]cێhEEOMmzߗ\oZ:3}Zh>u EF?뵂ij>y?Ji1_}G%[[]׉߅|AQy}DRf^dBSoiW:Y,sF1k_,[TWGw6M8|:Jmd 򧺜h欙!̮9pTp#s82SgGхw#r\{S/DMQ&ax|%6MmOy,]='IJdkȏN8aNV?l]1S/ЍM("UN Mz_ĭ.\_v5XqZ9餓G!\"ߟ:s[EaZDIg){EyW*x96sa+Kp#ڮ?8wOvUV1נIס7wj<_h-I닿,~>e-}ˮJ}:Ny*G%::ˉMPGtWFfDS+Ýʩ5 "DOQ?p_N$@gK6=z`]:rF'j.8=&|4Ԉ]C}|Ak%)>WwOݣJ|syuhEL¿/-`/ѿܭ,vW3!s @eto[$A:-m, M&2|kbue$C:#A CW?U8l @{ hgi9ի#RcɁL ǚv=}SO>?.GM*B}^w_9Nowʫ4= c_+:g6rb{0V2vC H=w-Zq@qgAcIDt˚bq M ^.wۨ 5\Ob{n?.jd}w wNޥ|sS`ZEi6 9_XI^7v8/wxl|%N߼s5PwqWz$@+*9s>s\Hxi (_yEWgRNL.s݈[_'nNvr)'i̺ZV ${2MZ 9/G"y|z?T#^>>1 #Ul //>??se[!lJ?+^Uz0=K|gE?1F+GE"i幻y(U_s}_ze X1'D;)p|n >7ڡ矫O?n*c]SmEk/*Vs%rm?Ҩ)F^?ˠP2HT{5e!*'qI'zY?mwۿgջ>6sLΙ86mܤx]+t]7^ Qgi"5^#YJ6Gsi~goHw^9{{5VjU ?^ ywLFc2h?>WvOƓW[Ggi~A,Ưލ(/z[ٸUvO v [3Gj׿+EV :Țsֈl_-rt `+3u+Ul'_n:3Ż٪Z x?DߧR=ȟQ</yi/lxa{?O|eWb\3=)v49|q݅ѓqktxEz.ا@^w^v?+K]{i?O(? f,uy@IDAT"Fm^5˫XND*_ēZ+Y#-[^sk/X:/xG?׿.TɀW ?!ϟǟx"g>&)ZM#[= 2ƫ5ړ;n>^y5necf]|RǷX>>\ s] 9M'^sw#+m< sϓ-3Uܥ0hs;yG$?_ItbS̞?S'wL¦`bI_7؅ Vhm< hy}+$v] RWCUw/*&JHw|]5 3'm$? _x{A ގ>hټyS;g^E>ȣI귡'vBƝ( 9y-^b+u᧛7؁w_o,/+_v?KP=~pZ-%db6,:]S/_8H:?0zMmƍ_rg>Sٝ+]m7]DS+lD! @ĠDHsykq1R?Ia"zSS^gӟZ5kh ɾ7j0ګH<[m'ʿpۏS'o7MLٶAXwu}3RO<q4\:sU8qUP:/v`Mqp <-?f񡹊ح˒%KʓO<8w}ƹ|[oQ][3Z|?_>sa7PY}NnatyS{ p}͋lTSE&/rK = i",[Z*ϲ{03wn>L/^zjCկ} kEwݩ(~rхkk?ʿWJ֨'x_Po~v>'A}mA, ߭L) dE O0jk,a@Yf{lKBͺyC?yﳯh2Ű$_K/x2B$0\~z}_n|?A^Xφ}KN$CTNQGz [Q~@c~'}Doƻ{[vWW6n7mEˉ_O_RsRQ*}og-uNW Cz|MC~cȿNOJ;bFU~S7G£Ou^G/y祥 /=9G̲t ;ԓ;:u,gֈqLު~~}r?p@& }{||F;7>͝;W5yY?m?!WlBIjt4r]I*yZ't%_?Y fbb .F_Xo@'-FƆ^Pvyj;/a36 ccF{Ot 9'o?1[:,Tmm8 2'𢃡_^;6t0<_j+Ds+5XK1[xp_1cΪ˰2_ޞ-^"Ў#N n89˳1"ut>#4-XO?Iz;!K,#߹s}2 vmTߎe-XyoD;~+cε2~1jKyAǶm۴T7o?<J ƠϪr1m_Jb*\F:aO>D^bwz\LE/{uoi% րUL? @,nz`\1:x#f7VA>Hѹ__hN w5pdq_U(X?\8)"LhKEM H= [_/G#)BdO;G5Z0z~,=)i Vy mJA[ ?WBټy^(- twz)A\1RgW7b.7,x0q5*".WFi^sMؠ p nHe??gH$4i & 8&$߮ݻX&)_ -Ь6`'J?/|jzEnd?ŭ `*z En)6`"BNz_FS+_)d{yWD|/ۿ5X&N*^l+}F+0[F Lqf鄲:ռu-ߠS/v8e̙; рX>.m'8hRNxHi9ҏGB|)%&mF?bz@(ۖ?aaͰJ1 >lxhoHZ_Z6g^|3:--f#_{hT{0D{+!ĞrO*鮻J2g/ցR 0R__ i4^P U#/HhP%__ϭſ+LtuAw"rnҁumT ?4JprKz'218Ѫ9rPZw^ǰ8ǍMв|gѢE%5abCeihѣdoϴяv^ȶE2b[Ue>m FR3w4/Kx1y/˟<֒ ObL5v=߃='\9e/Z't+|PY1/㟅1go8JϘvd3+׿hJx6`_)ÿg>)ÖЈɜb ri~nq[M9pN}W7 M"nw,˨Y__IM%L nD 'Sڑ[^ű5GV>%' nZ,vWŋ0aq*H߃I`?bNNiο+u~7MxoM!o)_ʟϦvϜ9K6&9s禱X=m1]D!{K-olO]L?P0_'8I=EwY]2@x ldw~?o#QkmL+'޿շWl8^ K_PV%x4l8{] ype}#.k1G`b Cة^5^,2C D4h(`h}l嬵ݍ>"O֤`seN |71ˬ\޻7#V;tRd,]ۡ.mpD_z%r\GaW(.eO]5|qO~b+Mdȕ/Ȧ7P/`#G"(N Ņꮑ5#ѦM357 _=~X Pv܁tKؾ˟'A+\_T/0ZY0k艈䩓~<V%S쥈`I( hnR^W~ԊÇX`Zl%5IeV;63#̌ؖ[5^d\,oj ^W"2NaK+K`3Rj-]JHl=˟}k֦;=WYNK-M-@/ [悤2P.E@\Ɨo*&%؎5ߨ% $A-7,ZcOG+a"M//(-"\V|9)t6W䩘PS ŊKKK!0Jar!:|"c?uۣ2!*3UĒ$v_7 7?B"%i8A$D$^P_:QJ_L@FR6΂i}jE"?¯ɿl+ENT-kEiI!v'r&\-v87n\]x3g,&7)#ḭUC։\ڊb"_ mߏ-JK^ZTYGoM6 v;K,ŪUXLq9)@4b?ڴ uIsOhKLE F)m۾/YM|ZϾ' ߊ]Omu[6+nO=z}rŸ<1Ha& 8a\\%w"i> W@x`sUX][t&{3eAp/ڣ`T Lq}qZhS?$CdNZڟh$Dmfme %TZdsOSwfcNGЅFZv@EdC=n>?{?Q6)_?Ճ?aa\/={Zؾ}{:Õ"AK ܘ8*hs<r#!idaZ?cV̱MVLoĒeKC?ylYDs߽ٓH߶_3`_C'Ag_)J>{#Xv4$xxIU $7|'`Kn0!yjR 8 &%ض)& pjIxY)%. \yt q;o)[Z#w@+QB"%ASkg}N"yeD),/U)Kc% ZԿfW7~9|;v3\!¿/'1?-\[Luhk JBr+ ƿa5 F}i!ς0ՈUV7C"NÆl̉t۷nE*䱾9 X O[L,!^bˢѐ1aVG$2B A-Ej#іX^Ƥtt?ῨPȕ4 ac#"RR0L@~GµlRg Մ+?_H-2#O64H(''Dvc3BNeiSt̛w|@xW.HF\aM,&[Pǿb2gX@c3w3ҿ&ٰ+Xƿ}ێtJvCjZnΜG VO~%M8i&!g_A#U+ Me=!1 +h%`n&YZOfU$n\㔫Lqǵ(Ϙ%}O/ٿ1Xyi;0N&釿݄ǮʼnQJZ?whixqn%G'.V4 K<7.e!" 7NYJ[Жϝ;_o"\l-z.a \ 0vfX"i6-MoF6n|ylD_޶G3ӯj,4I#WSڪWhYx+'4͉`xDv]CZ?U~+˖/Fq[(?! @L,TE:!Ұcyj5zᖆ.;%lT)ʾ AgM;٩W"x.(4P6gyCl?aVX/]jـ8(slւ.',C \ }%?2s@6\Ut`!øqsOv63?٩4y3=k)\G88܂W/ϭ >̙2H80Y P!\!?9lHSkyrVPz cG}+H!Y[?Eo%ѼV> ۨapK & i@ p5h͸3:b[C<_$hOy?t!O'5{˖-2gӿ[o%z=)s JK/Ԗv#nwEk : ʕ/~/bpQp;[_mp<[W   yȓ F(-(SF,#o'_JxYa,Fc?GӴ< 0S=qgI ectsZB`@*M[ad(-w`T濇oc D~ ht lGY\a?)؅&^3(9mG2 2~ 23"*~D]%V r! Bi?W-yݞK~ْi^e%K빨׬^8_QXWަQegAt.vpb C%*%׿_GԆ%Rs*g& f|3?a ~ĩ"(| AM%/U"'5ʘ䵅r?5;n'@{\!Ob>?AeIښ+7٣#Jʟ֔ORJz "E%[*/p{F' g ]vJjGex&JXw qdHN~97?8,GiHSo,B.[/x|kn91Nȍz1}#ISOswEpF#4?PK xtr3AgS,QFq>i-X[AF]w]ڵ{b.1!m۪[mMal(YתLYF$+ }ٗo,ţOS!N$uRc`>^K_0qjDD~efRљ)׭9'j7?BV/$?0f &G=_UT?$kzyJ63X) I]A*Y^]&-a@UZk3*Kz$wQgIKB[@!?cUO5q/Ohk&M: & mY#60LLvM{o^YmH3kJu/ $tO!1ߏǸ L2)x Lxp3RI%H(|Fyl|K:v, kX.,'~! 8RCaK/=j=h\ǃڤo~2oxAelڟU7(?CT(.2=? kMN-X2˧: t_O}:C>V' qt,5;px~ݣt4EMC8F b[?.Qrb,o?h3_Ҕr>nh=_t>xˀv_8u]p0u˖-o4jcTZƴa: O#GVq'd,YҰ@?s:#Gg!gu*q%g[i<7Jq l,?/nh~9/_^,]h5 _`Mݳ@ͷ܃p/tKk-9 %TJ0Yj?y1P&n҆iɢeשp~%\b.DQ%*~淋kYtS)<M&jg~HsfJh2??UN,*|k}{1f DaV4'_7"<QU[EtJQμl3y+|y\^+ /_"ϗ/NgB|*X*VvsA^K/ID9('&dFGє Si[#hbYbڢ EKEiW,?n޼pt6v?Q3f<%ƒμ4c<%ω6D0O 0O ?ou寺'Hмk^4 ȁL^($̐h$,y.O&Xɾ j۶^(~q[h+,$dX+'^~$XutZv-S)rboNP_&ٚQLvM˜̌P?˺VOc7_Կ/O7o?D8 b ALfV/vsXC+ۛ 6~y{LuXӰ΄*Sv#S2!п aZ[폵 @IDATj' r|-!ijЭU./]o`={ (sz~^g?@xnp5kϙr8 lP/M(683^ם>Tu7a *2QQYyIu׈#ꀞQS)S*-%\ M#5رx1#B|; kÏ* }]X~;ޔ$&܈|HYq]go&JZza%کLwYf ~.A#;F c eA"sp2ًl쒫'UBCVr2Y q}CL#Q/-~n0~nWcK*u:S?c& T˖/g~|52YS^oxC>r`_tuEu{] c`oqS^eI4u;:⪿!#Ezd;tіWh%TG`BDQ@',?>UUbߨko]繆mj'7[iqrFԩ6I')(M7ތ4\X>X_iPJ7/Z8 Dm-W2!b9fL)@8@X#I $|V''l1˿ϒbpKEbI:X@Y9rx)iF(Z?!Me|ʟpڪLR NCr@/?ϬN?ȡ=/kcaJf<«SlV[G_.p@G՚&&kvQ~2SLY Ri8yls'\IȟUп\ E؟ŵ6jGn>{o#xaʐ2%‡bkޔۿ<%AynZb|5>/E+W/p +y!{Q)˘ibd>e4jdEZt D#yJXWoϻd\zҨG ⚌6?W|1q1)5j^|!B x%:w3͝; swAr ΠOif6o;;SVmgbPxLKg"}/KϟdJY#= GPM2[ϗsgNP(1mfh?|Q$k ApI2W\eh̓-Y&.%zlK0P9/ c?֭,N>4~OyV+o%U/L":njI_Ŷ8޶׿&?~{qR3zHK37exTƓv3'KKU OKHXu߈)Rg''Bjfh^Tߪ( 3'HerڪH _AȐvH0iB?2- 2ה?'::|,/18] axS)S}cؚab(FYz^jm#oذ[k~'&мw Q!:WMx_?ⳟM'aG]S)vڟQyMx'Y_TR{O> g.'Q8@ 5}>Zp,@\ʁz9}:6nnoƴ#(oG)ãwС/i} IϟÄ=`(|wͽ}ߴ%>OYf..ʆ boAȀiGgnEߨ{*AFvY6\ςE;w]hՆI3g4dX֏%āq6Se?r/+_ԿC` //!hq^ %.j&ldiLjr@ecggNw:aP@㬴 dd%eXR,g;z|!zE M$g֌| TlN嶿7|cK]`\ܹ/u.K֬YLsuM Z_oX`>ػg_:x_) ǿityָ4te/~B௒.Ÿ]oH#1? )Z柫I/KL;/[_g|=7 xfm@kqo be @VccgϞ>3O7z2~**wP')=9 Hc ?0ukr Cw:5ѿбvg447r^6sӴӬٳ7$-}LʓZ_kR\Hqi|Lhj Պr.Kuzլڢ\eyx l};vn,6zN֫_5e /r7߅>\En,-o4<{;ߕ>O믻N"J1V.>r3Wb `+&ߜ]?Ml,p7I'FFJ#G,\N?~'` CFؿ&4(Nyq0_|N_ϔ7v;Nc}wa2N]:)*Ob'wevcM z9uoݾ [#ҴӐo(^Pކ->&M(<#0˿KNL@z#| C?pހÏ!kSON[d5}fjs˭8N[UՌ1Ym$tAu!1l,&̟Kqx:߈!*/Enpo܇U8|}kbďS1m&n`8RN_R3/&'<:vo o]6aV,aXω :5K/~Zc:axBϷnۖ.Twj6bO۷pэjD{Bs?./;8f djyz?{0i!IsI}'4l/^>dfIst?imџoY|a{G*oj3jk Z`'^J1Wޡ|o$'gVpk/4ͤ砏/Z gE3+c# h݆8fzn]`"ƙ7`_k\OuL;vlOsb|Ą-[6G3HR"7cX0ZXVW =F^Oj~-#২L>Ԯ#d8|*q?ȎB툲k|%]5\VZN<:Bg̜1èhn&'#ۻo/۠  xYO|"߷O%qiQ*Yq$zS[LKO?Ah8}ĩϚ? ldp q?SP[+#R3>U| qэp_R _ƭqCFC^zؾ&e*-ΟBi2_߀H=Z/5_m QMY3wL66o_zuJ;/h1RLsjˊYg#"tɃ-LIS ̃U;XNFÐRd%tWeU.e„vSVSҴެXfM$2!#pj?K.(0~>W^i!(oj*O gv$/;/je(X-Ķ'n,C܌_t:yz'K1eWs;"ۘ#od7q&awɀ)j_wOYωwN#oݺ5[ݻҼs0 ,V۱k'!DSǿ 0Q_P/تW藍jܖt N3Hma_X|{a= Oԟi|TϩOS1vp'3o"&085b/?_4Կ*~Juh2z/+F >@?RIwu)ZpT5Wq.&r8猣Rlh1:Hze,V~ i7?*! $WfK(?nQFsu_P3w.{?.a+ ]IAur~k^Dqᵥ#,?sLڤe_)#p^/tJSO:Jkʘu4(%fWe!m_'mLT2(qq{׻ߕ>8J+9")h^zcqǂ K#LWM*o%KvHz QOR*ɿYtX/8^,nc-2RxblڑswB?~: ۶o?EV3<.5YOedsEo0VYBI,JS괩ct. :$;~,¶ֿmEOy _:t IEUڒ Ad?z?Iu+Դ!8lyi"ֿz2 l/eWy&cRo4+6🣌eځKgqW`΁Ӌ+_Lǎ~{9Z￈mbG}(M*L@ [o+!YC0QmTHVp\ ]ڸ /-Y `:6n/\8xo~]C? avg?~ϕ&wx ,t)7zQV紽ojׅ"bViɓ%g9:~Zik? =l?S ~Mј8 ?BUMAy ~ߦsjc>VO^v_WE,GB}&_oJ?Gڽ{gqV)N{衇җTdV0kظX+1хJ:)WL!g%c[` 6?tߦGA2ݹcWW~sL p9_UwɯPшzz8&V@2ǟ/`ͯ]^>s:뿡fVUhq'{BH@ /ƿk+2_aQѤ,쏷ahLDj  2A XzA vta Np on+ļti.c=nsqG, 2hV~73-[Z?=sw?b$Kp&vx_$~*vi(Q#5k6ní.-M:+E #TگaOO&|UoGG.9A >՜}eKQ t?ls edgLYܮb7i4b(s:k l;XOׁ.q"uVOgbK݊3GaM[ﶤc7~6g)ۿoaj [V'wՙ/0rĨtt }I;x8s;\KZ֋qȹJ_nZk@$t^#(Ch 4$u͖x'B'on7Ԡ@#'_Nhש˖ڊ w +Uo}VpU=l5{+*BCnӈ|rI64HVcKIA,L7aNl(T׿r/;SxߧvL5?pdlv"񼻚S ?!$WRENJ4e`R?-0(%Ke *niH:tpYx=nݻ#ECUi8>-_zM^yt;ɢEہpgߔ~ѣ;vu1/b3?ÈxB7D?䆒Mf(o׌u@ij1'umo o!3SȎ!~=AlqcQ' ,C.! Ou /O_8:ڟhAc} n`;$^J@32'{?wiaiQxȶ6e nME_Gֈʑғ+l^ؕE淤ag@dzJ*~-KR\.ƿW0VWp1?E/׍?gF1Q5ԩU#ڟh CW!Y1d0N#ha| ;s/zxtrOv9a담XksUq³.j~=%,YJZ)o?"u_c蜰`;[= Gu/10 ҥKF rV̘>#]wu&|Mk׭/ڿWkٙn+lw/lvlw/l75cl7HKiڵ+ƿ$TgAc`O1yOAxMٟ*X2XK .i1c\SgMե\Լ|?s)#2hlFK5 G3'/[hǭ+SyM7oIgϜ.oS҈aUk{ҊWF!%܊5 a&ڿh~^{ipݗҊH7Iw`z+3)/9$mڼ)>{V0c+bVDkyG?Or`uO(:h jZH@ `8KMF" 0qxH콦$ǜjou<@eIFT !( u#JMB F U?n\I)`'ڟhi޹;PL.9V,=hY([\ի+a ?wޕ.?K8y_.pZnCk_1㟨021K1wx¬_FH-&2+tClG:EJz&7P2Mu\\DAOԝ|&g4!E8$GT*N_?S3"8JC a$KTa٠@G=n۾==v4?~<_"mظA&lp!9sVȈZ1`c۶# E>LڰD}ip y~䉴{" A/OŠ͋'?Άegn!4%0aUH*htc:zep6ʸ0xVa}El-m);%8a _Կl\x 6hQJM R?E+JIi+9E pb!b!b@K ). FY5^EbŒ[6 )A̢SIAyPքkFɘ>?-1T8FL(ׁ?/"Bac:F3_0 ?!ٯ@D??/E75101/lF}b/1=LC?Ul31a2:A-X284 缠E`kcЯqLFѴyx ~eAC`0CB0{Y Q>a#? 3o`"_^}-^p#6J&1`:_1"?GK?[5wֻq|u49*+BVV g2 /jl90R伖:V4&CrՆ5_Ѩ.E3 Ooj'rۡkwR- ?G/s(Y'U7J c!1cQt8΃W1dMCԏLL^js{ K5T.vc'y%$a e BAjV?D؟2jZp+= eEas/5%/v+O5ނ7u!ՄEGOv*̟f8prF31/RKHAke˳zEQbb.J HCN*7D 4y%YL{<Ό:lX K!п4*4aBؚ/ڟ, x@ABC~km Y%4(Bwt*FE+_r7_?,TP8OXF?ug[lE?H#fCӒE*H^ս8f Ē\: hJ/<c&~\KN1Sxu57tm ?Y잞FR\C O)yHv%)MQ&O5b|'@/ C_o?iCC+#s:(O5b|13 Uà 0 !:n_1_ˇ/Y+6Sﰇ[ {5=Ӟ!9OR6Nې?%cRB/k}mgGJ(EI7G_ ;Fw_"lG&0DWO*.Ɇu'!nHUwGG?\YZapK & iC3> u&HK"cn)7.{wU7t7^Thi'$j_I~& (Ƙ1NMbb~16DܨQfhxWސ ? ¬&Q0GYDhf|l!?JПo  "Cϊ Nj J͡(¢TRXSP1<@?1"FAq$ücɼϑWgފ#}9k/XEQ׭!v5m_[lfْdyyʣC1-N{ʁ{3%v6_JTpLt@&DƧ)@?[&cny2ѣfQ/OOOOϨI(?-+0i %yB_uW4Miɓk9v*gڐ ecOvYL+imR쯩p{1i!|bɩސ-bb: ]h~]c(+e775KlE^Ҳ7Ԋ&,b%'DDSC?[XxeiVPb%'dnEۉV[L(h_QEc0cdfj p T g<1>ğ2fM?PG3K|׾f3D@tpp஡LAo8ך"VöڡIsϟN-YD/[U'?𯌢nl!#|@u)L)j갗?,CPAua(oh$N\-p 8-d,8Vp#L{ [<#x?_;-l'{Eg/IaбdwkP%L/ yej;5ޔ ώm1\ϴ3`v=о0BmmO LJ 3!c{iK57`rU`hn,AGO'ì.?ȿ/KF $=0X]SĂC /_/8BxҴh"٪e 9üX8B%z&.u]| ,gv3`oxkeĻ{VmZQ!mnޓ hğDP_2 }C`iT<56akͶAyO7naAl PA]Z 8Yfo2 mC#_䟍Df oo#wi?qdb7ҔU0 o\V78蹧P~`aہ}C'&V'96O„ b1th@?_ QG $!ooo'7wSE/ԟ5,Q6ۍz0oxcɼϑW`@kۺc]el*5S'Tu[ѠmY/>lBlHy"½K疚jr⏼+l诮2!_Ow"=%{onmdYApLȿb|tq+hLEU*I*OOOdžPS$c ŒfKf}{8w3"IPP9qu8\-\6"Rgsll@IDAT~{Fo?ǞZ<%Z"d@t-E?_+05G MgzLY+?Q23RDɞ@SiEFSlT !s ̑)OIٌ#s?ukf35s޴YZ*킢FR]Զm*zrb2|c#Vsjge:;g%PFOcYS$ M 3/[1@ȿ?cWI;!CBF cdKcoo!&a]9g?b ?__M/hBiHMM@O%)%'O-{BY֟cIǺ=>V!y5hɒ:eeQ|a_?_F__tνػU$h?)F?)էå3?_Ϣ7'7/ԟ5P+ˢZQ_D[ 7\CƧ.ĐEGoos3)t,鍹ec2~HPĶ|Ъ֑kwU{e`kphǶ;p+?/B~š+RFH7ߛxj a8T$0id "$&&J_u/@<3>aMds1k!?SAb/2R#@ǒߠ#/ϯ'+ϸH@1ݵ## '!L›s ~jCfG{ggq4[/\gGs+K)$j诒QOyŌ ?[ WX;FP^Ԛ?$0y pmeK?fKf}{8w+)؋֌fL46λO9y^Wxm}mgg_7t`&GgMA՜+\7v#0G_Mb;b?_I'q&m"Q30 FAMMg?7wB沰=&tбdfc]i/ki 6.~ h~}_V_۴+3h?K1d1e)+yA  !?_υ? _.9VCJI-\ȿɿɿɿ͓- g'J;`@Cɿk;?P}چaбdַZ7W`QGHwNQFTn;4C=cڪN"ʜZn^#Z`i5?_GTWKOD?PdOF+eu5yFߩA1/<.OSL@¸H wf͉b,[{͢'*a>rwmѺ}$D 4"2WP _ `& "4,b)ȿ?PIlOԟ9PVZ?Rt>=u1hбdַ苳`ڞY#Vz VO8#K/:Sr*굗m٘ýKVF:gVR$vh?ϤGP/D ?ȿ?ɿ?PQ8D-VPb=3T#?EҡOAyNjE%7^2{^V7xMQw9^ߚonho˃ˁmi?)L ?’9П-\ycC`?WCi)CB@ /_4?fbsBg2Ӄi7U CK!>hбdַs${ƌh:ȟ`b$D{(jrՑ[2C5Ys욶ha!|_`Sl{9(X\e+9&PXlKXF9/kG6 ?ԿW'1EgJ6PR1O6K _a:_13UYj_W$G%ַ8R|rLɳzɁgvlrvJm+<>,k.ƞoŗğmBE$Nؑ._ `Пo/ϖSCDcB/ԟߨE#sZvtбd+K.莚-]ֽ@0mĆgp xng}-*ğXшE*>?__䟑w¡P+QMAKO?C̘`xD q%7AAߚ`c2cX2sܥZ6ɗY-yM^/E[=X jC^He mێz-4j'Oh_ 4cj.&1zkMkПFAAt!" 1ooԿQKiPX*ĚKkƤS}^k>^C(x0(?t9CLѬQwD@Q;xkMSٖ fP^=^}`o<'WS:-ư?ϞQs#Q CEXPDcp֠?_ g`躻OyÞ GhLOR1wbFHW+0Oɿj /׸+AѬcɬo1n#n -\a]Sx jߖ ToɊe9_R)[zu>xWސ ? ŞqZ_מa _e Oh5ZΗd C ?ԟ?Q$2TqSQ-?.Y"@ǒޘK]D^{4*JXkE:O4V m:BT'(YvdC:Aq#+c=*(@aAm?_O7GԔGL$zKxTIIIII>%tl@i[zHz &P>clS?+a$-ddBE7 ]JY/B.ci| :Dd XLE?_̕/3 SEř\U@'OOԟZ>EIPKM- =%sA[lcy犀3us ϴ/A!H.]jޢ{כږ ޽L=ȹJS3B ?eMoC554@l ПM!g "L]%] 6I-ɿɿmEv6O'_ghۜJAI) $<g"@ǒޘۿVdp2mX2',ծ8XzVJ$xPMBz^; | d+n?o񷔃y#Kf`@e}AeZ @(xДK O/ɁS@D|SCgF_$,)P<\c\IIT jTCr+ϨStIZFN-IoK*~:Oĭ},N^h5q41?@_Ai5FQh,d ٢G MBpG{ AiMg8# CAE&֏Q M_RRԲ*eiq! ]A0Kz@@@@@@@@@@@@@@@@Xr6:pi                pб,ѧm1t,@@@@@@@@@@@@@@@@,cYO                0cX2åY"@ǒDA@@@@@@@@@@@@@@@`бd7KD%g>mcɌo               gK}#@ǒ. @@@@@@@@@@@@@@@:%                3F%39\%t,9Ki@@@@@@@@@@@@@@@fKf|s48KXr6:pi                pб,ѧm1t,@@@@@@@@@@@@@@@@,cYO                0cX2åY"@ǒDA@@@@@@@@@@@@@@@`бd7KD%g>mcɌo               gK}#@ǒ. @@@@@@@@@@@@@@@:%                3F%39\%t,9Ki@@@@@@@@@@@@@@@fKf|s48KXr6+g|m\1|3?;kMm;Z~^ΎX ;Î}iתaӒOhLo)u6R6Qqi'Ck x>4nzDпĜm?'>?俭.D|tL'>"^fOT'Щo7%s'wP>?/]_Tzsr%%g,<оJMGo0nB~eL$q.?ge?B4yH_}ۣ`q[:RX :+ȿ/Z#:&Iug)@WOɿȿi;O^^_99rSбdSn򼭺{=N6+TQlOd+WhHno,x6 bV6g49V0-t,ą5i~_ U⼶_ūdHF}k=+ޟyL}c?A;=@%&*AiTSoa?g*DD5z \͗?/߱BcORדRPWO%v~S,3g]F4ϫAElr_\YךEږhXǹ@I-X :S; cT3a2/g'ޕ?tNCZ'b"l[->ߚ3P05ya+*RmPP!e<^%6 J=%XrJ@OLݴ'!}\Ux7_>b}=۫m>Ե6G*?M?oF`Ŀn N7. y?#?_yJ@3_u"şur/Qki y?~g/<e/sSp%,V8T1KLcSJ7Pqzw}v+hc߸ۨ XAG~Ff5 O˗x/^q bm֙[!c:,y1W+g )pԟ?ͫ2ieeߍ_>.kbc /\a4lr FcOp[ԨUx_?hڬ^e ߊ?ȿ1M*#IGaBa@oN{c%Ԛ.xEZJΠqO較k4wb:xJE/c'5(WXT` %'9 I8W,?Ø?|/rr-ƐEhuV*W(-%v͜Kv@)ƿ]3g 7;D6Д껍229gZFF%=G_̭~'oiaCͧJĽbkطr7b/mǼwЖ$گ \P-g7 /PPqw&ۦ&"M_+F@E8!nE&'Sq(\9x7Soarʑ3S%Vj ?e;-9XMLzI@Wu5VR*ޛ{Z*j9#OjumS ϣ@޽Ўr&^oYWvM?I'r*jd[&!MH9N ۖMO6/=XdQJ%0S5G罱m?O?FK<q7C S"_]\lYBձ_^ݎGoI6IsB{W |'e-Tz?o2oM1/?Pr"[l3}Óg(UG6װhJe0w3:矟_7G;PjT'"OokGdL+! ؾxW>5FebTaKN 1TgCVioTj]yMm^-.= uH[?u<əsOiߢxH\B#)b)7⟭lGo }ς֝A7e!'/cc]<|G8W1+ߋ%3fy:,$)Foww%؂]? f:GX2ǻrkjI_!ȗJO}5,fk|k;xq/Rqm'o1> d@h,31#F\!>/0 "?#C-v  OuQw"&ֿ/D%6 S QF /O?w^-G}бt5Q"`M-Qju/eMѭI;C7y}O@Dx4LgQ7&g4gB)걿Mn2 τυ߉x:!A@elѿ=`Ҿ @K;7jh ?8"8?Gg?g 󇰃!.xjg5QFsD%s+sMqlϼM6Fˎyѿ:^u>ܾ~ַ݀/zEp_9|G䁛 iD1%4מ%gm<YiDOw?d[6O/*?zL[蟦NƊWWOCıM@#'???#CO :67Hb|pOGt%a֫~~sp^>;ThՆt& 1qa&s)r%럱|ُGu$5v2 p^*uȿPHP`0& Cm(obhR~?La +?!Z=ټ(bNγ)X)d7z=%DKbp!q:L* g&p׻d24doP뼟><оJMG~Qcݓ<vG&?/D3`=jBAZơ^u̟OK*s@IDAToMWv{FazbǿZzk?6EAq[n9綟rלovD'31 /./бdOBS!jmBlIvk[L#55rc>JVi X知 2;O1dyXf@  c/w[Ҫכ^¿ğE/wc=$ǘGLC$*υ[b9_ \w;19Æm N?5MdJak~_U]sS,ydײ. =zM{%5o쟣DQE#TSoqKT|;$7xۨ::K_M_!@G9㖨?RL"<ǔˣS$vPGc_KY_"@ǒޔ^Ҟz 1_߈kߤ_ATѨ3Nm%m[XSL6=I^v-^^ 78x?:T|cW8,Et)&?/N9z;\o,wY. m?ǒe;>uYYO'c+)qf:!&c E%w#gwӛ;*5sZ KtV9u +cnZ׎>>x ih1N'?E>c",sn3.__ s3f%_Qc?E __$yɿȿQLyOކ\tz V]&9= NI~Uޞ3'o1.#M^OeYFkWiw/xaCS'?|X>[Sf"/CHެa)\ȿ6_#rqxH:_?3ơ6<X]l ?ԿSI~ б@]vn HU=w='RrtFoa&jĿ+k6<럺迒{_?FfdQo2/܉kx2o5YèSL?Khccɜ %~P 9$y&Q&O34U~B{Hi*0:r+zM ?`Lo?;G#3aS`_^+MZG%$NIԟFs_ 3'r?؍ M ?a͆&E¨T/gw:LB%BvuܫRRhKԸz R?:}ֵfѻDb%4q.DR>jgBԎ}7,fL cw%e=g4]п+6Ϸ /ԟ.LmM)U ߔͯoTuޤ=hGa{u Z7.x6ǂig1FP`?Cu?vn7AtMma楿e?\a_u+?V&kK} Ǖ{lJق߾h \c'O?LZY@4wױϟ˚:wm71~K@JJrX:zԣmMΒ ƫ_z`' h?W!L2'%G"?p6W@BMaՖ?G?m]mGS@3}0+StCgE?_s3\c\%W^{6W_sf^#?4Filb~Kk}šn{ѾVaB8`m졿hv ӌjĿx4?wAP?ğƟ)j"W)L &>󇪺!" 96K6Fξ+N!z'w?Ɏ#J{LO~:G'kmzV5,k:؟ W ⟜|*'kDV3i35j^j?6W,m?Ge?Ǫ(:HQ[)6[nQ Ps|Gy„RЗ総u :^QV3h:6M]X'R 3k_KXmM0w? DB_?0f~ipDdK1dc'Z`߸  s?c o3' .}oy̙45&M=4nLY;Ѭcɬo>s߉yZPq;MXs1%k~dgj>jYM?t NsrJ'bpe?% IJ]#/l~:Z`F>{ywD_n!^˻|\{;n[}C?'L?9Q1W l#b{rMԟ%B8&<wQ${.t,9]mA$DXjbg)rqcKYgtoNM^S>о p#?cofԍf Pz/EӼ>3s!w"ެN%i-P[oo+ R΍Z:ϥοE#( >OgwfrwwCu]6oƙdR34O~cw_{}'?=k95rnp8ssdGNo}ۇ_1~aua?9ܵ5ϽLxmcRSgb?{ 6߯0"}ӛ.\[TPmI8UwΠײUos{9r/?Bfǎ k jdž+=7=S񐬠nxS24*~/7|׉k8| 7\{]9GOsxqexS?o}#npϻm?.\ÛOܘvwOrˇʏ>خc]'G=lžwo݂1_߻Wax7CZ}P6|r_:0dgIR'vOK/?-^~;uןUË_ڻep~no7aQk=Y|{)=kxKܾIo2}qc?G-ߝ :OpG>|Uw~N^}7z?I@Ot-J\coj7aΟ"ccp9GONJׯ? |W)4}Ï Gof ΰ'nsn"Թ_ISwW.y᪫n ;5鿿e7$O?/W Zc4kX2sŅD2}AJ…*摌FJUT ЬF%%Gh_nYdҲg}K}%}wtɭ԰N _S_7 ~?I%tt'=qiV?k/ZMv׻յ[ddL'C _0뾪6Ӷ޵_7ÇR?uk/8yG?:T;|v~7p:^-uË?xK[]oGͅ$6/|soz9Ѝ^z~;ۨ቟XC|Z~8[o,9o.~2} uwy~yӀ' _arZsݪnfx'z{MZf;r7 9o?va_/ U0|_,??g^d?Ɵ>/%򎰿O/x%/B=ї(9xx?lVg=üs>]Go]Ն O{R޶K:_/(7W{_őIna]?../7e평Е_ _:*h-?/W|_Nx{}O{Q9'euߦ>W^9|_2||pg|׶oٷ??2̛{#9b8Æl_+4Ne—F.<:TEsZ(9\,9q:zKkfᄊO5WWL]iVřvm{ O{ʓ?^9_^ۿ=8^վw7g?BEC}ZRv?EcqO/+iM+2t8l5u׍O[YzXg,Vyzȟbǯ!ra]t}kfvax\]}Wiu*ghgm.=Ϫ dZo}i?;ޭk'>7S_wc׷?ڟ+ַ-fYOO<=lZN#j̱揋cwx؃o8]{\\V9V~1wqL"BUWx/&M_m-=Jt;n)}}_N _ov#?}×K*pV4 ߪNyҷҏw>!+~=WA'ө$mTC9ꫯ4a9kΜ}gê2f_wyꦘ<|?6=֛?4oo?,h-Sƿg$;izgx?[N֫Mn%DYڅhY?A~w;6 Ggॹ'cۿ\HoXXJbmt2K'crѧ:.On}|?mwi<(g~azz6 [{ۧf̉7r s߃pw}+.J3!NʿwCm?_$n ucW6(?z 9mOk&l};m~3/<=m:~e#>O^_;H [mHBr^E'{'C^Թkme]fc=}j\k`WEo}3MY(5B3}~ÓxMyAæ?qCys?ݹij̹a9|Ƿ}pr[xN`]/nqE{γ7~?r^>uң?gC? ~0eٿ`5/;@bye6i$ZbZGp͂Ѓ'mV4\Fhc9ޭ򘄓i)k_x/;N2B7Foөvƪ{}Zu.yyFsonzg8"=_yDQf_8}ݫnR^\ד_iwGy'?fa٧tϿwjŎq;?m׽59 kvy;| [߭oܒYlԞxgj?ytw}s>wsm}Xo;aSelD~«P9v+wӦX?6q].}~~eޜp{)WMc534 L]ϩ*;.qHvK\aߟ ۉ?~qddxgvnueoOrݢW /z~;oz?A.!WD% %_yz۞ _Sי T'mSf? 4|69WJC:ڿl.'c'mtOji@Yx y#OwuO!>!x^V*׶gp9rfkH_xpy/}qگIr|:-"W_m䒼lk_#rV4s[9}×e-}%Z{Yׇ_@Pٰ:\C[4,p3~:c|?I[fe-۠~׿n]ݮ!ߦz/ [oyzuF oݯ{h[?%?yeG6RrsG 37'tS"ìcɬo1Wlڕ`VI}g&zYJZj@a_ U^UՐ*$`@<{m 7`o^if}w8=n??<^ j_o)5#W7{ץ <lۆooe:-ѿ?}xYxxglwyu kz]Qٔ?ƭuQtvϡYF:o}k]O?yO_yp޾Fe/'մ-ib߻_ CI6uzRO 'vѦOF^&;D-wO=Ň?~WW.zm;o)t Z`Yuj+i'<5yY3s}oL?j~3>a댓ǟ=|N=u|JZoꍸ SXڿ3oN QP 7.5co)m~cƎu^yCth^YJ<_o%?_&}1"S7tݵ,8ǰcoª?NZ~#OpGt?w2_fpg׮;r> +N e%EU+=`1?j?\ ŵSE?+2:Eߌٗ 䳷%F~_2<{(UD_bo;L(7v6l]ĶY29/_lO5/塿Ĕ?25-W]qx<.X389JY5'W\}Pz_\FuQڟfB}.QXQ¶j`%u0ScIu5TE|HUt|dg{[jcVe(OZtҡji³k^LBHk~qs-"^8v4e2oؠv e֬Ym\s v\fla=qn8Vdso#?m߶~-l v(ݍ5<#M 5YK Mjyp w[b%*+CSKfԮj3l_8͕Æ2y~e!w=ר>7#~CYn\=7^tQܾ?~7 ;~LFR-*id8yWI`1E n}^_xuw)ǻV+$Zk-loIfrϲ% >S G0)٫{lVؿbXx۴Y;J[<.'<6/u1\kPO:hBRO ߸ L*su 7€B QY+6@c|$_~O~]~G~eMtHeq+_e[Fʩ_>/_oǘ@踟ǻ8+3-AvmۺfXe3jo6 RR7)lf֮xgE<iѢkQ'~-J<ۮV]u7T=kn!q񊠹|Nt͗ 7yЍ'%#>رKgsߍ&a=bɰBO_1(9_uʑW^sg|k >V>_q /?1it cI4)͛6d.XV[zxp)J~"U$hʯ/yr/'}e;*WotFU/u]k 7{?D%ϵR߸QMۺv8 GM4э9r(vG6hA;s *Ou)~>/(?(LI^FYˡ:5 7q"$"+vyDGw0i<]3Cˡ]cxYrO\?~9_/7_ojRkl _HVP 'j'u 7T>WvS/~=Lj 6wߏEۑboy N7+T8ƣuyܹsqimwWLkזhǍq_J TM_җw_1GcƸ9g@4] b_|'H[;'Ysd;wpˣ-s|X"q0˿"Y {-I?8>qde&BQ$}@8_f=vc>{r?$ fMb2ʎh7n˾y~!˙G:G_pJFPq6d F)wݠyuggW׸is/M:c?k`;~4%#W2x'B6J@:w.|#-reO81zL4)L$bhJVZѭvW}olY*"7*BgZpx3x ^Cߤ1;7s\d¯]?뭻[u8ޣ hNFw_c'hsp/_Eqe 늱]ԯl/4gԱ#dhpW KR:2rjuO* +6uMMDžNŔ.fkh] !@IDATò({lTW8qa:)SbTKUߤ%3 я/u¸Gh+c7QsgC=ŷ?_ ǭ1cע/o6vñ̴ilZ[&3Kr,_Z`IOViPKC]҉%u9w H%j(zB<϶ՠ`:UZU$Wsħ8e{g9KO5yR#0}~?}ݺkw!ݻL1 ܓdSGN%WN:taϾΘ9=‹ߝbt`$.`tvV@n )d?Ku %}N;ۖ= .[DUWF\y&>u‡&h+x3/&L@'خv7uq컗'?wAuo#\rh(6xCwƘtcI3A?sap)rퟲf#Jt򔿡_s;lEY+Dǎ$AOzfX"N|]ܗb,@Y9 7|񢤴 &}. rǸSOm.l9dju{ ZѮk[5ǩ  u6: _q3 T߀6F3#}GxN;L [qұXM[ 8 ~wPǏbtԾGo49zaP AjErRݓD9&tz]{7'|6ԝp? Ga;쓏? KX?_on>{8[C˫&yd0?r`~ 2Ey̝w_'b ̣?[n[1NQYHy䧁d?A#% K(ɓ: >c@ ?um)4pg@ijvn]+&+{v: #>k\GGŴb!N>.q/7RH?>Zr͘/Fڻ9uƇ>Nf 7;hރN4ڄz?ح !_͂pn}|(} G"̡OvOz6HYWþΒ?nr9&sAX?4W[.H+2`F-.I'ԿR8QbGq@UJ_b}\E\^FXLA]\g_d0Ky 49H+aPg?#`Ntn`.#ۧ1ȦAv >`r/bN=T>o!'ۤMCh^fx|,eۺUcnrߡ>P5p lK.}Z8M7a C^(مX˯`z M/|[?ku߆_b_/}߆{cgl+wp[g& _ܫon޲luP º0n|1U VӱN۹)&|_`ws}tL|[i'_q FG{58[{Q_z?ї^ˆRfr)s`SO l^1P[I5JNJ , qK*Gb+EoE%;T-6$2&Y̎Iv?x}ݽnI%&EĚɿbܕ;lAfgCKV2~ #lv/̙TB |)_2cn ٤ `h{쾫n$^Ű?pЋzx2f4'h.;O'F (ioc%?6TW8yJ@ShOJ_x=ø+VH " x9ĠԼ(27f0|H$0Jmo/71>k]UZXҴ5'w4b_pà%h6ΘtŪhRekْq DTBsM6=|mnw|H']?𗁓X8W_4lgwUۤP~oǶ}ro^fK_; mZMߞ0b;ni Ǝ%=[np{캳[O*ɕ?KObw!i/`PBt3|ʤ´'SqL=w<-v ?|k;ov]tC/ے/*'1S'4]ϏAbB qWѿb]@z]cv݇yXExjp^{Q?K` K/~A/t>H.R]O;J+ !vwv۶$f|UFL0m_}ԫ/mϳܚɨ4/^ߦ?v uK?P鋀Ps;ܽ3)J$]n !/'k)?㛮m-0d"m1 yl'Z(fc ?$zI"D 5+l))޳_zkڿA ^}sL7I}'3O~Ǔ.R8hj B6O.A?Z<0%A'b=.2q@ϝZ3L!1-?wG@paɪ&y_dCow{_L*Ig§GnBz!Fݣ$8)'an:JrS~Nq[ 7) ʟЃiB>~]?4üܞLMbL_Oaa=ﰍq\8 VKgQsML:Z<:8!tΝ1Vq%J"UL"r}oÛx!_ۤүp̀[ܟ?_sW&ğڟpO?_GBW`i__/["Pv<ϛ; ~eŇKϝvР@Wr8fH5i/* 2 &/2 dR>Wj3y9BM[z:_oeδ'(?Xy 2qv(vH%? lAc+ Hg_r?JM0=}_E,*SJq[=R|gӏO~2o҇_.tgDMY_XބX*Bm]],6/0@i| y\!% >>ywZNvCHCV"M?^Ը! S|VnS,g=gzԛ`+BK?>N}X>7%%>s-l !9E{(+' jND*]oJXx8+];& #vZPHeЯ)$0K>WVj\ >]I[rWF:?;`I~8պNJxG77u]z@󯿝nL[kc){ ?8تԋ*{Bߘ)õv-n4 h>`|+5OłL?؟XM(̌^\X^xLe' ^q1ycY}2c[]7,.'|/O=-p\>2MhCb\cu'<* ?U 7lxᆵWiEk= ir|֡m[6Fc<寨 /`,W<=3g!mղvXH rcx8pbgcr/&O ˵)cGGZ˸SqgWj ؙЇ};12p2=< -3j62?@cq&wq+i;=u;cO9\\=@uaoa +Z6_5QsYLf}2XM0XY`y!'[Η_ wk`9;}̜#? Y ˿V^̞FLLU=g?\x)?W鍧#Γ~1k3ᖾX|TƑ9#E_?B/omF_7AC|Ӎ01VDsz_ Fvl6n!9~38r`׿BX)Ȏ P|TOB`^fBHCiom7}ߢ}J$>q8/H:רA#{iGG#WBgz*`پD!K_~0CG*𾖿l.UGYXl~зz硿7I\CC^~rߏZari-hHgz;hl3FCN%)S>}Qg/ o)>} Y~f*&ޗ =8 //1{_vmt|uR?ܱ+RWY\;25[iO!ڟ{ Uϣrbh/][oʽkmwQǒ4K?&Ͼ=`$.mWqNs!K%Gjom RV"Pu]~_*aH)"g/#mM˹!nZJ=Wa,Ooaڵuuf2)NB<vy *[OnMy*j_`ˏl1&/\bkw}9Cݪ[Bxn>b=f|zkwՑ}+rLm3O>|yhB-3?\}>ʩJ?8 |_\#XqD ?~NT[Ch' #rW“)?l'RT-!XՕ? x}i{2vl`B\c[s+˷ޅXCyaj7 vc|&tS^Bv#G_26OOnKJk*U_/ -6=}ORƐ9?laT( ~u.t }y gL 4R?pd) cvO)T}4f2vA~39s\=<n-' BG@qd82㉁>!B/09-j|!c|7Q.7ӧpoZwtUL?1׫NA&Ez=ҫn. FXjݷ#G}W^x9wU_^}AA.Xd?zi7gnBDJ oz/뮽6vI'ssQ1Yi/_zL!( lCsd,>/b]L|7Wc-!GRgpW*9/.'b15ir|/߻(oCmȸ5nx؊>a{{ض[sdd _PNPunj`Ē/5c? ,-lA >1BbwhL> }Ĕ~$ $,d诀#n|&#+pWφ~{w>?>qʿ*?Ksˍ;OtGN@ٲXI<|ӭG\r8z۪lk0"ģ $|?b73L=CqV(?H(>+VvT?3}?^Wò* -8Y_0k3Yp~fp%o(>L O_⌺fXa9xD?>BKVߘ"UC6}ف҄Íe;0TF4~MGAA  #F:l?Z|J7? *>1ONK_?+m#d~KNisb@F~O,IߺU+tO?j A12 ʭ@?ܒO#\ oBXl>$d~9b i8t6"_r=V*$-ڵu{99ۙs%MXbۧc>U@+o}1 |18if,w#!w B'ʿwU }ʮJp&({bdeak_V+.H?"n]:)gyC3\*_&mݡO80YlQl&o;hPp履LsK>\odg|ɧow$HjlS>(L6mvG ?p6yiO/ݮ=v0_>O U_x%9}ak5*pV\wWx[|Ww7]s/{﹛#+1iw]='r[2b>w/"g-OCcab7 y 7|>0⛫iΕ_ 8O?"MaZ]3ltnGċ`4s7w>`a;z/}Xo&d?2cdv#^8$Ooڍ dcp=m]kU/9׬üOʋU/:rWQ,,BWf/?y쏐JpA6.r Lx~I;޸qMǻ~w߫NmfM,O$]bWq朠X9_ER3LA=_'z\C6Om,{p#Ϙ>ӝ𗿹F}h^սfv|M$@U.O7S /klv) Z^ic2ʷB\yvp]}-ndW_w0q'vfcw\?./\ynq 6+o)#q,rq{M,2L ?a(AD+L?#gu`ɰ-ۿ*ooGh(Z|g~H0~֡_`i&3 y;[{ǭ {XB|ꥦRq, u[s[0 VwIqtV!.b2_ B 5 mXgHvk7'5o6j6c9DlnQFbKk=xy`T MV4%P˕ N#PS/FV?w'+}H4HN쉘{]f_DpVq@zz>%v3 Pο*}\ ?6 R>5Dǯ`" +K?c)96](~#rKS;{ J 3:cɈ-;9q6oX!e+s MN( 8yuXJ{H(8PX #i%)>rCrtQ,L_M?a7 K+ /Co=nY~q8;nr]T'*F)]̶tH 7 ?o>[?@k>Čy#Y g  +U931P+!d7+a{A#+ohn„ݶH И ]*u1m!,ݲTjŠyCmKow';NrS)o>V`CNj!_yP3 //(MS]rvN/ b?ohN9`_bV"T)?K?m:d4_\#ԇ@0ݠČGE>+o<}8˄bKW㡐Uʟ=iy\c=Xphd>wtڷl]zzlX1>2=oy`xgTsd:#ɲ\ Ybj-W-}S`TSʯ0|2L*y3X=,7s>mUaeaC'70>I%FoܹǞtӾ=L]qi8vrj\V] |@=iϫ c\yR?[mz]nm{>ogޯrG %L=v=Vl{Y5G!K~ t^+iE녩+΃E5`߰!VsH)\bj+/=22FԪCG'n;<}=?#QnlM0(0gPr.=;}ߔ9qKƤ-``ADNk]S201DEY^NNf0s 7߆vj}HǞ|n?w-]F?7_&7_NO)u9_dO,CT>or(_)_ 0G)Ïyϙq?сϲħ*oW5$Fdc^@sEn̰ oir?qÉ$(HCvՎ"tsNŤ"f_DyңO7i"ѕvs[Wi8aQqd,0,|d\qdKvB i u'dlN?<-n1Ha(-\on)zo xue>{mxk`_~gKך/TqW+U%>w Aƅ;ITs\E,j6߿^_a1o ;DID\q0$Xg̘H?qcQ5wx[{Y1n_$>1'>sl?q$/B ޑvUS. $/P5`Ouǥh@>Rq>h'RIwLb*:h"~'xc;5J uKHOLq'LU<}=CVfb@&%BfL8BJ 249Jd{1?f/E|C \M8IIB4m~`ߐyDg7, 5x'0@xJĝݶ ?x[+>~m"!0ލ;$ @lV38/ ?@Ւ4M͚j;ursgwG{N=|x\I;qg,D"՜gP\\)3egtH[#[`$?8WbǭyMv"%I^܋&JшdcT%LbcjեxOi,xmڴOҩܱܡԩV;U! O:}"H _U 0TUi$_xAoNt|2sl @lG/h>*M떭|ؑ(x_3ML PS`>:ϛ?D#(_cH<6PG /'Ny"UNÕ<֍b ;<%5,苅2/"(7/dJ>R4ku~3vvɝt<|}ݩ'bec%):;ݘ1q"V̈́w?w-o_F؄2:[H?%O/>u_}1F?,: 5 Ƞ4f2T =G2jh 1'}v/f$Vnʭĭsw#}?O*]>}_ПAa= +^}> =3kZ` mz WWk3šr\{>M`D5L~6My+&},6uz `4:c=ܑ@y $ x/L@Nsb etZ633>M-/d>=qCX{(7W/=ⲄoZs,'lIwÏS>iϱ?fl Qylc[)0@*D8VS/'cn>M|G^lpDO -9b1ڽ ĉkG#ώpҗ#j g}ɟҷ񁐈O_@",o\!VYEq)~hf8G3?v'H~N x.h:;x@ko䃼_&d&E$ jVćLt`vZ/v㞜`?>YEp?iߢb֗4_2nabOV1xԿ'R),K}[$XgcAC=ZF_Foy+:r~ųٌ)lj+ Uj׸qX!}D{hZLiaAu?#[?v/'3 ߦqIИ~7v 6b1.,-#U(矺|#d()}r4m on;4sy,!h3.|xz0!#@ 7q!jX7ܠ'|g۟ƧK?}RapvD9otw|\;C4IbO1ц !/}P,~&XU­Y&n÷;kbg͚_ A>>~Cy+;?EЯ3)4u蟢 B2'"8?ܽ(BKW^+I".ZS#bU?cPZ$t8ibvo<~&kժ 9/&_˟M,%p[o&+~P[I>3绪f">j7ni0ٹ+0~|a,3K1!ӱQ*E򓇅LO78ɢC&%E*6+ h n<|!*/rU߱g-rѴ)WPl9&06ٽ1V }!CL? yH~K^!/>ݍQ]%@&rK/̜kOUbZs0vb7LG?y<|1s޿͜59"@nj$@IDAT [oߒ^L5M…0ҿ2?0зoƭ/!ɭ__ `,9#@`aOξ)0 |ڧ~}IP玝IXs@G,'@E|QW\ǟ~4}C?1VWɪ蟺_D".ȟ?1%>7յ?}%ϡGh F &rQ.Dߡ`cYnrhSlvn43Gdٟ Eyj/_=,zi/bӿղb&$#C_)Sm̧i#i.m-/Y1#]3!"y |gkpћOd7G>Dhu$U^B ?cɽ?EU' U_:o:vh+!nU t A| W TH+2sBvv?mG^!LvJD)|BD/3i.b[K }Nط);?c>=M+ev?q^3qϙ= .#PrE]( gSQ>q? *ݧ_|gcZ?~hLoC*>}U'?a ?Q ߺeuϥE3o\ho$91tyt6Ҫ@oHSҔÎjVC_ȑ~҇_ ͙˯{R~eAg?nq=ݠgw|#:&_[_)xHYyدI*RL6}3т7d+Pz݉G-"&w`! {ɏkNbM@Ҟ<7镸Gxkb/ĎJ-?L1}"%[!bfS|ՙ"U ƾɯDsU/ְ۟89p?֦F?!}Sc=Vf6hve'Ro [03 G3T,c,cC+%$YO^WCxZivjYb"z&. p+98F*_x<}{{?Pfߎ$p~ˑ_xvJHDy?E2N0WO:q@G0'v[7Î/&7&ld fe{-{$/ &`wPqmѣJ?kݺ('Bsg Xgv0Ri><3 l?s6d#,ߐyO.x50^-dWpXZOAIg}샏 "U?&CP˨J>zyn;p?bW[|lGv?s폅xHsb DMAb0Drֺ%oơ>6P˜_k *F%VI2@y!W ^-~_Q xD`/O #? se_Mgڴi;v>;``ԋ WzK_t{]`HF= ܂w]`L`WNzc{ZGLsXj28Q_4X mͯ|m+-Rwu9OS/yW ]9>0­onť?M[%}SKʏ L,C˱/6 QCʀ+3,oxr?37z> 1Avc_m;}˕E`;?8`=:蟓T>bc$(8LRI?J Jf8:i݊DC+M#D?fF~< 90{ .m ᗉ|)B?5m wh_oȃK8 Gk6A"y2b쨾)3Ͽ9 oN(pg7 Rp/F)|E"2*?oޤXoq@2 /#H’N?GXyv{ZKLjySFy/)$WRo Do$E] ,XRw&?gtHpQ;zFeW^3n+ĩD(X0D볣5NvQÍkAdAH X+*+c1?j:C@_r(J<yԄutz{ܽHep_~puHUdxft+jB17zO[r>8xsZ')a8%͒$k%tw5gPߵs'Neb) PFhxWOqX40qHW=x5lUJ`1nymr6]U9_-V\퇣UzεO|iuc0X#<?cbm A8Q߉:XH})Ӧ;=#qV#xO^}-E'?1\}9ڷ}p\v׹CJĠg9a:c7cLFI'OOk#!G|;]z +3Hqԟ-&cfyA>rb̈,'vf_cD-lB3oШ&JIlȠ̓ ͷ]++D6_Ma qyk|Oy<%?҅I D94D@w(A9> $ &_PEKkXUT 98ZW@@gҔI{qtG84nd )$U ۶ĔG&ꖐkI) ,? 11(I?cE0Z$2j z9k1kɸ q⩡_bsWgaGMz\eS {I8/LK(B+z$h҂pLbH@AeyG&#&jϝw (kя+bbI7I6[!)ŹaCGn&Ol`XdKZJ^0L!T*]k wi,4Q(+?1*PE۷'o'tږ\2;WQJ-W_*&4b/6?@Va/WݒmeNzO:Ds|[h-X7y4zH_ 2h#HjJ!N zX[75tbI̗\Q&P8l3tɭe#h][=&P'xC3B! >:ZrTE_".j{%J}P>AR iK78p[dv-ܶXYg܍7ߎggU F!icCI76{R$G~ECUC"2yhLx)[J.o3iLgT+VK_O]e``jr0i3Hmd7vXl'<͚9C*h3 2 NWxt~ޝw. S~qE1|UÿeO}pluW?*my. QS˿إCWn1ya^2d,hB{^ o/ba I4P4Q;ﺝwHBcUT NюJ*l8ҷo۱OR5T|"e%?ЧY(n>?209;=/:u ^⤘|3Д"y–g$#dekÆaEgOFCje,>H?ѿp A_(d9&' y:D[VAKln"|X/۬ 5k w7+EGb{y3LXɑHɿcNu$ )=n#M&샩&\Ph;݋nQw>7Xqsk߳dSY?D]/V{v)??z{? տ06=B/3H+V%E3|HwN&W+HÎ96^6Y#2e>*7Eu1ؿo^{-==%w{c ݊+`{T2?`_C'b[^ ?8Z#4Wp1~NSb2KHi&LĊVΆDS'& Md!)WGش7C0*7ݦ)7^n0Q$d&x=7XThh!h/ѓ:q1%T ƍsxi;C}'yʟBC.}ks؄(cK[p[6cGC}p& (Vy?{?ȯ<Ə5>kNρx؟a8ɥل-`d| xcQO0T\&_0wm858S#>Y̕wC-p\2H`$*oʡ߾C[w՗ &(.v,oI?ImĮ*Mxʟտ1}ɟBH)gM~XȋB7zWo8v@L*@?D ^\'cixX;U*)_#' _|%}|W`^@мySJt$?hCؖ̅_NC۟A~q^!Y>PDH qGO;n`2I|?'Uo:f w?NO۵u7\sM*!1 8Kn#):/&@_|@?yk?`>y^[DTLU`"*T?|wy#2Dn:c<׳1OMߞ)@燼N;8L}_& $kv+AH&?Hq/FHR[_=))['xL?濾YI)bKǝ>CqBVos>CFܙPeV?OGP0%F7fQ" ?żSŤfpxVP<05e Ef2[~̠OeR &a"꿅 H?w\T-E})'I̟O=#-NX&wk M>#!ro0JKb鄾<ɩ0boFrg[pT]E`CL9SNvconjn%OqoqU/ugpŌ-OXDGĮD\?O#F)H<+ 觶X6f"ט);Ru zMЎI> &bSRU$E1Bx8ޖoj1Ӡ#߸*>Џ';WyduNJ8m&^ML,Mi r: 8%埶1jymi# I(`尿4d:/$STk:ShB㏣kxYUVY٭6d|)&UȜd+b/HI~Ve&jA: #I!^?iiqP5|..ҀU*@Q?T|se<%<¬t2G $r*)9Re}-쩳ܙ6Ͽ2}/T#DlC{kXu~(lewUTk PCo'(  S>sO{v5f7k={62~W]}-N3bJ- hiEwE'S'ŵrF+oCD?5.b1"gnv -5n("[Kqtg̗i >Lf͚qYvnG_yG:̺Mg砭2YX >gP5T;ɩz6VAy+,Є~"fI%S9XH LcpYugS oC.Z icq?"7p YmoĒk l@MذG8Mܮu;V lR&JvBmwTZKJӡW/ټϤ.`U!|{}>=>~;Xt@Z-"ΘF[ukYVݰz{y -Ev礏NoE3CAUN:!ZC)b𹺽I@?o? .Q"Jr,H`c( vY5+Xş@)-I_CrbK vw#lgu7݌ՑDWjM?5bI/Ab KseR\% **e.YQWJ;e&0e N9H7CR128Ƴ\k#ϟz3Mk"5IW]``08w*67 BhW^2f o"yx[ 2)Ҷ H.>yU|#L,#>DLYOEw7|&٢bxmqXa(E٦(n b=ε|ag9CsGk>l> }7G6+?#4?5˾cD1cyֿ>0%}ShR'6pW~ ī6?W,<(ieuOF4d׼:R`3:'<-bj`;1OeF,?3F{W*kU|%!IrQKa6 zb6.ǹs)'ퟒ`#/{;ncǮ)RE-"}3/1,K^Cmb ?r_zUFbJcx/ Qŭsh5,7GlOMeq𓟝Yp?ТSFEjR%nMpK-O)'+OWϥMݵX&Qys쭁⊅?CQ谒?;ͩSb \h/~ol'U=n6k5•gĶ,>OO`{֜vEY9Efo./5̧QoT~ج \~ޯ oy~Jq6F|.ǪN|+TW9p^$ی~dOw`ӄTb6xڃ-k/?D0ejvtXj&ymsϿ$t‰?q#kV&O_[N1QA=HdEI_cy% ŸK}tObXm_<+o 32M2tĵxȿUmחEW`+E]r)~_Wx|t_|SElڽ4 m>%0?sx4owU=3~"lV$=L2T{"_Dmϲ*Jv̪gycZZ?x eZ6|n2Y)t  =(K 묽v{&QyŸnտhϾJ`K,vm+3wl$`:C[՟iSRhhN Ѿ NϜ?,o,vDlǵ܈?80Ywj8A _2C8{oА1x0w S~ րΊg Î3r&cs%βp?orEeгK&z[2_o=>?f/RW*X {r[iIʢ5?s9Ke_͕*+,BX ,ʋǷ }z?gbpvî?ڪ?WGLHM9|+9/=0uc,` IJ:DO}Y8hl%.ߪ%.~-}U85[@4D*A?VXB b&?/{K® ( x%R@FoQcogk;!il5/L0C8o+.uĿM/Z/RgA,׳1ʿ}Мv?l>4}+G{oag4C7F\c I2n;Oe=ӏ4_JDz9o.Fy~OcUDؙs8 !c.8:l`kߟ&(+H~NV&SeG4?R^-TFmۂ w.5~!X8QF´kY@K6σs P!q?fF+fE/?3I_\)RJc>فG~Q}LxK އ:n{, Γ4m,vm/OO?O>Dx+*l@x$1ۥs(+n[~a*~/Vs ALO"m|Qcۨ;UZT]o2L x<뎻uW-*2F#͡7ޟ~,!} ÆQױgԿ~$CZ/VEH~]޸?AXҧxbB$K Db?_FH&?':%\pɥ1\~r0l%|nNblOXEi!㭈m$>(V*p3O3ß V>q|(/*Iv{γµg_ڽ[|;/?$>K1c4a\j$s.>s>mk8B:,ZV`+gd3D=o1Q0`ӀEljr+Vt&) &[o\}~KwEVZaŖ__-ڽ[%՟ 7ZU߃Ϛ"<_>q?HEgb䟸5F_)lf ew-i(1a*t$p:$87u)g9m,4x,u 3Qabo:٦,o&Trbcg[Cnqw9.]v!lY"v_LxskJl|YbTHoV&_x Nxfν0<+foމO1VB?}W&Wx|xa.a͵seS%]xy'qY0U󿘾cX^2c}u>Ub"bF2η|EbͲsm*! iI8[QJ3;ogZ-0W_s>4t)S>@b6fz0|W<+ ү1ǣo_'_l/|>-ȘqׄcWvo}:W9v-A6 #1?cX)Z:0ߍ7/3/ w;Xޢ7lW"%!Y]z'[W,ym&^Wx/^xd@Qm$ś [3P7W:bl>gAH<qcwx  {V-cU;5n9go@Ĉ "EE/ Q]A~̻[$߉ Td?MbqC t'z/abh<{OXkM6(׿N; LHiCs,I&i3akp 4wkF ocG3a㋶SFHM75G*, 'b="h?a)윷c=;?WypҬ*doWtQ'';WoYo s' r4)A,QSL^ѽx1x,Kx7b>.c萾R[Fnq8_İݶXzI19J:~VZIZk^_8@-QZ˾Vɫvu:wpIs_uYu`i?2Z8 }/%Dt6PX̳s_%cH/ FR|۟K<]UW]}3CfrWQ$?xb$|'lVQɫZQXF;A8v9ߊE }qȼ׳,5ijx}ZbV螏"mY09OmkI.rғWtIB)-rUM[1򗪐Jv%N@l%`G-_q5>??u 7.VK ~7 y'^No*d<]R7oe^yl|n }KC2WtR'+gze8H*YލO|5rFk>K$-JSB}jҬ+b̢sQQ0T-,E{F%DqTIhޚki³-ۿz 'xA(zQ^7sLK7xomNo'-G':kڟM?m69|}G|㿤dQޜXnowoǎ%y!n,e4ҷ!GzHX$o6?+:6teN'>/b?e!?ۯ 3&vrk{84y~3&Id|{ Ry&"1ϔh;џFj04a)?gUjcHDOx0?a'φ[Lr~!kT(.YG}=w8 `pyѷ{1A_k…\cҷkZj?õƣgmE F_W Z.GJ[oy &[0ӟ_o򗧟 ih.L⨬g}: ]RʹMOpn%QL(U>zφ>Zɟ7e{^|] OH˒_ Ky]l)cdBab9+2.Nk9Oy7Z'/KYWfa"r/.(XdpťN$8" i)T wt pe |,US@}DV^y>eMT :p14_RIYds SQ|3 NG=~ECzGUpc?Wa/1Ɵ [.$܊79r-4V_}d̅iiXW IDN aC|{pz頻7;kVo|.;a84}m}*s\X$q? JmRv#^7k?>8UNN߯?~V[gG}4as^v)V(OY\suտQ9@`4`^Z(^O M?G$u5d+1>.j}փ}=p!&mSH9'8wSoA_ؠW?7qXmZ>34ծQ=c&/ADųf@m]DI"jTxdj5\+Ñ3ԉ:E2iS\ ɕ.J˯Z]۟{EaWX|OurYA7vzɋë|OLm΄Eg2yjSGg?j_wop쏆1riܝvx#mW[[H%()\/+7]!\ !.h5ʿ??ˑ2F$se-2!B?৓jY0_xe QqD,cDK}RWiRڿ,c%fO?RBq m̶D̗nU9p&Z~b`}_~e[=V/xs|MtMsP%]S|~OϽ%j˰A?]MY#sYauL X&]L}ʕ? ++v ͸otm.tۍ6X?m: k8~'=꟬xѷ<}gͯ>ҝ>]0JW*ٟ%]Qx+ҿNwA؇L79agO[[p}M:/Q`WODz7k-6TiɟiW_?+qoy5DyD0{ b8Ows%,J\?W\q=;+otzx\O YIB8`  ֝,?2kcEHq 6_='?BO HuD@OjuRȿ8:2'Wug7_ \n0s&pycXqubgmdy;-n̒Hp"DZ%O??eӮh5?b +E?1[iM?n J^OO[ ;u"Z~jSD?Nv de,bL'?"`;C;3vl"ڟw_<;>Ԃѿ9<>UJ"CO$[o#< ]aXWįSp*'?1[a¿1$saO Wz)rnc ԑ }_F19-ڟ#b/hԿyVzG ?u M6>ٝ?Cp>tʻUKe--?rd{-˰L]K[/?'2uJ*OD\3_j޷j}5 ;EaŁ+.3e)>~E"5;W`o|jV2@~{_ <&c*J_ai|2GWϾL(h?>=yyQWf:=k_ 7B'ҞǞb{/\xKEqR0ե7ˤ߬? |7y^b`MF'W1Z3b3̘X?)8 aZ':⌡O )SvhM q`ĥO/ I׿S՜yS"O o\c]\su-AvpOGB; 6;&'$UH_* >{|jUG%?ݑR *ٲ]G?%f[w]r,hh?]X0ѷ;ٺ]lr f[ oU,uY[J:U{['2,7i oBv_n9p ,gV>]w-1&W>/?_̊_M5cef,sc; Y>d]H8/g 80Zӷ7G.ʢKV~y #1 m̑ ykV&kM3 =lvO\|K`u^$}s2Jҍ1nAk4bY4vb~3T X?UHGIy%;/gcг+M[5, 363* ,{g?c,KԄ#2Nσz̑t\nw2A'*xs8l+?񤟆@?]o S?}ݾ{!ͫ-& S+uV{,7wW?bY3w}>Ž/ʛ pc9M <"pE[/XG`/6}-~sR>%xI /q#kn%ɑv刵D 8xl&SQW~j"-7lqNx}u{qϷߺ[>ˋ'.WJRPjXF+XbNجaj-M7qKno~(c1$iKTcc럧_ -nqz?~a<Tk? 6?+V8+0^% 7`"mwzHvIB$Wg6Fj:N5 Տ{swya4,X(৿5!W[P($ "D-){OCL?֌\:\eUv,?DJ1?c^`V+:L~^;EKjXbdĕT/@J*!wh-C}K']yg=Z+|^&KYxP 9u3YߚS㔥8g:G\b|ЮJҐ*7-lEG31/f9A.y0c*} "(#o;QɌsyh"#tiTT:Ȥw3003H1,؝)7' ;? 3}fLlȨFlN?<_4Y$3X}t2a=l5 =X8c0T9ekdwƬ:&ͦ{㬉7'/WHAwɥWju$/,(Fhu(ֿk`S,()m6W)HdR SUcL"uy{̷?W9:.j70Z䏫sG}zKSQ?}>f!Z9:X_b_G"g??3-a*+ȬVӀ}<\]"HYVc;+Tq%"ݎSvUH!WE- ?RѮR23fR";kƋm T?gxC5*wȧν(|SDO),-E Nr5P9p.~JÈLo&?>H5Hl|'U ՟ٷknYWYO^q㳘u\c݅*"I?O<8r d[˙vwaÎ@λvzJkը2-]90W )i#ZQy:oo?q)%??Gw0j00yp%kvbтbWm`Rm mRvHM (z My၇Zf2c0➿`A1sf)s _ LVN:seXδ_x6+pEҐ*R؛v‰ '|+ȣaBIopw } ;ӗbc9)"xr/1d9b7w>pkx24dd@;szqQ,# Ám.(`vg`QH^)>nҙÛ:BKu[F,]1)_`%B?_ygoXrC,XG1 wbYی`i("s\瑊_,Pc`>`f*׵u<$[žudLQ3E<><\:\-WaZgx$z1N_SSOI3Sލ"ubeGG;D098D6['!^}}ҋeh|[*֟/ 'ȋ|_uoK4^r9Gdx)`c>M#}kc:|c_Ƅ3/wp[n.3is~"Df3z16oV!+eꟈWQnc| oiIiuK ^w}8?_ގ$uXXn-|L?.Crrݥ1X8K_jgO%Έa߼yJMq<~=”?]Ј2us ?“t븘g<@gu* #x7켆?Cwb=r_iDceW|o+̍tVShR;Q2 L?$=߂ˀ',§Y"6Շ>qB׾,=<u9u&5xdx+31.8KC?χeOnχ^_f<\xc? V3]a_/#? J=y .OhK4qL".u*hJW-*J(_$G?A#J%!OpqG8?g!Ib3P˙>pogP9`I<!]诵_Q~ <ڳԿȏ"A_|m9]9WI֓z >}?~Ao ?X5,DƂ߀vZϯeמ?O +Oe>a09,;マ=D]FFD*JDE޴xT+T,lp1]QJ<=`xl&LߡkV_A]SXc5pls)M^:^&w-}Lk#6&^+"IVY+YBԤKon԰F#ϧx0V|w69Ll 4!D#[JyR>|DVOL2ӤMհj ##MzcB\aYĥ禬v=l?.:*Rrp/;?.!*=2W\8@6a[i) KS wDAikξPJ[ yKSgYB_tgwçȞp V~pl,2F?1+f _Nx[)Jh':I+ƧlPNzXֿza7U93_>{4,U7Oɺ4'O^ߎ%>eoB(3kϬgPN?+cr6')h[~aS8(.$Ɓ=ֿ*7YNoZ$ 1R,oJZ?KWF:G=M.QYO~V&Q?}߷&?z 6S7$z!`c< 'KnYcqm!XҤ}tYʶ?u 7P;[߈s>hç=|.>IF0[bƇdZw*@/[i>v)OkzZ'{pۮM>Oj,1[䳽Yi@򻓿<e'ӞA#t@}68o@:fa ׇdy9 }]MYkm9Vh8NK1|]#bb&J-{cV[N 묳G`[1YQİZ>,'Cr*!rY`{N8D}`q=3K ^k-ڵCtWFڵ.8'< \nV[nO[6)̚5G/2=ɡ)RlZ?d_!g+p<*2l0o|mNhy|Fm 1JeW{?fXiWc'f7?Ī>t1ڽY/>uf3ۄVZ1̙P$c(L?:5=_+f ZgSΜ9;dys9Ăl(lx'Þ|*T`$'ym͊o(i _ßF'FƗ<_S#[mpn~̇yO? BjBcNO+(_+g۟xʫTcb$r|"8ƱKXu#>IR͈>Olʊ vtC?Cl7w}eV'L`&`&&_DӨoyYFrg6_N`9Π}3̆8ÄMjH )g aF Tttּ1)^7[ܡ{RbqCלPy* >+qJ0r>G舓 #D]&C֡o Y"8`kj2 ]-C9K>P6eN'A%HR|"O7Gj%0b0YͣTxS?)m ߪ]]>A8~1LSIp_Y2nsl?OP,iIvo?0YN13wkמcGYAW,r_WlEIӎ/JO:N%~IUG̍kKKWMA-QRin/c#ޗ+ ۮ1nxvNo78] 4L*p~{vVc8Sr%c?B:럮vJ$V/SzJwA~J+1݄5O>-ZFhkcokw_B/6roum3U?O ?o>d°#dfqo2JYBGA;S%uq-U*c`W]ƆĘIEp':]T?~VHHFb&HS>F>gꎁDʀe]Z]p@[]&սU:c㯏rgfnبfgo!D  ˆ"ddTrkX/#!hB=8=p`Q9 ,`H!j@s ca ]tOi`CDxd BG^x$)+" qV,s݃Ƹ c2q Aa"՟ςֱ BD|ZJl@c<%; [nՆ ?۟K~:}ޅaͧ9>?w)<wQ|MHxy "e\l˔d ASpk%=!Խ}?%4- 3#@&A晘y^$1Q,7 6/YH /18}c_G㿏P>Sdb]J,pFo? ?՜nCfE|z?usO}5T}){`Q4\i䱯Q"?-LHo@ƍ7zNB,ceКk4650U%q8i o*>翉 #ʔ˟*'N{?|' cU,uro2<0?|p G ? glHHwW?cԟ?/jY5nP ,=ѸpC"Th @NtW5)$,V,IF猈&?ZM4zBd2OA+ŸguQj3K5?|Oi_!d%i0?[LRuu߁e. J)j'=>c=O$OӀ|DZ};%:MFec'5/"8pP kT5"M a$339$;) $??7?7O,\#n" &_0?%?5s?JdCR=G$'?%%X?q#ljAN]~?1osǞ3|nhsyP1=~s yM/TR4M1Muxd&@-]s8}tOv(l6I*NC"8%K>>CTȃN:n]3_b$npSn,0_%2KnZTmjn5\Wr%Ʌ?ܙkKO>%IL·S:hrTu,7o^N?ؿ㟏 NKX?[!n?x>,@6s9eMF{-a)KXNyQD=bBdfhL- 8-os] XI]b6-tUgw)> X-u3:99S3enX,bۿ ,`W??$>8Fe /9TsEDcZL$[d6YQ1K1G񢇽ַqB#r9$Wq`b .'9SUrg9ǝ?'c\z;a_we++|gǟRg:)C;EԫfwG֥J\XO)o= v"Jf|SHu(rp-S0`k(ř$w+cFx ?jGݫ0 #2ױpb M _}b"?ՄEB0Vp Fid+M;9qR\o7Ul 7F'?,}/B5Y/dĒAn&eF!O Z FI &EdxjOndpy{6< y)wp.;]U?=UncsQa?eǿZ?1Tr{oĿ^׳xM=p ߸gYz|? InL,Z?/ }ΟTe*/שTL{|Dn񇩪r{;o>?Jm?:5n.;+RM7bW?}x?6/:?rkAғ#|b822R@N!0 E.|,x$w쉁rKGpXHN>=0 ΢9}?$x.:(ol?'äX#ߺ?˄W19ւ߮]⥏Ivww|Q؟]ϿjR_z[jUO,ІiU,ZL6Ty|WL^;}U8՞Y}b,GZ9@;?L^Qpsp?AA4o?U|߁U&5u?d_nҟ?mJfA^pHFޞ?0?3P= 6|b`Oa&,Ps8!}Ǖ@:%Bn2AB$[s3x`:α% U.BGWa99gùul|Vfb#?]wgҰi.M1n5giL| apr O+LoPVwuUT9 6̘b_?k}ǿj\戠6p!/(ػ_M07X2S\8Ι˿7xR1gR) q0J͝`koیyF\w)!xK*'V:nM4]Έ8лQ-`%?>C0wR`Kȍ@ITQ̺SAcWV$IDATIͥ8p\ mS*$ʏH@k/4O99S? eV@±j CT\8?%°?ea$Cq2+)}OdLT*0o٩߮p~o|S.=}92NJ09p:F,޴FxX$iI&ku=^cJs2pb.d|_eþUE@tsOCz- Sf2G?osU~"3&#+wk|@)Tos+;z/@yP'լԞ*732cbC _PdĒAn&e#،cG1yE%D:1Vмl;1%Z##)d`=_Ɗrl9bwu#7 ߮?e13Kpa?3 YL?`v9|" •ظn/;`YԟB6_h@_0䟫 XѼ88YkNN#f30v-s\pxdSEȎ13R=o<V:89G!eA)1\u?a"s/XMeAp@ِr#adFp?hO?I,GdX2N &p<6xLF G$ފwFr! ~K,D.)z5#+p9]C%QQ :mdp?y0O9Rshs(?]>'^\X㏏ +L6s#_,^tp۹? "׿]N]_}?%*)LlC/JxX]F5*$Tf-`.ZJLW#.0&8] 22.19 n|bxXYjވ5]wg"ԫ__|JG6P = _OA_Ēw8ȹE ,Uc 0)l5ے8X癮(͌Mbv7yY^ N ?";8%aX?A ON"Wx* .9,[n$dSi\yo }#[\#\?ZT样F?в>qGxs*Ё;3J6OS+<럮'4GSg(.YO:C9[4Uĉ۟ipωoZ)t8KƉՒJ$P>8'D]PAX=>[Iic1wN(*MU 1翄_?'aOٽ㿏J>S/Sz'k}\X@k#iAF yG ?ŻGrv b!ʟ?沢-{|bI8|-x u\ЄI=,'Ak!XR= )NAxA#'bGUcS_1|nT?Q=Km[v\#bq_D`.T%&PGCF]g:+qwϾ<<_ X2.lz 6K[0lM9u0̲mDfzF™hেļ|$}.?6/?1?[)ϲbUn4J.]?$cBUl( \Wq?3?ᘊ?!eSQlz6}@|6ʙBUH)"2Jxi #iPQL-^'HN'=.*s}7wj&??:}4pb֧뿮!YV2i /?AZ Z֧&>_ꡯY8,=o%|PzqO,'FLUGL^ϊм|(]Nhj0NbT8ˑ v18}r\"'>r5w'| .'qO.S?QQpL?a4 WQ;ZK5^'zj*Dl~=#߲=ޮ4yLXlt{ebןM>A nnȏ`XZ/=~9K)D2 &n5L)%ǵxWQdf 1.Ouy3`j̑8fL,}F)ui v? n]uwXߚTvTɢ>R֧r/?z+w_b-+ߟ0/d~4%0-_, *|,~Iɞ:( xSyR4\8}Bq'8 رw'dGܶ?H&[ae* b HX\sā3l\cqw(_\wb^(/x4b'b1޸%5$&[RJq*?UIpă$&.! 8Olp2(wfwsO-C9 l3 ϾjD%{d4lmjUҹc~EJAyzadtF]p  y(t3#f?„AE'NU{ǿ}HzN+~0_Fpu|s;s_eC9vߩL0S?U=W}il9KQ:-Rd'OjGCRވY9$% 3xĥ՘n*~-w^ep?}j>,? f d\u_O:xѦvA//O&Ch GW?Q=[2^!Ř[xA:eQ\uU?e*990?UIg.\,n+^* 1gS^6?Tnn 5=>rhaq@H M)6{h6.?5/㿏ql' (>Odǚ?S\ue+29_꺓_h5q'ne=dq2?w;U~u8K5:,9N:I!%ٿp-S0`k٤8>{p(7^3^m ˟ E$*~t?^lb_7?;㟏G0c.R&Q- n$hRFLe`l]]1v{J n"5#_3L>/Ē~qrܳ`VMipg`Rb)GGgmW%PFFceC="HiV5)zSL˟'B2?}_X]㟏lG) 1\Z2`Vj5 Yzd]%k4!)h14:~W'5q."SsqJa݈lǍ\<_p=@+Jf8d}'_ȔХ7]w?뽹8Ow3̧qܰdxA٘H;O-[ޟ/> <ˁu/ϽR3,4ߔI3ooyKv>Έf3& Ұ;>v`˟?v^'/9Os'Hej7> U/Sz?z?u  yKjD>P ]vcP^P8smLsP8U =\n'? nRLN>ײϟM|;%:&{3>ƏHXjF aYIbq<6E /ştYzO 3?N_O}BSIp} Y_QlUȈM\w?@%?8ioy!/yGAh$VflviX CϘ *|bɠLrҲg|s{cÇ&J@x,>*QfI9gI>V..~GDwC:g &rqsǿed7]Pv/Mt+OC*?0?dJp/6VG9K߾> &`M*;~(n22<˸/]16#Ky+C9<叽|@( 6l1?l8׸êTLl?]wҰi.=1n5giL| aS&pKOwH &.k ^ 7dJ3'|wD >i0O_Phֿo|tn W,'ߠnErm) 1= n;ZAlr;xߩC_P&=w2`o5o>dp3'*o2bΤS⌟~'w/i@@'9dfHXq0~XZu{K`;^+ηnjPtG\z4PקğvRW 91zW;gzWw΢i!G ei]W˿{m?ܢOF0Ƴn+Qbwg޵O8C"VU{ca%5{^7k8^wџmy텹M{rgxO]xQ.ԿcO!7O%8_JUϵj\\.9$hV`*B6 1m=Ie20O[}>^-'pi/x~/Xys|R}0Lz,z{,;mn|VܟwR^Ʌk^WԿO릪kT- ?G#<}B=忳j$OG`|O=w߯_G׀-wf5L!CC߮l]Izȿ,z/'a~eKV:[[M^3KOܑUݷŀv@;I6;sZSy._QԿžS{끫Y]ſh|RWKgo̼m/ξ_[{3B_tDZqsv;/>ɨv|qgb?WYԜ2k-3Ƴf%ϋZVO<SEƳ&{ܫE>C};> O{FI}Y쎙s??e+f{A{ԑ.'q3F2)  #e1ar0ڥDCzUԿ?&=fO0I&`L&I]}gtM 5+ow^^G%U,L1ꭖ喇SV[?IkHWSfI8&^K-KiM?Bm4m/Y&nA'YAG+X3x4L~cqƟ1i}Dk#c}q*!%;{J]Ek}]`+ XU_4;iL>AJ8ߝճ[ߺ~ey#X5K)_5Wʻ&qBU1P aFOx݈\ĿfLN:ώXyoӤy/ISE+\^N_w&iƟ??[jo߷Ό "ז%5_i?}uB+`bs=Zn[&ڸ%=ջ&6|HyL Y”bEEԿzR_R[Wx)'{PGJ# Ul[NoAe_Wnmv][/i?w]ύ?/sg{::$> SY`vgƿ/3W݊ՇXܜ @ @ @ @~D|&@ @ @ @,`bWǹ @ @ @ @N0D|&@ @ @ @,`bWǹ @ @ @ @N0D|&@ @ @ @,`bWǹ @ @ @ @N0D|&@ @ @ @,`bWǹ @ @ @ @N0D|&@ @ @ @,`bWǹ @ @ @ @N0D|&@ @ @ @,`bWǹ @ @ @ @N0D|&@ @ @ @,`bWǹ @ @ @ @N0D|&@ @ @ @,`bWǹ @ @ @ @N0D|&@ @ @ @,`bWǹ @ @ @ @N0D|&@ @ @ @,`bWǹ @ @ @ @N0D|&@ @ @ @,@%i"=KIENDB`parsec-service-1.3.0/doc/images/partners/arm/Arm_logo_blue_150LG.png000064400000000000000000000102231046102023000232510ustar 00000000000000PNG  IHDR10s pHYs&?EIDATxUȺoA  P#"<r 6$U=k͚s٦JR$p^^$AI!k/)?=U6hklPX{Gzco`($H8_mJ?f{~M%}ۣ_ϲῸ'-e@o1q?{lEܩ N}-G P'-N5o_{)R_N"SmB|3`G 7f&Dt_Hki;4xo ")ݰ h,)}'/F} >xu42KʝQwߨթM+\Ҹ-\7F徼S63{ҔjE7̐_&BmDeMlbGgnɐ-h؇ܟwM`I[Vk_\'Gߔ~9Lǩ)/( q 7Dgxd,fþGJʋ+~yϏSOObphc7rwM%*/|$ek>fďg c:C?if=fO{6Xwþ;=論X&{yqyH[HzR^7fWgȱ!kG}YOnI^Zr2pJ4չ+1xtv3+F7+3;nb"z[L}A^i Z'nu[~S< ^Zƺ"؃Hd=>0 M:Y|[ٹc]E&cnoǒ UCujgmx$ xxx%lf1*: 7TA|_u[ \_>w!Xy߆)}GwJ]# Z3ַ3{A]E36_F`B-Xg$Aڎzƶ?2N)6C5M߅"^g'[!Oq{/r~mߡo. C=<, ΍arP yIgU(*6C=s!ni ӆ_)7{螩AI3hc}!6UkTUM^:v `ANȻӋ@j>m6sxN/k&*8B~qpFwo%#7zA_{T&|%i*+`ܛK7Ѕ*ʥqQi"wwoƝ?|;lЎ}?~؎Z󽈣>.|/`/ImˎTҏ|o[|8;q|,WЏ9DϝMw]]֎pZ_E`PUrK7߅x *}އy%.9P%=]dl{:wjRRX;Zv o_c?STyE[Sɷ_>үν8olܝ|tE>]Xuh(OVL۷cWD {B=o4d]g~R|\dϽ&gYqV68idC;3e/üJT&pӲ!t8\ZA^8.1Ղ#Π |=O?l0S6tp`OpJ:[ol~LއnFӎxז*!fi |P[. 륲A7ep'~s_~;<3ڎ|8U|x3EU)`g>{}Riɚn!N?V68pI3nS"hSI׆ƗܾP;تM+ fʭ_@AO#~Xxؖ`; {w&o{ ۱}T[<OУmcWͿ?iʿA;nf 7Q\П ]0U6"m+@kXϧ;ogvvץsx"* wp&j~JF=ܻnCH59|4@~ X K6Kq2^ AH)sG&*y):ɮi&tc\VJ1vtg|Vvl€=oK ?VWE֎Vɧ,PRqTl,Ǩ Y0U|PJBZ_cVVe"CVMw濬Kq vJ%_xcSx̘vlϋlOpm%4ywդy3%[ߖZ['F4`Ӑew=k+r%/I7t*U! *ۀ7ʉ%{KtO{bcO Ty{#M-ݫtp)xnrv%ߜtuTC~lwٓl;.onxZ*ysgϻ,c:~Y҉wsx@핼$~AO#Π'ۀGзW<J^{ނuEB>j^r^}%ZvZC|>/n% p|UMWWou,]5=lh)S.A@S " tr~6[L|6)/.}5Dҕ+E%!I_'rN v@h JćXzr|H$M?I?}\VC޹[:k`D.' y-\=Qb~FW%/5M @l!~,wP*]_xD2F3*yhCoD#;}[xa@!Oзgh9A Nu< _;.o%w5V68!BD҉xN;ѐwA_,}ϭ`<aKU1M_f~TVR&){$I?Xɯ^Ϋwx%gW.{Iid&); Jϰ*ɝWv?ƒ&VþT*/zn$" ;@B~2 I@-˻uuj {iw0?i8 ; oμJ'~ÀhQT T٬7!4(Z!_O>-QJ^$~?|K|[ 6?gWOt3VVIENDB`parsec-service-1.3.0/doc/images/partners/docker/vertical-logo-monochromatic.png000064400000000000000000001417001046102023000257770ustar 00000000000000PNG  IHDR$f&tEXtSoftwareAdobe ImageReadyqe<iTXtXML:com.adobe.xmp vertical `_IDATx-e}/ "#(`A#&jkbDQGF﵇X" c,1*A *#b B9p̬wf>=p^{S֛@^/yv̎1[JgóU h.e}X>gXTٙI! @g{,bFNyPUdgX[.e~,ϔ)&SB! @W'bHCc!`Qt"H?X$]yuyYoˆHG IB! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! F! @AVX$]8GI*,BUEve,DN( Ir )${c.Cr.EQHЉΌDr^{sEQHЙ3wNU1/=(ERHЩȚӃshJnE̗c{ qK^w-[Z"<TEv8`)<&^1Isǜ.Z~oˣw~_^i,;lhVĜ‹@bysLsKnyG=7=cyk.{aᘷ~_^?1w 8.>])$HF^y,_-%b~~oWb!kUG{X^'$v1 #5F2Ny`Udurt̶bNFSF>#cc_3[:2t4yo1r ͔6=;ga{Xe7&罱7[X$ 7?:rI$酱?[L/$8g159͍^)`Q3E֏yBha&"E! @ HI.ǣ]\:!/[rgq{ֆBNI۸RI} X$з Du&^(tN! F! F! F! F! F! F! F! F! F! F! F! F! F! F! =v1# `п|./o_($GyY#bј;J$$/mb)o׋yQ_sKIce*Ƽ+o*1`ЏZo9<>$㝳ŝ*9&/  IXGyY?4][1N)PH:zXiҩj%XȮu c9ȼm($`-od}st|"($`%;o;/gBo IX yYo)4ג||h7>)$`7fM/𾼬-G)23y]SHI~jsP|~eK IX3;<`bfBVS^9">-8$c9~1z[M! a~×MSlH;}M IX=/>hÕ@p3GGqRhBnsg;:R7ڠ0$1!c3QB! 7gӸWXRH*e^,<,u5֘BV 1xlG-QH9:r!fw1kJ! 7"/?/$q^9\ PH?"X-{eD1K! 76VEvެr{\ LY^垒H1=YO0 IR-Ljt^p"HJs=ϝӠ`";>%c6whtT ->D,{NNyYo$?$ǘbHJjA"[itM̑x)J<S'c}.a'tK$im?HN^֛ܘ'#oɻהF|%U]~7o<7[@s-lGoݔnnX옇ls+[ѩ.g|7?b~觱[j.#]|ߪD㤐`2nSx$OUdSG`vz;18)$ 18)$`<+/㣐`:Ib0^*$S` e`\LBrxkIJ 0. IB!9L/zs1x($#(AMK㡐` D o%$SN1-$Sg"A! W#B)F"[OY^r$F☭"T0\`vh6f'1)$;ksDæ`yYY 0\ I)b@em,/yB=cn-5ý1{UEv^U,E̓bsй՘>zX^~od;:w|<^>7osCbbVĜsd̾r})1xoOŻc-빷Fy.10y bsK,̅1/79^$uoj_ۯ3\X?pgc,vNgu~A,{l""fo&7c|$Ώ S`3emxaʎ09fEkP`;~[,)#y,)#SȘGw~M,)#G{D[wh=K0L I yb$ƾ<}nNhؗt~j,oqR:kb(d%3~^?jԼ:EaI! $-^D*wJ"ɟ{ciyկZ6J"<7qJϻy3֗h_ %@_>pjnFc_;א<{H6,~75Iсxwe0jRH{&?yZÇt2m=z*5$ BH뎥N-=Vp&c^[Ʋ8yQ7H0_`@SzpSNy(f!i?-| ZsNjT^h($3`XR^eB6m{`8D0NۆQH0V&i0 I#yY'$c&c{A! XJ`0($+GHNCDàA"aPH0V`Rba.O1*HBrS鹏 } IjCLҷKD09/˺F1t>$6#t7um vu||.Y>:h>cΫ*[)$a]yY!lu̖1w"N1ufE691ŜsVi1gO̩j_D! X]&ɹc^ַ"csΖJȻz`Gf7\wSRt>'^FNarL҃nwvuRh|w9ճ򤘣s#*BZ)IV[s#]0fYULTI! X](Xci;?Xf3UUbb]($+B;|ηbJUdX I|@'y>+>2/nVBrt_yY1Q($+$^1k&/bWZ#mXsj^ߎyṊłBQlL) hNTyY?f;LB1s6eWƜ7b*f=LB1s$JyY??"$cv yxLK㧐`N ]bP$㤐`N εO~8G! *{쟗wb'PH0f c;/l*SH0fFaWƜ1l IF*+c9]0%z1L ID8樼($F龳R)$cw`690/Ĭ'aPH0v{ltPH0v5$ޓgKpo')$. { /'"] I8dlsP^ֻdL24EbHB)`,/E$SbIS%bHBћ懒Ipҡ`*LgE$S;ˈ($#c ٪ȮCZLBUdM-' ω = IPd>[:2($CDќB QH0%Ĝ%ϋ M I&*|Y0z,CL͗D/ HB9,R1't)$.9m'$T5# E0 Ic"Q8*C0 I&*c9B0xp($/a8L]Sd-lC! UE|(Iccð($`6VaXL^Ud򟒀AZ>[н_Ǽ@ 㵞zۻ^"k c76+{gK%=c`:M,<~p=wo~yuVt9b)k^7?fMfNq`m0mykf*fמ V˺9/b}J2,U1/`u($!ayY7~NU"$"K! ˺y^>)1:9iU( քBc-~ a_yvUd5e}Xticvu'm֖B,/c;6 a<*B! Ʊ| qTY% ֕B /-br̃@K*墠 IY^w{J1ϭ+M61SFbW1m#/Mci~ UƼhTF!{yY7G'~ Q_٥*EAЏW-($}RHBlėBڧKUR ($}) Ih"&Ęo) IhoDL"[)($} bps>$0E5)$YSX IhkHSsPUdX Ih#$SRHB*.$O! L{D $0|N PHB˪"pt9@J PHB7 ݥ1&`M)$WEv5n%`ĚT_ PHB70bTEvn($1{n8eTEvn8BX Iƙ"F1X I9"F_"F PHB7! Msi}+$t\#gUdXW I@j☽A! qm`,1mPHBwG4gO @[Н_}"sN5 I#$[6)$;~.M Ii"]"ڦ8eoUEv)$;m"TEve,gK{tA! rc`!+ IBeU,+ I֯D n"n"`@.)$[0 ol.)$[8)@1]SHBNY!`#:屜& q'|N @=בRGG}QHB@Y>-/ I;@Rb)$T5w>)${ I U N! ݫD$ؙ;k UEvQ,IH[ ,B3 Q́bA! 8Q@BXJ1~T@"UEvEQHB?NH IBHU}] ")$X.`oh I菣$EjUdGX4$G! ,JsG7HBUX @ ПD,]B! 9N|*_HBz/ΈУ c! % I藣$>QybR~)$>1QHB@_RebR~)$>11)RHBNZ @X@УxA%]s{n0LĜ+)1wHuAWI@/yf@N! õ/X1c=peXvtU=_ q$ @w" 1K! .@0`X! hՊۧ} IOZ@70|_@w0pU]˾uvR5tH! "urAS"XRHċc9JV<+^W, )$a ,)3$hJE l! ToYNXN6Z_w;Ke$0+1=5@k>ኑ,-I`i+ih#w;KKKA^y7@c]*I`xuK Fey)F _~JCxA9T*5l#1l-l_N'Ks+HĀ8E&j)F0$QHOتI*K 8qloxkkH#7d.xnT0.fH#Ys#6W`$wPIGFlv. MA12D\+E=b].L5$95$ WwIRTo8&sZnRye֓OG*s"6YR@-/!b㈭`ػ|R*2kHc7S".#q^ij̊ ] gD\-K)FPfH3d`VxiTP'fH.XbGl @)H6?i#>etPG^&n7X%?B6OHu TTop8lơ/w; NA`8l/w;_ BA`8l-΅R@pڈ7D1O69vv+qRK+@c q#bHnWƊ0;fH41 `Dn|ěߥfOAhq8(bw;'I,=IѦzmpDj,ܨ_w;7K$xSCpTzF|\-0Z @+Lâ䦲,‚u"v. @kL;p+?xM9S*`n)H2{zPD%0 @+M.;p"J~st(HUnvsd=eZ㒈G` IզzNxl@eq% To\l@9AA4<%_XC6n.oACA`^f3"-P= 2EV6V~+RX`8qJB;Rէ p;zmd*G!jHA`1z|eľ+LTydĻ"$fh70_X6`nJlV z2sSw;Jԟ$,L-o"0'Dn"Pr>e(#0">v.hI4< J6`.h!njRr'EoOA`zd)w>"nvQSA}`a!W:$To\^5e!{w;I$ Ű87G|'w;7K $d7ؠnzt.8(v~+)HTobf@q@ėΕ,$L̈wEl(#ԵG|/L(HLTolvx{Ľd87_w;K$$*`7X1{Dqw̆FBAB&qOrmF|lH`$*h7X>ύxk}d14k%I + ;G.b`񃈃#w;H 0$jb7xBl0B"dg暂$@L)ɜ90 *d+)HTon^񲈻qUQ_~۹NJIP`8:"J7RL$@LKar%h#~^ gBv)%@(H4ToVv/to #pRnT$@M;EqYh3!"$P -1^ _#bs"~)FAzF+#v}qŰ~w)LAŦz݊+wJ$ߌ^۹TJPߦz\_Ű8mĊ0V"w;K D ToFs#MiN-EȣίhInToVv('Fa2$F Tof}ē"Vەb,qJ۹IZ6S`V5')'ёY/Ȝ9[(HݺxJE8.?8,- M(n)N>.4H~bX|qIniIToRxbfOuscY`v$`8l[FΞ\WVnDsl20 LTop8<>e%+]qbqK~s I*c7Xҽucw`q´8 @eMyY|Tģ#֓` ׿,,_H (HP+D,/3@~<\'-ա @Mwæؼ BzϜqZ1|bi6Ig7X;ElQVX._΢I-H @(H zzP=̹gFv)5͠ @kMwCݽ7x@ c\qNYe\+5ͥ LAŰ8 ~IJ2,>:~s$Tor1,TnX gQnX 3*!]я_pw;WJ (HR =aa2u+ͪI.][w;fBAToR1|>^HQxa/c\,E,-Ip=r#tS#qQyxIrʻGܭ{L{vJzYH"_a.#ݬvn*&MAjn7X#1]"r杧Ś2V i2?g~sP "SŰ0)c2\Hds2 5MK% ﳰ▂LFLA`z2c?ߡ,Z\e2-5j->m\rg|ī"k'^3 s,^Z!⊈kʸw\^o]h\0aH M+a"q#V.F1₈#w;=hXa8<:bˈF#b;F\YF~qRı1N'I\aψ"\f#K_d5NCvx~#g1~"q•2 Io0~p(5dj=NX>xc1|cbi "? 0f #_hċ8'qql@- b˨1/ '4VJ- D|޼dj7V#ӆ?3N ItkE~'c~"b~sJr\W_kbp Ir8o12FZh:Iz=+rk!7\ IS=-Nl;OwD0q^3x;[K+M IS=Ÿm'i"#Vg[!♚h2Ij5L}>QqzR01JM Igf7a̵ H 45Y&5S h*Ih5X Iݡ&SAo`$mjM>$X "(Hc $06 (Hc $06 (Hc $06 (Hc $06 (Hc $06 (Hc $06 (Hc $06 (Hc $06 (Hc $06 (Hc $06 (Hc $06 (Hc $06 (Hc $06 (Hc $06 (Hc $06 (Hc $06 (Hc $06 (Hc $06 ,/51kFq2*+,⦈#.4\*Psz8t"V{ykHWF\񯈋㜾F&5z\|/Yq>_.B~_֋C]"cFY+a栏=#+NŰtusY5(c~04mL9% (Yt#q߭]y8(/7hysz <2r`K5*{yn* v}5Zs8-[ L s}ߌ߇d}`i L92KâтqӎZiqQчrOxtă>>'@"F?t{6WVwoyiSy*>X)71of6xPʸ1bPqFęy58uΒZoɴ9+{_9DX+Yx8e9}ZlsfoCij"N8݀}^>_h烵X-o#>qPemNyٰ<>3S!xsZ7sK#^Y fqs#'-[Z><\?ߦ)3sZ?&$GseS㯔ŋ#yYи0Pd4})_zDyNo^ƪ5Jy9<34ϫӮwWʧE,'iq_W(H2>Bxg-D WsfƑ/3$ljgO;827kӜYɗG_Aʙ+3GU%hWˇ,$2{Dɥ?ys@bE*VlW}?{pMl$e2𯜯(4D^@ʁ9okWFqt9ƼBAɍ39=NފHT.G}b7ͥF-cF׶k_+Fpd\:Gi~^ +k\VyhտHAr;e.0gBZ-NG7*(H%_1bb^J-NG?[GDA Ԉa!r#_9,N|?' IrFs-g<%Y2?ziwha.AQŰ@yQrÿ+5%--^G7a%ٹQVn#wyy1zk~ njF?Ű8y$>UE<@(+r]u$sG>1-Fަ) s7O)h,Zo} #x|np3EU1|QW\tlGy\# ,_T^`^X-\δ8$oN:l9pkt>^IVfȈ"k:` [U,;&OBGknNYxA%}Fiכx]b%H.6* [gh479%w^y,,^d3ڽIA\h^T"]hOʁ6`sZGy,\h? eMnD_T,q;ï||m]X}1>ܼd3?}` ~ݵ?|^aov/-3tӻD ##k|5訿㶜_(H@zs22'C/#ߥ9>^9r9phć|U ӼBA_*'"RuXgka?W~Z/rmFt_!8ϋҘqxE1|1r7׾ѶiF|w.^Sl9K+ܷ _[V~?}CKn+{ÂŪ22ΌHIwSm1|Cn`j߀WD1x˔ElLL_ _i;Pl9 2^˞oF3RRY|ny>GF&z~gM[gRA9ȥz?MFnۆۋF5 ))sq8֛);o]%Lė#Ɨ?lNe"2 B]v*t$"r6dB1rA/M-$lJ2"_stb˓3)륯^xv9)帾hh޲tQO8{98uqэD<igjp}rN9 B:R)*Zެ駗7_-<,#mr-O5ќq}7^A qpqK!ҤjXן ߭[kF=7f2򕐛(E˶w UYS#(,޺>\LYe9n5vp9(_WGF"I-ߪks&SZvw-k$r6ozVvұvp_%x<ޓO+-h,اs+-Vjeۈ3 (4]*%3>lJ2rD \3ڞϹ»ne׈ ~E[9oEΔ:empȵڸ~ߋkzs3+ٲfEL~ͮ[!_4O#P(FUޠf1h[_7 qB#+ge亀9j#hLB1r}pihoWsJĻpo>˵6Iulɏxfk{PY!zev­[[{qpďs:t"b8.6E1+KG?daz-i. r+4wkr6iZ#b*ss#7ӛk/.l6s+w%KFnkYOlDAq\ s#m]^^ݦ-AG'3-?E)TGWXv=**o\5mʶ{}a&~ܳnňHO|p,r,iz۠[ 9+2Dnpm|E,T?vt4vO(OA_6.hMeӈ4ם#-oǨVn|>Fжcħfћ-BuNRq^ϸvFeXzǗxj1|6m|H*5\6M `rFӹə$i35WAsNqWk`m\ yY??kmڟw\9o<ДW,HU>GDX\$fH+=8^nڟϫE|)roz|'cjɵPQVlPeup Vza6ۨKv[/#T{w sڐ;*åLer|td5snpznz9|K_U:pV-4r ,E̮;eDeO) m^loZ$WOxc*нJ*ju>k<=~uN+ڹ½eir_zpH։FyX-W5r_6KY|˷okHGܸ彡`֤sfb~5,\.'_);sw6a$+HE#/h[lY+Ty۷4 y#ӣYgDZnB=9yh-*?,\MS3𜈛t:?,*_> Y(%;u##,F>8Dy^g&u8.۴<ikxb`{&º㭼,WzfRO Hw=U _TdIM;(V|_y>+F$r o{1^-WyVίa9J1ʷ7-+Yy^e 7[C4fqaaHf'iIE%G f!?!R~E)Ԭ /918a TZ,HƏFЈ7kY:o,=w*:RWiJ*ߪ\6wz?3¦JAqb8IeCMRx^1џL \A2~$v+;+ ȅ?? H.EID|1׌\Y6||l{H#$F0Q;>NRY[19r-Y`nTA2~^/E,m7b^ҝ">t9b#R%F,#Z9Ʌe悈jҖϊÑp;\r)`.LA2~^6Fl#`.qD]b,tr~L0GrOIK;Oگ\W#Vd́|EɇJL*QqCBQijd97*_>9]xF`}m{i`S̿ ddr-נ=/\ZFsfD,Ly/g]K[@#n rfw#&I^>U0yb+e1xP f;a.,ҍ{*ޞޘcrϊY&愉$̺fd12gF>V6^IH}8=f2D~7ܾ*ަy{cqCķ=F*`~8dS7VS^cf$3%wëe xU?jr{O;ᜈƌ||m:03ɢ䷢n!06gǵ霉 d߳S'4 _5-LX^R>2+s,iǟG-#3{C9qbc?n"0[p6N-< /MUl}{[5[x{ɳ )gНȿ"vU7k)l`C5dQ/$0-'EhMB>uLP1?,Kt>?#P.3 M5ddFQ9me-PwT/QWc+ I}͸IxV **rǀrC~'WV&-bV#b>o{[p>"~T]a,7q2:/-<ل(lU 7(9C*~Rߤb$edFa9) ٨|&߶YQ*`dn3U./ '*yCC#n&1g;$C"+w]ehP댈GȌ1qsA xr u?]n ,Ƶc~:bS_b[e\qUujUa(:U ׿Ab y8Y1EQ G\V -rw,;q6cd<./:9_@6XS1|M&^ od򵯜|[dpwVm"*3rsG9n.dؼ4- >rxmy8szy`vvq{ÿ+n/7\Kf,*cɯG$cpwvnW^LN8kZG9vȎ;U glEy>8bCٸ]Y8%Wg~7}j"/Zɢ6vJ^gOi`!^WnT˛1Fi"o)L:fm >_-֍EY a,\D_ 8 $5j'[ߋ8.h?L. ˣMS\2lEI%i0)rgrP#W "Q~][u곅Y37rK*'[f6-|x#"} Mm.ٹ'nj, щg.`,qcP\cE-3G_q88np*Z=%9wls6u;3Nm|eψر:MQ6/n5'^]iS&pGwkA<'ksy$s ~ˋ909[na{ӋR ]ޤh*^G<.-#ɹu]9F!]ˉ+Gl79~Gnol||;=3gY|1"_o8<>?h菊p^ 98?S DfrwEWw7-#Vqdy_r{;$o^(H^vm/D|k 0nz^1|0ʥnsQ*wuE* "63R/H7[F9M|r:q#%E zqg,*7=I 6WnFQfRAr}5%Zոs //EF;Vs89KbPl~wG*72lyNAy~Nya3}h|먵m݉,RZ\FOilz}SS ،B -l\k0gp|tRkBmsB6޸mIm$Ƿ[ח7>mfRhu0_ q\6WxewIO9Uq %/̷ڸO./D^kNnWDu?R$$G&>h~8<Κ| _r|–)ܹBf !_~o$7-͎Gar*]v$'ZbdXs:/ IVxxf͠s \zMkA[ 92_uKK&gE _6{)o$?fQƆɷR^\-/L'G7᭚sUmxj(H^>~[7h)O7ĕʺ@εs9svQ#]trCwDmλDs]j܏OIբ=kʛ}ӹU|!-+f-\jsy8s|hkUv%8b^1|lH>}MÇ4|eYz[ߏ[v~;?p~[֙7Zi{Xv}ʘ#ֲr^8'8OG?%]|gob}"XUu.7CrN\nVKl h6wgbUF5onכW p5 ə˙YdDޠeFWwox:C\%~놾,b!#3RɂdeWNY9`6~zyA~E Bew0A;޵aj,~y>ٻ~^ؾhq> i>F|O{Ҳh#[&պ-(ZUı}ē[uO-ǐ7V- 93[-R1>Xd8 PM/FbFb"G83OE{6q8aM.@\ dS=/;Yp&" nYjr1rbYKE9pSQ{#>X47E^s~oPR@M_y!⼈GE}b-UA=)1ufyk}~^W4cE%rW?sr3nlW}W\ݰ"AYB>;:9>(Њ~Oqm,3JFfˆmrp% ËvFpל|='|_MCD>Y3阌Y$˙T`qSwY>*9->\SQd_prsGF8~~wPy_]y9E^9co C~BkJoV:&gifH>eo5gxEXĨ|f~<>>fE.9}b1A~'M]^_4s9.?1k |j6e>Nv+wlru6Y@t+oWbݱzTלQTnK}1wTLެ qw70c5An=Fr9bsFj`̷&ڙ|X&ol`lMP8<"7ᏲZd_׳sddr}w['6_]4sv)e}CW5p\˽Ur soءiKflgHk"w]O-bQd|_-7 srN>hhڌDYo* k9o//kMb;8aՈ'[Ȍ ,fZþ5ȯ99Y%L&a_~ʕK 3Z ays sz8<:N2$ŋ#ӼszNήjڀ=7Y}׆[|sqxL1]__ߔ\zb22XW%3}V G.}AK s~WsoW{Gw•*3XA9'ϊz0Ob84H"M=/;sr@yjZ{asי69yH\o7xr9Ӛjֈșy}cd%99M)T_S~DW5'7'7kf[gGlцDuDiHV|*lkuX g] >pfIT뼾<T^C֌Æȿs=3 Kbuge r șdmo_MY17,,gzE"n뵴؂d Zfl^ͿgǍj_|rA dcNml횲WnWٻ,W~YgD_3b\W[.e[oˬGלk5^Z!l8oVԢs*!RQ?3!UUjst;,Sڳql̙Ϧ:=bksNR g?#Tϖk-W v܊bJ2X#6qјk'=[3f bl\lҚs{ow]%/8w7V!9O&$7!ض_!,lkfc59C1|m'0w+E'̇CȌ - їTk F8e^P󯱷3_>r4fHnW u;jC3k'#Wjϋ{XK/fl}|~󆈝s*7xK1Í,[.z[fttƳk|zf}Elokɑ;7Po+Hֹx`F}5€pt7&+ߜb@m-YdQםz{>So>q3zj!edFr{5ܹnѮiZ'@T>@zNWIE-{;õ(_&66f4rak+"MnuLVesrܺnJqlaZrd}Liۛ!3z>z QQ'1Wph]Ɋ:;2osm盜 0`c"֗Aăc~;4_cƐlS]X+Zq$rGn ~|㚶Qr7IRݨ'ZN\\ U|}|RΔF}1\K+3sI'хұqɟ0-]# qfM?襗oCXv\of^x^b]xn!b~܅דӹkjQޜM##֭ar0[vgnฎHna\.Ǘ,wxC3joϛͲ;t<Ѯ5f^WhFӇ~;7g} 7/DF#njsگ#v]wߠgFU:5.w'czm &:?![I%[oQ`m^x>Ff If+׏K a2G@F48^4HxL( +ظQ &l;d裗ck?*.Dȷ Je6Q"}xt3V3e/ ˕. ;$'w8_#dTdݑl &.ɞ-gn`HGS B.g /dQ>(-;X%Ɛkl%q7z.;+?o\QfyibX H ۾!E }Y9o>Hos\ -H 찧$MTkJ/ɢr\;r%g l$][1Z$̝B<6M$sI^cYӝrG !!;.ITƐ#nkoB"!H aSS0 kU2a '5̕勫t٢Zk '+ూ!*-0_@!L:Rc-H^0Kfs%vdVL*`.9fJ}qNcR&ާyIXCE3yjŒrwRGlf ɄZٻ4*Kxp!| ^\ ъhX `! pro>R:DGlA@ޑ:2aSCed% L6W_%5Հ˺EM?0-Mn- fuln!ڽ$7AFrT)2Y d&N>̝,) I^TfƦsGNJ0L5YN#|7 h[cEXHq}&PYCe^G5Y *Fj ]#2(fCe"P} rBtk(,-UdʊRߙ,(JQgd21707 3ga2h+;LZ食RuBT о.,UdJRU !5i2/JfW wHqh~I5V!#}"}Bs7%EfX H*$JAA.;i"zV@r%I9} W5:Hf2!B€2F:^IJFʩU0OwKNBCI T Df{>lTj$4RV%T|:s4w+DUU!D k( RV*"1%tD0 i%x%4)SL%&qk !2`p]CRXB*>V,$UI<[1H Q[}+j H.) !s*RA!G KFݩ[pYNDL@2j*"iB,YP&UTCBhIq€vSrKH9 "0^,A !+dt]CaibhAI NB Z0XYVCDc}t<(jZ5jsEVk(hW_e$Q 47UPMF"D$~**!#^v*FQ@Rsha@„[JA̷ZYݞj*4ҟ,`> H !Iȕu RE"[. (W2(YUME̐ BB ](+S8rd[;$E9(xCRLeLcgɢꣅd5H RxOgM;K},5J mb1@0T bEI"Vn[$^Ұr.N+WUBhn3C(RUaT b-$"DbΣ/!Du nREY|'9Y€,rX8wm"D/!-y' ]PXZ.YRU@ VôJ81(E qz 9Ls Gc}xq.fUPXXf/ :L D0 ia5L%J+igF"@j Jd!Dy>ږ?AfveLҸe3#6ZJTiV6!}02׀#X͂qNg@m\N~kg?) ?#]d6?r;#}Ҍ' ]+S0t5RiF@~䫜i dh:1eX+dCcH!w\~5Le$IMwX_EpOw5U_;*)a3BT1},OBy8>rOshzD\^ f#~B{T"D띮ɎIP@eN:^BLzf3"/HYPǫ Q2ܽ,2ސe|W!MGcN>M쭁X=}'.lƯGY$!h!t`ʐxEf^KK!#Ep7v@ >Ԇ7>-h Cv$ &Y ^4 ]9DB:)+˟ L8weDʋ&5~@ 656Y uKԧ8x[X2撅C ppUa!6*}N%8X5Yx7  JO"'wZٙ@~B!Iq@qC^r'ne 7K?eY>zaPU"krۡ2("ZCjN(FCJh5+Qq6EV>!9\5Zh~ŁDbNKž˚@!GM b"nZL/|k`U*fY5͐n,"O cH8m[Lim Y/B{4cL5'[c~^!,>%Z>=6C#tdl!}]ϝ _|b`EzBaS->cg=1>`qb[1 ]@5^4d 2npy!G5U\UȌQT+}|f{EzGi?ZTگ@_+d4ڸ6>D-j !-j q}Kgm2_+6w T#%½Mg>l#TÅqd}?k5t>sL,dOb`> ^T1] 9L<0lzo:%lG5]8A:?|+v/QUc֢B$Y_ ؙG,U; غj|`GUs-g"Bkq%W3}E.\h;7Xy}N!lR@J?3\,sFܑ*~S~A ;u55M P"^kGPLQq2.:!?5- |zC& ط7{,+f;0SaUT|t5,憽 +`FmmB/j1@ . J'S& _cdTo{ʣ]gK4ɼm'~IkXn%B= ~A߁&#B!r8ϋ#'SgX'ۀ[gW1+7l # TtJԑ!(܅~Z8 [D!Dg@ϣxÍ|&bs̞O-U}P|YoXNj@{'-{5< ~p Ryާ "zN,+wfhz{ZQwPv`߰ m=] o` /m\n̳ Q~.I'wCC#gKdZ` f o9Qzxœ UOrqu>PE]5e;sW쪪sH2 #g*.I)ʍO!w᎕;8O3,$oʕ_wn"ՄL@a2g2w0])C= )_h >MHY;wcae<7y[nc\8W`$(FM`)9 o=7^2D:$5W ~򲡁>zC g ]YUi֕ؕg}ǟN'4!΀Ր}\rL冐!ܑ*O(,жΐ)"SIot4f8 @{'oz$wA\2 xI|֚ҸI#P?b <8 %|GWroK,ާ3N0uYۄr)LoG+Gluy opg0q_?io<ʹ?/k7(Z2=W.P7? DlnfDu%O?ֆ4/D)k <  { Ҏ v:dgbF|<<-3?:۷<9ϳS0@^E?/@vI#63+l8n<$W`]$C.&o˼%)@L}ژ$D>rqqA1t_!NXQeq;6tj)^ Ҿ;uO< gAZS7_oV}%Pt oRVKn<&N*}({ˑBdݧ3o>OU9:7wmjΐ!wLy9aގY9n1a7&ҍ bC.`ye;CrɦyѺ." *w8] 2wY_lx xvgMIӫdW$w-M`<NޟOҥtgCC8a K2۹w};09W7Ra~' !弸%&D{2<x54u65::Wq ~?`9.pvqI ɋӋ/$n\쿁NtahM3Sᒓ s^܃Pd5Qf;1SOjiCWk*DvT,:]X[o *f;=1ȶQW;j}˔od]KfEӼUH&#:"r!WPǯD1{nhɬXWۈvu_q0r㩈s}r!Srk^\[J%";*%}ym4ƏWG%#&KEG[τy93Ddkkd`1sih^]]rGycM4w .;3>1& VO ȟ9e]q9pR; 6rA|73eڱ'r<¼.Y؞/KvN_$aP#;BN9NBdI][y!F7R;ڒGC[7Fn%87|jԎ$H\ b{~ lПbgGl]`$ödO)4Jl>niO ~LȹT?"Ox0̅Ps5_[P[>PE]z+>:TyLw6Of8{F&8{X 'Q'GQTfy/_;Q/ޟSd%p6vEU9DǺd'ޭ>צ /a^irǁ)걟S@2;xM)x$z<]wG2Vt30KVI\no'̜GIw:X/sIćrɊSy8z!;C6:|Pc< L 3[qwvާ)+Tsplu&tyޟ)XC` r!7XWz[%yC]㮻mPO̱ \N5{^'&)g6[%w%PllݬGQ@R4<*[|^Alm`2GpycbvY%A\#^y6΃B6K9Z;e]˟gj"_x2ǽ?]!KDMRs?dLZo՗FCvssؠWS?h l!`}E\C?5}"tH"|J% nAʏ![)V-Ժ$h*VR;%撘c9Xo镊|zM$5l .0|(U|_/0xQYjivQ]M+^뚲Kv ѧW{Ts.7Hs Hm/ χ|c0%_sBGm_|U?/F 4Kj؇( )rK ;ъX R`phF2E>2%iX0IS5z^xJ?XH1jѤR_Js H1wgs('y "d/3;Vwf]+E={C%GIdDr_,MTŚ?3կTh#gT Rhn_q`]Ca@xK6@#w3 w2#/rzws;O1咂3N*";2Y!7Ҟ!u pz0OuMB䝓ZWiBZJ"x+k(ͱuIFCz!B$# p?qT.r //88o7t@Ig c.A]Xj(_CnDNPB k(9.D9Ef !ie 鏖Ea1HJ(y@ɛy$-iCNJY.9!r#RHc*# nd]C[m0я!5y:@l4 wU yL9FO}i[  (ݟc7K҆"<. Rnxtxnu slg0!㨓/J BdKhܦ/rP/ߖ*D]'|͝B^6Dw v{[wL Wk(tα xD)(!g2L]04W0EU ]*Bc<.m*0T*w<RCk(tȱ nCFJ BdOM>OxՄGT #E=TxZU}nS~~\\uOۻW5 es3_PSA^We !MH O?O)))9Y?h%Ep28PjUj~.?5͹ ģ^Qd{!CM walt|[d'[(OqNϥ 3SZltA?Pߔ_\T 7 .w :-a0!JM wJntэHNe ާuэH;!}QߦJ3//m9Ej9hVu = j䔜)Rz& Ԏ !B r(d;ȲмKA|]Uce܁[REf>]" n l2gf y^[ȾcU/™nsS/:ܤ6NAH pɉ@]r9};YEk<ceQAlTOҠ-!;0_<Ӊ9o*# )\ZWRW~zT!j, ?N3 GC^jݚ \%qi8Io[]*%ʘ\#Uҟ8_2Ɨ҈(y">͋z$h,ļozT! s.) ځ)CSRiDgG6r5.9Ӣ 2ڮ~E]3]5 r.ҢrtG}yBȽO3g^6D+mQ_ʷ??FG C ^ S.)J*:vztNDlRQbV>p~ujFd7>9"!@l~d1E?b^!/6j_JUs.r5Jf|[ܹp!rkt؆?3 mfa#븜0΍r!q[\ uN}N׈RPǙE?9n .9ǍsBpnJ*2R7k/Wv]LjˣĬK% E?@ި'-fbi&W?" @uOԍ s-.4"\ ?/D'x/(Lf LBzEߟ:5JY޺B<}&VE|qTxO5 4du$e@vz͹KrE6VPX Pi#:jagD[G:7\r#.k4 uM|(gvT)g&bae.-8aHzT5c8%g^| 5;;ZAMo П]r9N_:#,`өnm)-C,wK 009A9/tP~۷xzڄ)_6\ 8``9ygRGXO}9`ʳin7hm&V Ϊ3i(WNwڮ*oigoo@tɑ?&Ac#h~%iV·̔F!;9hDJcﶍ;zˁڄ]ji0孌5rs]Caq<+LY[`zxiX+h03Nj{bKvPkx_%`ۥhًJzü? d>+QN]؏S>2Tla<~9`c!gfߗ(.WC]}S{6 m>0k.I z 8Bn| RG{XrSXpw9-'gފ4+'rE G>4X/^U3Ӯvv&b.F`iˍ.Y yԚ҂sLӖp~\ 钔ϦAP1U9&92gh_7qnJ>.i V8۟ybGvܵwdmyKf]m9n6ܡ@7Ia7-@>:n,\1( E?[x]Can!wEb5!Cx ؙy:$Zia_ntiqa./7<(䞦) p9-5ZU\:ud΋~xhfꐳ _ƚ)l=-u<|[jך}^ m] LT_'_~]C +X+gM /^NG`<1 v!ӼlH?y;D'D d[uyB%@S> ޱ{N7Sj(qo6rTZ 2%IStZ>n1&a:-n@Ӳ]2=mlg΢mUPXٯgFCML/1Bv LasإqCϳ ׸6nU`p`Orɑ˙P)t'o'䢘R~urP'Vg<<ygL6\ tqi$u&ρ\ݐo7|%js O_XW.G̛!?i6y+x+n\x߹WoZ/, `F*wK+4% F*omŽ?]GvF*_Ɂ5HO] \Ov~$[T)V^d#NnE|_1d4RQx|Cf^9vƉ.IYuuKMlo`iܺGq(OE?cg1V@audW]mNţ*WCWg#Ӌⱟͥ6Ax[n8||k6Ѽȱ0`vUqGṇdZ'ZJE8 uYM\Krs7W'iB!>!uT^3U)Q7'ƄxjqP;LwF m{tIq ) 9ACiNh |XwZ4quդVaQ.;o*rq0d_#yfXQϥէ\Z&c{ ]9f 2ԭdؐcS) 7ddQ.3dB}EO?-;#7x;-kOo >* Y^d^&g c ȭTI%O3 WXC q~Š?s<{9- 61yba#dhO?yb$'.HQԭm/f3NŖ+fo S0w wj:̠$wJ#y<{%4<Э?1 [ wF6.RMTٞɫyTl\lDh((/ħ(^./J,. c۶0 v½)e~Y1ŏMq-RÀq[WIؔ?.؞>ԽT3L]ƂNҽ4%ިǑ׺9/im5Sq^| )A&( Y9g L2J5%Y]Qi|oY$W|~8IV3nћ5 9^;*Roxq.PixXnV^ w@ qQ[/HUmG b|h\i><-[gNu*gCc>Bs/XT)w su?dz;u!K(:S3X<yK3W rI梴ܜyN{-*ӅnޗAx#}3^ nES+LjVێ[%7NI Aќb-9@*_ʜ`Ё!+taʯC\+zV~uk-dwYX?!>q7 %߹| K Q?s*ELYȧ̰84}Z5(ͧN?9<ľU>RU'!]]r5L֖.řKټw"4c}?6#WŢ" ^^}/2'w^c]xqPԙk}}m3k/ڼQ@2?0ɒ~@Dl .'0Mv(yofM!2eO/̧yTK3?owlhњi|ςKrF"U^OM_ӗvLwvs㑥/->x6ɇ~y49VqI(ց~OꟄMgJsʼn,S@ەxaA_w<yuk4;>ia b3MEs]˻w~t ǒ}E:ɺ<ɍT+7Mz%LDՔF/[IENDB`parsec-service-1.3.0/doc/images/partners/linaro/Logo_linaro.png000064400000000000000000000356701046102023000226610ustar 00000000000000PNG  IHDR~ IDATxw$g}S䰻AsHBB@- `?|/&[`8c&#%PE9KҦӹ?ݙۯ~Stw'B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!B!☤w^.`)1$nsdY3y=L',&]hq!יu^l¼ח1OB}8Xi;U3{֯2SBc-w:Tv.f1g+Uo[#!IJ"r-:&EF$jRM 4q^mF4OQfD~$j^"ՑBі$(7b !V( b1püHbE"{CJEц$jn r!hK@D5 Bі$j>@BLXLfdD^$ZV =YfG$Zdױpmdfޓ[[/!2E=QyY}`Ja%X@D#0iB B!"D!DC$!h! "!@B4D恈cY R@VS,c_p 1?3-Wۀ z1E \f)O]@枌cY`6$jzL{'}ku^g0^KKm GLpyk՘l,`3ISl .Lmf5]G}4֘d\a=|1Кja z< Wgxiz00:SZz9Yy|oo3_EѥHՔCz橯t/]]L`my^Xy fcZi-@{51O5KS;4`c|~irLkaloԚ |k5NtQM= aӖG6L:c'.x̷_fpl> ibC5JoDivXeLHs;a{o!2 3 ˆ;:N?j!qtM;_#V/A^^ol>N˝!fM#~S`mvi? ZB#*N!S`j }+^>M5vxjbȿ`:-mӟ0%LS(0T1soeLQm qhwB^HwQ{D|g0Ka| 3WFO Df/͠8P[c>3zםKjan q=0G DH dnXo[B^H+"P97c|%`"k15H D(aK IzOb߉7j 1ˋ|c 8;Μ'1Ⱦ}ި? 6Yj~3L[7&6ZF`3d7C"\'rk %b"a}Swh,pf-Ͽ0:jZ@R53|Sk.#5Sh,x)P>IX 4|hǜ`& 6j Rꑦ1U s^"i,O3b |(0>̚gK![,h>y:r]$`-j!r獺4_ <=´}0yߨӣHH`G;L?I#'\mkѯQwFi $ M熼v /,cLafF} 3P"N˰ֈq9LXKƇG1YFQg`t4[ݝ#Ec>Ikf/DڇZ"|$ IQ3jvb%4b93*. \_O!Ҿ%QF^ģfr]+4T a^+N0FY&@3 YeIZ췑i~+Dگ ؉7ˡ#yY'-l3BTf~UFfOzL$6TVcsha4ff>á2![p6(nt=H{RJbeZ)NÌۄ9Y<ܝø 3,{; v4Pb$Qv߆/oHj>#ah4˜FG5* Hm,qrg7UT4}.!k0~Y6|!0xx?`^4j"jHpVj$~^A=lom)ZU0 0 HnY̪av4\o,HV"D{.ì;#Y.j"DJCu3eLƣˎXi^#gZ9BHn$x3!ҞU&jp^s a fV4v"pD"mkaud=m %io,b)H{"f9V8DZRt?% %1DtKC9\<LH20\YP;\s>^r^[S DNGpX6SEqvdQ TW۞`l H5Dt>F҂e\G"4?5d!ud&rOF vOi Qed6 Vw29ŷ=dJj! H+wnQ>:-9z-rݻʓK<&:M ޑXWB]Y>2?spa8ʳfz( 1 my]zK/OLHrqwLJH9\ƿȜ,1.g7kgɥΎȇH<>+#_ ~#L+"( I`Gd),42}ԝY a;͟L=f ( ՛hE=-dq9g^v|!^yF~+˝#@n3q {"d10K-hD1}!o qc~{aj3! @0K-Iι{/?~%nʻ]L;ǰQɭ!jי.x;4]9:n/`.>Pgۄ `ʣbh28=l?w\%mc.td3 pdcF,' 7s<&Gxp{!j?+*B֗fd XP ,)>"7~ f($f3jk/xrݭTUL;&h]wmͅ-;{mtS .|B^ ̈Ü`EBٱG6G>1DTsm&e'JȌv=  @VX* aSsIλ౯7(13 Irҫ߻805h2%a\lݘ'QpE(Ylݾp<<^ғ U hۮۖi{ M΅ӭ@b8 l3qQʴP&57oAHv4muh2{(Oz8D 89'Jsֱ@C@k97lLaQe+}39U^hZHd!x3bu*]=ogmg G0 { Ū3s;I2멹~3Q:&<& "9rloj55gb s45duGYs>NyxgBhn6bi0}-`^Nt2yR'-FJ_2V7݇_)ƪ~FM9{:q.TA95 OLPrͭLM{'r}9rZJ[q`\H%3{;nSyl1d?#嬷XduUfv,wbD! ~K_*Vk>' *5:v_)Mi6FݏW)TƓ(Ksv0uzc~.ՕvC&4d SjҞ6Wv6Gyg;fmZY@f\h_Lv8q͌Y}>B(`Yy&@;WI`QY.^!.Ն~3 %i@O yL*yK`j'Nt g1 GǨ3X` r4lLulI_gQ<'$ sýY=2DzM_Ǐ*&x1\& <]̲D NK6=gpAسbٻDMs}0xݔaD)W _7n\o:C2ߊ"ck+XX$I}LV * ĺK83򿋘 CITYQZa䜽`7,^Eٌ=2Dך :|_rfjg͡&4+> ԋX ^4 Mn"_r|M>k s &KMnbA^23W|Q8',P4~6zl9֜K6'3*/$XvDDiM~"Kvrku)u@O&UgjF_أ ySV%pɰܽ 0NyfeW"{ Mz8[k&d yeiҫ9kf#Iyl@aFD:' 4Lrƥg .9{Z0gvOi7x]w-sЭFBY_kGj0Gq6NRFMD_bfa=xZ1m|²,soCyEPKvwӳ~MSm$*Œ.Kkb7W۳* "HԾ.|_QpɁ S1%@ xEtu(@}`ݹrvZQQJlݥg7luř8c>AYy 8*v߽L ;VG6vqӬ9zP(eF@QO_@~,Ezuf 2IuјgNd,~֖T 0XWXOD_`Pp҄)ebx$̿?6ā&? &/3E`,]66wdRkԾ)S["ǁ9ҫ2VutH6)R8Iӗe>]8ʹ'dG̺?{!0>_*U 6c|b2nDj0OzuPYRC9nk%$H).֜MS$r ǯ)Kt4[#CXN`!h>gyN§0'ٗ'-IV5xwXj+='z xEӜbs)L%tPc*MǓl|.Nz#+llJءՔ޼DOla,Y@ʑOKgٗ&ܼ]3o$53MԎ{XF pvIxĺJ2v?{@)`8^F~&,-E~2du0jatHr%lK:i3$z &.@*jwLM:P2?t`js50ߝRe^ΡQ]|/"ơEkPvqSeb=%bt %(9r$U|Th_QλU|Uf,&9{3N3UJ59-r)9\xekG}/IaQо:JE~" ^/[k$𩣌wc5'$1ɏ9u٪{7YX!?sɍHrލwsevO7"krOS0|['H̓?:s9Icf2M*e3򫔭m졙fXst5 =W )ek)bAfwwO8IlSApnWoϪ~0sIDATDJxySs^~Q &܎YҡMmO` /Ďnfms(|[''w 5Ƕn;ݦSxv߽b>7ON;cQY`[3jӚ(̓-DA9k,ny90$AaӋnGTtQP}BW&źR7Aӛll{4>Ҍ5Fc>؂9*+pױQKcv][RKaV>L4:3p,NrLla*qpD03f7`X5k3 '-SoDkE9ł wfwjaf3(31D4dbC%Mac&Py{0=::0NJ(3$L`$XJa&)ޅs>avcʊBObœwTWC)kUbNOיF65C8}Yxnߛb?-yA2jl?yj~,Yܩl=̾dAY47rM)kF-1s? o|s xfZ:%p# VJ~F0cN#zf}_sװ0Ү/4G0e?4z%P5]f1߫y 0MG*ذnk/4V+R XŠW%7o9s{-Lћxݛ.gS.$=^~~> 7qbfMfy/a:/ʴcâ_h6 KchG'?,o:ފ?6o3 O̶P%p32R ,`~҇(Wj*t;m+]Bl]ШFu=U*ka ̀A,گ`1nwCr߫س0|L\:zU^ִ\DU`fe$b7NRP G֤Q)lPN!QP} W*MV#6Ys.-}JcYB}9ݷ|˩F4߾M܃ Sj`/O ?4^/2WjRO){+}Uqrk(K/0}ζa9.h,3gb>PF%-T*Pw5~kb !XU$ڬ7E@Mkv{*ܹGԡ0aiRtNx /A]g !2@ܑoźښ&ɜGgg`? ,o&1K)PUfh,,, t'k B!dMO*E>VGi)([ZM[Zg]R e&026 Ӂ¶U5,sfm`shcYP`BW5)T7Z] @Z5rqS YϠ ƯGL`V*cOyB}&hFwBt$~>TL>qu3; pf|/HlR%_% \b6TzpӲ !D[jVv}̪{8;[赳ƚ |UcUO`5QYCw v-!B,e|=c+J&[^/3W?O HBy:w+%heUbʍ29'{x5KXO_tgPO -!D[[D+TiuW1ϛO| zԔElMe?DMB(4}LŇ)c+]V! 9ħۧ(T/؝B4ϊ -MQrRܱr&&s]=4UT#q~blO$!y@,筕&3A}J<!)P>s8?!(;?P5Rltх-K!V v%p=K{ݸڗ`{M'Ҏ"|v%N_JeT٧+j 2nlXF!]G (lf= =}پ[p)ѨXC8Pz0*^Fg/~lHZBDjQ(;\ˆl\'Pt?,l+P=ܠRMJ =j]_'F 4+o=Kѹe̾IЖF[ZE~miʞŁ4V`xSʼ = _E( yJx!jpУ}R Bt33YIkMpKJ[h[ HL'={yPf:YvYh*QГݨDL*G@v}Dz\Y0D({i֖nD~<;Ki<ϦXrp:-x ?O:H;GBѰMX~P{=ݴd44޶4΂N]/?~0Vh+PħcJ+cf_( |_tPX!=YB~ 1hccxkgk{yOyGy6"pDxc|.Bε0޹ml5>񷤰F \sΣY [)?S@ًud[S HվlKc ]wG'ƒ !ǞH9 ܍LkcҦ8AbWPAtv'BtgVPeߡtr}}& %0{F \ӃLjAɖ;8DZo9Q›<3CivħQg;t]ΒݴEyRl$Ͷt鉡ڣhzPXJ;sPtМA<=c$G9T&='k&b&,KAч p(NM|Tlusߠ@kI=}ڙ_pBkbmCBlKM3kAa6*¢45I-W&Б5h)4V&xiNQ-TyBABHD˹+6(eۏDtA)M-U>wu*VWV4hݢԕ6ԗNنT>ւbe~WEtG@'u,Xi8Y(+qSvꄑ^%vʎXXc;ރ>N#sWCly6x+a3)' rFL{ ߹a /_i_<&kx37Y<Sֈc \Z(;hrtU{<Q1_1jP~&WV錭]a Q4A= |aesWS_b|]/Ρ1>.J渰~RyŀǶBU{vo:5 (oɫx?v.BBY WTac<"eU{q~>7PDМ%-Ѻ5(NrYmritq 9Z݉{˥)T0 ZcXKqtF6﹒(e= },=|QOQ3h9K7JMg^v2B=8Ǎd1Ǐ1V W\9ˏ~1p`nH+WmXBv4sYkwr7|6^Wq hyQX yfO 8malFqY]YK-!Qɔ (\p 9Ɇ,.kV<!IJhq%Zʋ<>}Z/.eq̺S Z[ enT5i*/.WHMd9U&揃U|W5tU89ќH(\9KjUH|pmu0oUO-RzIKמ]Lo܆[!-{2WC>MϠ5'ADUȚw.ҋ)~Qij\JfgxYCoJue(ߡ'H&ՑDe&!U V,.s4~v+6@i]gDFv".W|`dkPL)/BV5BrSFlՆ灶\eq#܍$vm$()5cݚ;/lX%5!D6ZWrOrU˅U?QT)yqD5=[padQg&}~#I$IF I$IR$I$IN$I@'I$I$I$IN$I$:I$I$I$I t$I$:I$I$I$I t$I$)I$I$I$IR$I$)I$I@'I$IR$I$IN$I@'I$I$I$IN$I$:I$I$I$I t$I$:I$I$I$I t$I$)I$I$I$IR$I$)I$I@'I$IR$I$IN$I@'I$I$I$IN$I$:I$I$I$I t$I$:I$I$I$I t$I$)I$I$I$IR$I$)I$I@'I$IR$I$IN$I@'I$I$I$IN$I$:I$I$I$I t$I$:I$I$I$I t$I$)_@ `*#q^"ixIxe>owgZ?uQ<;V^Nqn\!Nt@Yqm;ߍ?wg ]߉_-QA\6^@ߣRJcxnSGˏ痻;ĕ< @3+mJ\9NmxES3 @>\ ߢOXTn}8#t`x1>&,CvVQ|r_U&۱Z"@1>G2xdQ<1Q@1G,tjWYֽ,% Ɵ :@@q2% .vrcd\Q@:97Xތ=}[q83`Z<0=y+q)\'; 7[LfrwǕR9OG t7z|xq|ZO{++zD kZqӸc;_GœxU)Ł.+o+OGďwǝzq|c@@||ܭ|}0#o~h\\I~[3P ߔ5>E!=T.ߍOm]$Q[u}8=J t|ָq<*Mx39 Gqb\5n=/xy<'~lĭjqq-:Qh=Xdˬfxo\ș ǕxZmpEW~^<&~$n_n~m Q%?R-z},5c5eb USK+DyֽC`{@峔[׏w &m>׮~,sve>2kI.xL46x]AyFofrt.!g[Sˆ8o1X:,)K?ώƧ<_լX ]/9ve>ύQdjו[+᧗I/5²Ne}YAx('3;dW{GT!S2do+w Tψ5Z>]D9ٓ>:AB|[fMTdmj)s$@j u3Bd?jwW{f"{h>Qn@| ] t}ⵢd'ԁJ!G&raZ=,]0_YH )ԁ Y2czXWW-gς@MDqA@CCCQxI|J\k3W!@Ga>>~.>(Hvwƽyea/?jT/a@Mj;9~]?QGFqx g].@ t43 IWAxBITjbqv{ :Ǐ IA^ݶxPYyQ=lo_o3.Ѯ0_<0x'N.+P,] wS=%#W]aBI̼NJrTc,[ 𸒽  I_3ͷ:$g[g-xD\^tNq[qe}c Q>6\f ԭxTq= u$9]_sAA>:-z6q{ ::)p䐽Z3hrZLvķęm@$ٻ/33Ǎͺr .ym@$>?iw 2g[SB^.!I{\7sY ]sգo]S|0'ɶxb|o"Wg3gqϕ]üZ.mdlwŭ}+]7+?&k/8@ ttzod`L"z:_ϔjr{ :!W0&ɎjI6V=:k6]|x/Ivgat<$S<]ն] n )q/Iv|uU/?/k] .z7\A.Ivߎf(=ϋ ZGO] n nZo(+_'ded{ @8 ^m K=W⬾A\ģu@|W bX?+? gJ}Sqo|xl|H8S] n 9$מ[X뛱eQ@/^#kx@\О@ n JI:!g8ʫ ֍ǧs^}@0_8~@$9Ƒ1|v>N+ta>*6%IN "=]-o//d z/:N+[|7mř}@0_$a~=jeI'N=#[d܈;X& @kk I yTpes#V?~1z z/8Edu.Ŗ@\*~5>&x\\ @[ ImθJDyvGncȯaqa#7]jaPH;wpwW Ƽ=~(e@ ^ ILC⽂1Fm@'$;ܟ{k)ܘ[UUs$;ǿeo?ˍzA|.!̫ڿiGB~`n GbѥΈ+_ZFaOZtO)%^/g^x4s< j זN<:o|Oܦ,9r\Z7!8Wo[]J_~${Zu^.Uv=(_|.)ΫC/jq\E sUoNcG? Ivy~N.̍XbQ.)FĽzre'%2+Ws8~SQy7~9S4xF @GbS{Pt\-.-YSqBngUukqx\y[A$lMkr?M @ ~˳8z_uKJN ]Uį/-f@y0(R,7e½9}KL.erB]ǮlcL Ñm xg-@ AuU+˕jeHzFj2ccV^}U, sة>?aj2_Fm~D|GQ9Ar(n0 \7qW,@ ƃf=]?<<ܿgkL  OM,7@6^A_dnr4W3ȯ/? LHa.-q8ڷ.>3(.>چPDߎ爵!v22f5a.@ I5K-שfuhYz3B-7M9~'.0 @/hUCWCq"YL:Wq!Vva+{@}X3Ys_ 9Ѓ>S9 {Ks+{ , b(Zz\* OriP<8@#Vwϱ6>~VE 6fr>V._ jWpsS<4.{Kdaq\9q| ]O=6`p<̾>! X\9Pd}~hܛ.Xng"  v.F@Kjy-8>Ie)xb]|' cPҷCX}e<0(s\8aܼgĥ@ iwY2@ȸaqZ?\ q\љ@ 2P9ēJ#g="|om=p綠r^Ww.7i}l;'#YjQF21 |}-Xn? @ ?'rrh5~Hc?ɘU4~'[,7qh}j69Y5w(]Ɠʣ)='m<׉f%+ ]~>wt8UzP4ZȮqqZM{_hX}i n_KaeiDaǮwױ \dɴ},qV.Ї> mm3}ƃ:mǮ9pb gf&}&~5., ] |˄qmvu@ uv03{Nŝ5@ xk*C sS;_TuvYu7sWp.Ї7>EƯ6fr.]op=Sh8] @ j?owϸ0qgܬߍ:.WOǽ@ tH+/|;ߝƮ=pr&б8)~g*qNg+'7 s1k Io61\>#'Lc?6ڙ @i3d̪GlV5q&&g<[H|hcqGg*+Էyи `nv/8FƝ-3WML]@D ܬ{}e)̸xC+r."}x@|V<c8wr^hXzMInT :Woҭw1ؖwOS0pc73>ucC3tŇEqojs}91*nþU=g*'u.`nֳ ۽:F@ tH%~UxOmǮ9Ŷ_,7  +Ƌ:lLÙ @ -Bn:fco>p cCUKRpGqg*''ߋa:qϸ8qgܜ p{{tc[ @ -qƮ= f .~YjT :O/0r.yuܰ}yUϦq.?"}^ MƬ:pn`nλqWovpj8 @ӽ6wdtux=qI\ @-2ƁK'S07q\ȸkO'8|6~ @ޏO1^v-/oos.#}xH7Mwb[ m&GܺUe4aR/K:k@^-hcVMnnܜ  csB|>?r.#}N\WpL<2>7kH|X|ɻlt.w#}xN'D^c85R g#&=,9s]@FzOkƮ5p΄fc\k;qRwСYq.3i|b'k=ssG4?Mӵ|<)NpacӫF]@?}.UspGi؇_ψFqq36ŋ[]@}]W`/&kqq>~ܶP?6.,1|+eUV7@~Yw2\lCKDrGd n2\>?h`?3ZΊ&t1e{};^@Ixи 5\N'W6wܞZ}uϴ WNpu5]K ta5Zo'$>բ'}3)"gy*@{w] &l//q>"oE|{u3зkLm\ݯS tTkjǯ5Es3-}iuuDv@߄XgqNx]#1ŹTh~tU(K4vu; t#nvpq FwD)_jrǥYC[c@~M5 VhhTִ%ΐ@W{y?":c)QC&#M]Κ@džkI!Er'.|xXn⻪G  L833p<_D tc03{>?gk`6K?>$&YcY|\<8:w>@^c1GUnol^q!g[E:?#z|`ɆmK";dɶ:sbx0jiͭ t#vbQWoh6ܦ( )ցފ.uvz%7WF[lDY/ q^ݷ̏40s-d +qtՒiFx_mt`_˽f5 78Gǔp# t毊ˍD<4nxcz_Yy^gtIhuLu-,֨6odi ֈ8Ku|xѹZ|xT/q^n]@ Iܨ-lsƝ(5nO4t̜m(:tz?ˍZM^C˕DF*$nI}xF5i^=@N 2S,7۫%#;䳗׿ަc}@N QqxXnꇐ=,&E,ط:j:gWϒ,XQTsu 5.|D&^'ztcqzՄA +6Qa;hI7yzTqH-~/:tBo$lsP&/},^[uqx`3Wү@0_'J,7j< .6:63~1Ռ&:4ƳrWlp@oO=a5맫]ǫ]Us,ݏ@D/ψrVw)చ}x!»/mdb[s֫<;W=z,xr*KKUN.^ܯ@PwT{yI\AROd%ψo+};tx9VP~~[:tux0oL*HH_,W7oc`fI_<׿iܽ/?;y~̙J thm/ψF{Kˬff'Y7ǫ/9[xHܯ.qrZ2lrUqB\L^9*ʿbw+7.Ȳb]Eŧ|Ӵ=G#(.a~dY[[07[ A%IrVK1Z bü%Ir>Vaa$%:6= *IWq@ ̻} Qe&fJp}"GM t¼|>粖y &I-qI).`a^Mv0o˚ 2$Ip=k.:&޼?6H$[Ox#*.@wj-K "I-jMz#*.`xa>>~E7slHd=5.hT%:x/Ƭ~96.p/0$I⛍@ei*hnjV4$|6g3f,W,;O̍yg< Aӱ$u\J t0_=^ {]{,`<@$"Z:M t0_?^"Wq8jU֡}$9NW] tCq;RigU;uȠjxA&Ir.K tZs3/ HX/l$y;qQ@00+o̍yWؕfqx$6;FY]|xX|H07qnnrg$ZUFY]|r[/Kӭ݋Sb;LJ JI{(K t|Wk͍HbVe$w>@GIP07 ճ0`%ɞ|F[]|ָ{V07LqNYd+Igō@ev{Ds#>Pnc__>SP|@${⚾@k&~k?;fMj/ jI.nY=M tvTnL<-nRWϦ1%ɮָ[L tx(wF9 +s xI?g%:|/?͵[-Ewv}$;q^V]ˇrxjAZ#}x0IvL^}C t.L=ʫwͮp18&ɶd<2.I t`a6vW-G%5X&F<:.[H t`a6f|*7pB}\3I~\ t.L%g C;C}il3\ t`*atR|@8񈸜"}24&a t.L=gċs>=C8@$˥ t.4Ļs^?XMgP}A7INq6] t#˒]sEo7yeܾZ÷@a>,O\ůu{]_lqxA9>gqL|t:jym>P,:P70H'G>O&@/_еXqQ3g4B};xh\ę]w+kqrU|4PC}xIW=L._ѵX_ -e IvAg7:C t o/m)Wg!: 'gr /t-D6u](9n-Lhw/O7xL|34@2 {-K]w Ё>)$eҷ9!:Q>1*[c1{$)z>,HC踒/@=Ǘ-ZoGǍ,4tq7-g!:Qڂ5k>WC}TܦL$,HΨՏ}T8B t|W]'GJ<Är$07uHgQtkE?2ngC}lv!"Ktm\)f VPG9=$z0`>s碄{&tv@@F2e7߫h{0XU<,VQϕC t_|D\5xj%- ;q!{#.rƃ@@?Fa<qg엵wIK=qSqyg6tcW3(Q)ģDyĕL.->2O+<~<.,.:-gO?z|X˟]Y]@q2"+>V] t]p~q=C>VfX\n@RqDm^.:)]%gw:S>>WjNd}#~9n .:*ʗ(kYRdkU,*/b}L|ad#Qs- .F;)qJg @ tKţ5B{зW?^|+W~0Xn[Hc@≮3 :!GeO=(^oP\7eo@)Wop1gV~r]׃|A<0gd}q 1}r.r^hT@ tt+wƲgβeWY @rq_ĈZ@ trwƯ_RT-v^G}xjA$A/WW8*.ѫ1>K|}2^Mvi|\x qN/\f\uY;ZLr=~/~49h@G/x5ۄx@v,%xZ<8Wt}T|M5]nAmWo}ZѼ,@nO7-@3gnjh%~)^}V# Yw?ǧ<]UGq^{tD/׋'KMD>OqV{< :z!g×g/~yxsMY7qW'}{<"O xyyFqC8{/ @Ƿ_+ч]?+߈ŭ*q}{ecs;ǃ?|˼+.UIrK룉Q.zr%7+6$Q&d(^&jx|yqǸA|u\^fWʯ7/}&ϋħ0n,߫Iq@Mtuz2+׍-J@VoxT<9/7{4ſ.(Z~`|Y+}j\2ž;.W/YώMxqtYQn?-[n5x dn;t >o_|Up^egX?{)1e=>4NOoiZ*U^7kTzO ^\l%زmJ#޸`P\.0o뾌w| :]@@ t@t.] :@ t@t@. :]@@ t@t.] :@ t@tnOt]@t.]@.] t@.:@ t@ :@ t @t]@t.]@.] t@.:@ t@ :@ t @t]@@ t@t.] :@ t@t@. :]@@ t@t.] :@ t@t@. @t]@t.]@.] t@.:@ t@ :@ t @t]@t.]@.] t@.:@ tUǍ;xR<=]&%THɇiq8Vwy״<0m0:^Vgzp.-~WQ*oWǓ=qj?uvyZbNs{PD\Vsv?#ykxz0!Yb7?5(._1I?O.[so^5kkymٶ@3--)Mugu]TWω ʟǙ|7rx[͟@uGaq]б癵jo-+:@/}qq ^yOKƇ@!'T9Sjucik]d;L_-w{~|}rxm'ʁ5 zzGl#Ws^Vn[W3m t thC.mu:G~[}k|O'z.~8>_9cH K=W_]w: 4^[g+mmluNޏ^ηWۯs /:oIAI.ejwj3n{_k t ~ wr _>U<0}nl !3Ǔ@@@k=a_g j0=N&k&zWjo_Qp/zr_[_SrOc?!}N&3&|3EK7~V@~yO@Sb,ys._W}dJ|F tNw8clmz`'荬tNSױ8}!݆sK?+.лͯǙ=Ϯަn[#>N+^9̾#,+~'6V]wu O>{R>m ju}=d<~#*k~'w$zz+3<8{ >[!)л.лU{G> =wʋ}F !%]d2*>yMŇʳ>]!n> 8]gwg\7]Ry94~07(TklM;O,sT%>+z]^ :l&_Y(P#%]oGny'Ч>n9ʿ;su3n]S}.{i[bjgWVLs_uyqrt_߿G{`{Oh=on()kgXݍJ?tWzz_>1>(z/,ҁ+Wrl_/a瞣z}<-l@ ?y2KL;3c]էK<)`,?'ԬzGwS3]mDZb-oR3ڇ ]@nwWMY,a ՝}j[-c;]@o籲U b-Ԛb ;j&] tۻ́yM+@rr]X n}ƵKg[MTݍZ^"[v%c7L] tۻ~L T{@Szb9e^ v7Tό״aZp[3,Le`ji>+A5~W|\ tn{1'lu\ ވq.{p𦚎xGmE-öO5Kƕ@wӁ^foBՒ}F Whq~Ru+@=:hm>][{ѿD?iTGwk] tn{7_ 9}OtnruruM 4l\U~!|oǟEζ}x^:] {kxͫzOz9Hά9Duщn|sK'zF[j؞lpۿ٩"ޒ@M@w~D_6@@GAzW|e䪉~]N7@5lw6lp]eV~^Kz_Y;zvsA.Půb{@@b`QRb_m Ώz+װ=g۰Փ=w5i;/تs@wCc ,{5u nלW/4I 8x{goVs=9w#9~M_ޓ^S;6[_@Å-ޞmbU׵n-[sj~L[R t.k~7nO5j;6sMAM_ t냇Ϸx{Go ~Kn z[j]u@]wQooO=Y.>^s^)4I&TY#ݻ=At[9S jr<.`Q t>nx9ĿN>kRVuu~5.@51} /] tޢ׼lM穧 ^&f=&w `x]]avKmksa-~] Z}e@yd|rgr@=xg-]շ=oZZ=Ϲ[)>[㤮3 t.z ^k^%j}{@@/ԑ .-<\n==3O ^<|NχZhk[e(_)S$ۏ}.@o޳sea8>̔XAx+nO)G\k}r8_]oϟvj1zm>SUMyՏ+k t.z 6pq=Etjy999qo;]ls L{Z܆Wf# t]k؞8x';yA_zԼ6]㵏(+8 4ue uk>kZ$UkzUtsj؞{l19V5m[:zm5}tt>׿Y}%@C9j>n,ebK8kI ^4,Z vک螺ª+Q{@/k[ gdQ.} )/K t^ jx6Ş5A@K->) tޫO԰-؃۩ճm czy}G@@x˔;f:g$ӻr >rpA4[h ]ar%rNְ5Xk@W(rtn{wh E|Xq~.л1Ы/@n]e*{B˾='pB tNtdzWuW5ƫT%@;>G;0ҧUVst^&&he[N tB 5lǿvj赿_zyS:@{&gGE\Vэ@@/כX/_m@l'#.{i0S T!=ZFL[?, eb&Zz@G025LFZDu 3\6p@|4S(. ,X\ǶU8ѭW")kScaV Q>f|"(ϋ[ ]#is:F5+k> t+j</mUpoj=lX{xc]n[m.} a7uaOCeub@u˿@Krz @5/imVOϦ*+?q@o&kV]0|o{%+D#zǜ-*uA |@;$_u[fm{ils }'dz@/ox]Gcjm@oɮC5?ij@]<(hx|kmjxm6I5| f}@/qm1Z jsɃF . ;8/T@/w#ݥ>{ݱfxO& tޖ]Y.8Xu`] B QZ@jm~O=% 5Ow9P tۻ} ^Qo7=j嚟Sm쁫i]r[/L`_΃zp uDуq6#-~_}1/ tn{@O_[qeh*@`ڷc"*.;䋮{b(~g.>x^W❰cv.лgzmI):&@kxwg^f5g t(zCf\{#SVi]AWtC tWzxf^eVϓzj.@ ,Vp[ &z7ݴ4]V޲{lG]p|AaPcPT )1,PA**PS,XpDQ*0:!Bӽ}~v3 3݈][Ⱦt&#(ˉ*QA O^-y =@%wbqsC H}\xx@]2KWm5y]G,wY >@oow=ބGCrߟ_}]Oz1?sŃx O: y>.#5] ʏO|w2`''q@]<ӓɊ>[ t>n1s]I;ckac{|^7b_?x925 tnz/{Ex?v"xvc7p t^KxVϔgQ8ٕw,o:,xR@[qfF@Xo+-T@ǪO˧~[#Ou4M t-0W;'?5]G,|Rb+zS^R; u@o>8Q Wv;G)Dr~-cدM :P@/u]kg9#\@p l@]t6n&׋kq]G,mGGOyv t^c\bla]91E4h'4]o˟X}31p@x(C~fet7s%Mҡo6z_>(My^~.z %/Q]70_5zC G%wApu@o_8@7^&&k t dBH.{O/KVpi]9=u@o\$4fJ tn-9+zݼ-~=pM.Уiɗ[p t}ӕpLN}HÓ@o_)M|N w]G/@c޿ =yB/~~1?&[>[W绺wS.m]7s}7;r9\3-y|hSW$.-o\.m]7sOyf]7kSz#".Ѝ@o=`~&zs<c>tnz;z@79S tnH߻%l> cy?"}L t@'Oq;&&V?ρSv^ tnz %M 284`>,rl+zsxGyfi@[><7 tn̵vآ9VXY}YJ1J :8,ǯ tnUq89`u\eo Sz1b7vNx BwA.M gz~3*|doE A癧E]fz215`~&zktZ 76|#nx3*<=Y0sz1k`x7+oKg]@/ |dj=p; ;5@ FY;דm*cn7t@ tގ77tnW~ÎAB;tnيհd.m]cnZ=(ₑ#/7Ùo,Szz}cZ|@޳v*?_C] tnEZڗ#/nSpw|yd^:ثzT@޳v+i]D=-ÐM@ tޮ@x徭5,3j}GY{'%/i<N @6.۵fgV-cP!0`7^_*Ћ[S{N`W&z5=8o/M ki>x_1{ިmA_!Ћ[ @7}O4ۏ%zpZ2@6.۷n'[C豈}zI/`}=0' "@7}]}h09Q@ tεkoWgi ا34 n'CB]($%4pǜ=uM7J]G t@8ҾxA)Ywt<"hE͒G@ Q7iރLS݉?>}}]@o ?>Snt\0 z.m}W~Q9Uo8?[%;'%ndwOU? tnkx=ܗl78G / tn]TmQr{Tὴd.m]wXˏ \׫焀D|gq񌸃 51=[ t@gi@ tuyڨ̱=؇|#;^t$$^욘]T t-9{.m]wc-xY6״j|{ݝ&4/zt.ma}]@ t@Ϗ'1sMݟzyxͿ|ZWKu1%&]o^G:.m]wg= ~A{dE=Yǀ@/z]@[q> tn[k:w[z gZ'?湀9@/~mW t-Yxi{.m]wkMOEus::OWѢ~ ..Ѝ@? tn{zѡ5֨wuM4Oszk/ tn d@6.лxQZlV댸9Ckm󔿙h\}Eb}xLncd@6.л_֥5ƍn/n!k_ꩼVz+y]r}>䟪zZ^5뤱~] t@莵<(Bu_^,g:Nb}| .z|.3 mg.m]w{_x]~Db(5ę-t=<`~![>^G.zok|e+R tnk| 7Bn5kd.Ѝwف>dwHNMPG~+1]@?|!;`+AwT-t-0y:`.Y#k%7tnCwV뷵4/t@ tngncx=Eޗz"<Y#& tnGU4?Zp͝.܋&m_^.k9ܞf'kAw@o:9L t@23p?c=A#N8/^_ϫa62y٧خ9qōn;Q]"u49h<״AݯLײVV_ܒ\m image/jpeg NXP_fc Illustrator 2006-09-08T15:04:49+02:00 2006-09-08T13:04:51Z 2006-09-08T15:04:49+02:00 256 140 JPEG /9j/4AAQSkZJRgABAgEBLAEsAAD/7QAsUGhvdG9zaG9wIDMuMAA4QklNA+0AAAAAABABLAAAAAEA AQEsAAAAAQAB/+4ADkFkb2JlAGTAAAAAAf/bAIQABgQEBAUEBgUFBgkGBQYJCwgGBggLDAoKCwoK DBAMDAwMDAwQDA4PEA8ODBMTFBQTExwbGxscHx8fHx8fHx8fHwEHBwcNDA0YEBAYGhURFRofHx8f Hx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8f/8AAEQgAjAEAAwER AAIRAQMRAf/EAaIAAAAHAQEBAQEAAAAAAAAAAAQFAwIGAQAHCAkKCwEAAgIDAQEBAQEAAAAAAAAA AQACAwQFBgcICQoLEAACAQMDAgQCBgcDBAIGAnMBAgMRBAAFIRIxQVEGE2EicYEUMpGhBxWxQiPB UtHhMxZi8CRygvElQzRTkqKyY3PCNUQnk6OzNhdUZHTD0uIIJoMJChgZhJRFRqS0VtNVKBry4/PE 1OT0ZXWFlaW1xdXl9WZ2hpamtsbW5vY3R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo+Ck5SVlpeYmZ qbnJ2en5KjpKWmp6ipqqusra6voRAAICAQIDBQUEBQYECAMDbQEAAhEDBCESMUEFURNhIgZxgZEy obHwFMHR4SNCFVJicvEzJDRDghaSUyWiY7LCB3PSNeJEgxdUkwgJChgZJjZFGidkdFU38qOzwygp 0+PzhJSktMTU5PRldYWVpbXF1eX1RlZmdoaWprbG1ub2R1dnd4eXp7fH1+f3OEhYaHiImKi4yNjo +DlJWWl5iZmpucnZ6fkqOkpaanqKmqq6ytrq+v/aAAwDAQACEQMRAD8A9U4q7FXYq7FXYq7FXYq7 FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7F XYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FX Yq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXY q7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq7FXYq 7FXYq7FWD615p1m11W5t4ZlWKN+KAop2+ZGeddq+0Orw6mcISAjE7bB6PS9nYZ4oyI3I70F/jLX/ APf6/wDItP6Zr/8ARTrv5w/0o/U5H8l4O77Suj86a6sis0iuoILIUUAjwqBXJw9qtaJAmQI7qDGX ZWEjYfaznTNSttRtEuYD8LbMp6q3dTnoug1+PVYhkhy7u49xec1GCWKXDJFZmtLsVdiryzz95082 6NrEqWF0q2QYIEaKNircQepWu+cxPtef5nJhB+g7e6g7H8oPDjPvYv8A8rY87/8ALZH/AMiYv+ac t/lDL3/Y1eDFWtPzU86SXMaPeIVY7/uYv+acytFrMmTNCMjsZANGqgIYpSHMAvQPIvnO51S4lsNS dWuj8du4AXkAPiSgoKjr9+dTrNIIDijydJ2frjkJjPn0Zrmuds7FWNed/NLaLZJHasP0hcH91UBu CA/E5B+4f2ZmaPT+Id/pDga/V+FGh9Reaah+aHnKGcJHdoFKg09GM71P+Tmj7Y1E8GcwhtGgx0mo nOFnmhv+Vr+dv+WyP/kTF/zTmr/lHN3/AGOT4hTDRPzE87393xa8QQR/FKRDH07L9nvmB2h27mwY 7B9R5bBtw3I+TJP8Za//AL/X/kWn9M53/RTrv5w/0o/U5nhh3+Mtf/3+v/ItP6Y/6Kdd/OH+lH6l 8MO/xlr/APv9f+Raf0x/0U67+cP9KP1L4Yd/jLX/APf6/wDItP6Y/wCinXfzh/pR+pfDCtD551lC PUEUo78lIP8AwpGZGL2u1cfq4JfD9RQcQT3SvOlhdusVyv1WVtgxNYyf9ban050nZ3tVgzkRyDw5 H/S/Pp8fm1yxEMizqWt2KuxV2KuxV2KuxV2KuxV5j5k/47t7/wAZD+oZ4/27/jmT+s9hoP7mPuS3 NS5bsVTHQtan0q7Eq1aB9pov5h4j3HbNt2R2rPR5eIbwP1Dv/aOjiazSRzRrr0L0q2uYLqBJ4GDx SCqsM9a0+ohmgJwNxk8lkxmEjGXMKuXMHYq8u86WsV1q2oW8oqkhAPt8C0P0HPKe2s8sXaM5x5gj 7g9Xo8YnphE936Xld3ay2tzJbyijxmh8COxHzGdFhzRyQEo8i6jJAwkQei7T/wDeyL5/wzZdm/4x j/rD73C139xP+qfuZJa3U9rcxXMDlJoWDxsOxBrnpUoiQovCQmYkEcw9u8v61BrGlxXsVAzDjNH/ ACSD7S/09s5zPiOORBet02cZYCQRd7eW9laS3dw3CGFS7t7Dw9z2yEIGRoc2zJMQiZHkHiGuavca vqc19NtzNI07Ig+yo+WdHhxCEREPJajMcszIsZ1f/elf9QfrOcJ7Q/40fcHc6D+6CCRGd1RAWZiA qjqSegzREgCy5rO9J09bGzWHYyH4pWHdj/TpnE6/VnNkJ/h6OyxY+EUjcwm12KuxV2KuxV2Ks68k 6vJc2z2UzcpLcAxMepjO1P8AYnPR/ZPtOWXGcMzcocv6v7P0hx8sa3ZNnXtTsVdirsVdirsVdirs VeY+ZP8Aju3v/GQ/qGeP9u/45k/rPYaD+5j7ktzUuW7FXYqnflnzC+mXHozEmylPxjrwb+cfxzou wO2zpJ8M/wC6lz8j3/rdd2hohmjY+sfb5PREdHRXRgyMKqw3BB7jPUoyEgCDYLyxBBot5JDzTzT/ AMd+8/1l/wCIDPI/aH/HsnvH3B67s7+4j+OrDfNGmevb/W4x+9gHxjxj6/8AC9cn2LrOCXhy+mXL 3/ta9fg4hxDmGM6f/vZF8/4Z2/Zv+MY/6w+95zXf3E/6p+5P89LeCZN5E8x/onVBDO1LG7ISWvRG /Zf+B9sxNZg442PqDsOz9V4c6P0yTT8yvMfrzjR7Z/3UJDXTA7M/VU+S9/f5ZRoMFDjPwcjtTVWf DHIc2C5snTpRq/8AvSv+oP1nOA9of8aPuDv9B/dBM/K2mc3N9KPhSqwg927t9HTOF7Z1nCPCjzPP 3O302O/UWVwRiSeOMmgdgpPzNM53DDjmI95Acwsz/wAAWf8Ay1yf8Cud/wD6DMX+qS+QafGLv8AW f/LXJ/wK4/6DMX+qS+QXxi7/AABZ/wDLXJ/wK4/6DMX+qS+QXxism/L+L0z6N2wk7c1BH4HK8vsX Hh9GQ8XmFGZiV5aT2lzJbTjjLEeLDt8x884jVaaeDIccxUotwNqOY6U78mSlNfhUdJVdD8gpb/jX Oi9lshjrYj+cJD7L/Q15OT0XPVHGdirsVdirsVdirsVdirzHzJ/x3b3/AIyH9Qzx/t3/ABzJ/Wew 0H9zH3JbmpctfDDLPKsUSl5G+yo6mgrtlmLFLJIRiLkWM5iIs8lhBBoeuQZOwKyjyl5k+rOthdt/ o7mkMh/YJ/ZP+SfwzsfZvt3wiMGU+g/Sf5p7vcfs9zp+0tBxjjh9XXzZxnorzjzTzT/x37z/AFl/ 4gM8j9of8eye8fcHruzv7iP46pSQCKHcHNMDTmsPvdMNjrMYQfuJSWhPgO6/Rnons5rPHyYyfqEh f483lO2MHh459xiaRuetPnLsVbJJJJNSdyTilrFCAnspL3VIrdNuSgu38qgmpzzn2qzjFnlM9Ih6 HsyHFjAZfBDHBCkMY4ogCqPYZ5VlyGcjKXMvQRFCgirP/eyD/jIn/Ehlml/vYf1h96TyetZ7c4bs VdirsVedecv+O/P/AKsf/EBnlXtR/j0vdH/chycfJJM55sTjyj/ykVp/z0/5NNm+9mf8fx/53+4k 15PpekZ6w4zsVdirsVdirsVdirsVeY+ZP+O7e/8AGQ/qGeP9u/45k/rPYaD+5j7ktzUuWmXlv/ju 2X/GQfqObbsL/HMf9ZxNf/cy9ye+b/LlOepWa7dbmMf8TH/G3350vtN2Fz1GIf1x/vv1/Pvdb2Zr v8nP4fq/UxDOEd67FWa+UfMnqqunXjfvVFLeQ/tAfsH3Hbxz0H2a7d4wNPlPqH0nv8vf3d/v58/2 noOH95Dl1/Wx/wA0/wDHfvP9Zf8AiAzmPaH/AB7J7x9wdn2d/cR/HVKs0rmoHVktzbKZtmVx6J78 /AfRXOi9lZTGvxiPLiF+63U9uCP5Sd/zTXySnPfXyR2KuxV2Kpjo6W/KVxvcUUP4hN6U+Zrnjft9 Kf5wA/Rwj5vWdjAeD5pnnCO3VLV1S5idjRVdSx9gcu08hHJEnkJD70F6P/inQP8AlsX/AIF/6Z6t /oh0P+qD5H9TjeGXf4p0D/lsX/gX/pj/AKIdD/qg+R/Uvhl3+KdA/wCWxf8AgX/pj/oh0P8Aqg+R /UvhlZN5t0GONnFz6hA2RFbkfYVAGQy+0uijEkT4vIA2oxlgGp30l/fTXbihlaoXwA2UfQBnmWv1 ctTmlll/Efs6fY5ERQpDZhsk58nqT5htiOiiQn5emw/jm/8AZiJOuh5cX+5LXk+l6Pnq7jOxV2Ku xV2KuxV2KuxV5j5k/wCO7e/8ZD+oZ4/27/jmT+s9hoP7mPuS3NS5aZeW/wDju2X/ABkH6jm27C/x zH/WcTX/ANzL3PTiARQ9M9gePYD5r8uGxkN5ar/och+JR/utj/xqe2eae0fYf5eXi4x+6lz/AKJ/ Uf2dz03Z2u8QcEvqH2sdzlHauVmVgykhgagjYgjCCQbHNSLVbu6mup2nmPKV6cm8aACv4ZdqdRPN MznvI8/lTDHjEI8I5KWUM2Jalqn1zWI44zW3gYqngWpu39M9B9mdH4OTGT9UpRv9Ty/bWfxMc65C JROevPmzsVdirsVQbag9jqsU43TgFlXxUk1/rnm3tbphmzSie4V73ouzJ8MAWXRukiK6HkjgMrDu DuDnlc4GJIPMPQA2uyKXYq7FXYq7FXYq7FWW+QtPYyz37j4VHpRHxJ3Y/Rtncex2iJlLOeQ9I/T+ PNpynozPO+aHYq7FXYq7FXYq7FXYq8x8yf8AHdvf+Mh/UM8f7d/xzJ/Wew0H9zH3JbmpctMvLf8A x3bL/jIP1HNt2F/jmP8ArOJr/wC5l7np2ewPHrZYo5o2ilUPG4Kup3BByGTHGcTGQuJ5hlGRibHN 5x5j0GTSrmqVa0lP7lz2/wAlvfPKe3OxpaPJtvil9J/QfP73q9DrBmjv9Q5pRmic52KpN5k1P6ra ehGaTzgjbqqdz9PQZuOx9H4k+M/TH73B12fgjwjmWK6f/vZF8/4Z3nZv+MY/6w+95nXf3E/6p+5P 89LeCT7yb5dbWtWVJFP1O3pJct4iuyf7L9VcxtVn8OPmeTm6HTeLPf6RzTT8xPLS2F4upWqBbO6N JEUUCS0/U/X51ynQ6jiHCeYb+0tLwS44/SfvYbme6tKNX/3pX/UH6znAe0P+NH3B3+g/ugnHlXU6 g2Ep3FWgPt1Zf45wPbWj/wArH4/rdxpsn8JZGASaDcnoM54C3MVvqd5/viT/AIBv6Zf+Vy/zJfIo sO+p3n++JP8AgG/pj+Vy/wAyXyK2HfU7z/fEn/AN/TH8rl/mS+RWw76nef74k/4Bv6Y/lcv8yXyK 2G1sL5zxS2lY+ARif1YY6PNI0ISP+aVsJzpXk3UrqRWulNrb/tFvtkeAXt9Ob7s72W1GaQOUeHDz +r4D9bXLIAzu0tYLS3jt4F4RRiir/n3OekabTww4xjgKjFoJtVy9DsVdirsVdirsVdirsVeY+ZP+ O7e/8ZD+oZ4/27/jmT+s9hoP7mPuS3NS5aZeW/8Aju2X/GQfqObbsL/HMf8AWcTX/wBzL3PTs9ge Pdiqhe2Vve2z21wvKOQUPiD2I9xmNq9LDUYzjmLiWzFlljkJR5h5prGkXGl3hgl3Q7xS9mXx+fiM 8k7U7NyaPKYS5dD3j8cw9dpdTHNDiHxS2eeKCF5pW4xxjkx+WYOLFLJIRjzLfOYiCT0YDqF7Je3c lxJsWPwr/Ko6DO802njigIDo85lyGcjItaf/AL2RfP8Ahm07N/xjH/WH3uBrv7if9U/cyKGKWaVI YlLyyMFRBuSxNABnpZIAsvBxBJoPavK+gxaLpMdqKGdvjuZB+1Ieu/gOgzndRmOSV9HrNJpxihXX qjdT0621KwmsrgVimXifEHqGHuDuMrx5DCQI6NuXEJxMTyLxDVdNudN1CayuBSSFqV7MOzD2I3zo 8eQTiJB5LNiOORiejHdX/wB6V/1B+s5wftD/AI0fcHdaD+6CEilkilSWM8XQhlPgRmhnASBB5FzQ a3ehaDfR3v1WdNiZEDr/ACsCKjOOlpjh1MYn+cK91uzhPijb2TPZXGdirsVdirsVdirsVdirsVdi rsVdirsVdirsVef67oWrz6vdTQ2rvE7kqwAoRTPMe2Ox9Vk1WSUccjEy2L0+j1mKOKIMhdIH/Deu /wDLFJ9w/rmt/kLWf6nJyfz+H+cEdoWhavBq9rNNaukSOCzECgFM2XY/Y+qx6rHKWOQiJblxtZrM UsUgJC6egZ6c8w7FXYqgtX0m31Oza3lFG6xSDqreP9c13afZuPV4jCXPoe4/jm5Gl1MsM+IPIvNP lbzdNJ9StdOmlgQ1klQfC5B2pWm3fOR7M7BzYZSlOPq5D3d/xdrrNfDIAInZj3+APOf/AFaZ/uH9 c3P5PL/NLgeJHvV7LyH5wS6jd9KnCg7mg8PnmXodPkhnhKQoCQcfVyEsUgOZiXovkHyfdWt4+pan AYpIfhtYnpXkRu9PYbDOv1uqBHDE+95/s7RSjLjmKrkz/NW7p2KsR8/+VpNUtkvbKPnf29FKDrJG T0+ancfTmdotTwGj9JdZ2jpDkHFH6g8v1LyN5uluA0elzsAoFQB1qffOf7bwyy6gygLjQXR4ZRx0 RRQn+APOX/Vpn+4f1zUfk8v80uVwFOPK/ljzfp2pxGfS5xau6+qaA8aHZuuYer7Jy5OGXCeKMgf1 t2EmJ8nuOdqzdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirs VdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsV dirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVd irsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdi rsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdir sVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVdirsVf/Z uuid:9C26E8903A3FDB118275AC5A537797CE uuid:9D26E8903A3FDB118275AC5A537797CE uuid:CE04683D273FDB118999D9678FD3D42A uuid:CD04683D273FDB118999D9678FD3D42A Adobed a     u!"1A2# QBa$3Rqb%C&4r 5'S6DTsEF7Gc(UVWdte)8fu*9:HIJXYZghijvwxyzm!1"AQ2aqB#Rb3 $Cr4%ScD&5T6Ed' sFtUeuV7)(GWf8vgwHXhx9IYiy*:JZjz ?ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽twOM5[~?p*)1`\tʳe)t7M'}Xnٺo\IQDƚ#V3.5kLVZ.nmmVJ9t#pkZquI1=se'_r?juI߿9 G-֮\]N3,_r?juI߿9 G-֮\]N3,_r?juI߿9 G-֮\]N3,_r?juI߿9 G-֮\]N3,_r?juI߿9 G-֮\]N3,_r?juI߿9 G-֮\]N3,_r?juI߿9 G-֮\]N3,_r?juI߿9 G-֮\]N3,_r?juI߿9 G-֮\]N3,_r?juI߿9 G-֮\]N3,_r?juI߿9 G-֮\]N3,_r?juI߿9 G-֮\]N3,_r?juI߿9 G-֮oY}эcf\%簦z\N荙t #UȾy^s͗#Kglp}*pq!{#f]BH_ݷcc|I/c8C|=[ҙu:]+jI@afۧ1mwAg`,GxQbH2 Ј~6_eÿFrԻ?u{~OF߿A?R߰ƿ׿ }?p/~3νK~7^/$l=W[:.k{ߠ(#9_PolԻ?u{~OF߿A?R߰ƿ׿ }?p/~3νK~7^/$l=W[:.k{ߠ(#9_PolԻ?u{~OF߿A?R߰ƿ׿ }?p/~3νK~7^/$l=W[:.k{ߠ(#9_PolԻ?u{~OF߿A?R߰ƿ׿ }?p/~3νK~7^/$l=W[:.k{ߠ(#9_PolԻ?u{~OF߿A?R߰ƿ׿ }?p/~3νK~7^/$l=W[:.k{ߠ(#9_PolԻ?u{~OF߿A?R߰ƿ׿ }?p/~3νK~7^/$l=W[:.k{ߠ(#9_PolԻ?u{~OF߿A?R߰ƿ҇m9NاiT艣RdmD-Žsܟ-qN1jWQ2 b jmyxVkD4ש@5Ku>w_NX>c/Zǯ(yGQ{OWeU_^߹׿}e;_|)_W(yGQ{OWeU_^߹Й+w0]{MݛW%%c4Zb2d`u.]%k{ ǯPJн!xp<I^r6orp]\BU؆~ #%u׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺uHHq<;DDݘUU$X\։ 1@굷@\.w1[pRbU|-6̼t 5AW\,Ѱ%A6e\ui̷"(&p4$\1ɖwZCi}qR2E@iYԪ"u>w_N?eU_I?7_Gu>w_N1eU}{ Q~To^S}$Y_׿7_Gu>w_N1eU}{ Q~ToAO>/![nIheMw3&̭>9.N-/&/%7hdIN#eK i dUm:aP Оt6geKnGg` ?=AϨ#fQKnGI׿}Go3~_?p?X-O~e;Hٟ{(Q{k/{.QF߿O@?m _t6ge*To^Z{ˠv?(%SP7[z$_]>䍙G/ʟ׿'?^?$l= ~T ޽` ?=AϨ#fQKnGI׿}Go3~_?p?X-O~e;Hٟ{(Q{k/{.QF߿O@?m _t6ge*To^Z{ˠv?(%SP7[z$_]>䍙G/ʟ׿'?^?$l= ~T ޽` ?=AϨ#fQKnGI׿}Go3~_?p?X-O~e;Hٟ{(Q{k/{.QF߿O@?m _t6ge*To^Z{ˠv?(%SP7[z$_]>䍙G/ʟ׿'?^?$l= ~T ޽` ?=AϨ#fQKnGIђW6G}L2AC]+ce4ȫ/cp1ٞV^ͿIi)YʱBiBtPw}ilbE4dgwl]9WY~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7^g_v;_s?Y~G7SؑLo!_QӦWT[t^~!_{F"ƫWwZ`x#춵Xxg"$P%wAZ%V%iBMzi-e?J/{j;?ύ6SdiH$EDZ^v֑"8}w Yz[c+rNM*UXiM_!$&9yfɥ-KE*)휏=GI>8jNQMK8%%HA*J[#AqKql$ S 84ǯݔ5䎝F2Hb 5b*/ 2:gܾL),xp| DwtZ=mȧ|ua܃g{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{C5j}?J+k*}?K{qљ^s׽u~{ߺR)jhj&GE &M YH*ϺKE( "(A`ӑK,I+B #~A|z l>B9G3tP*he-Dqy=? uOY o蓔''I+I=cjErv(u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Vu.ׯ1o^ǠԳlfe,:,RQE'Ja3_\v<~hbIU1!|FwbIMqCshf ƿ$Wzu{{^׺({>{_^?Txbᮅ{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~= և|sd /vC_,Md.>XH?o%o}=_n91ۤ;hmF[”L A_(gy܃L+c  )،D:uh TpHIbYX3h-ooP eOкe{ `$c7ܫo}yG3I0(B/PQh43"6ۭخvlM13ȀԗG=y׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~VG)~/{xn?;@T#E& =m/6GT>GI#$bXSBcvuӻKwfH,hZCq 5mlOݞLl&+t̉HdNSY=V'{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ GTԾSO_Q]^SN?[ջϾ˞{{^׺uNQ.hd:TZLpPX}<Qs/`}1%qBUHsQndڥw(3"~ <3޾fjy(9\eTܝ6C]K"MYEY TUSʄTA"0ᔂ=o+A:) #Ȃ(ze@V̯o"VSP`||S=ӽ{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽t۽O7and V ~*9T|[!,T)!L{50nlSQ裋;Eo4D|*w;l,}XD_H"jaZ jvNtLfvzlm7CԴ8+FbYr`)o_6n,'bXTbasG1ٿ\ջ)]G%UvB *:h'aGYm;|"am#"f? .%X!v I4l]hc*+sUD4X\|}_+r瘧ԶV+t'R*@fJPntҩ9{xvrݼZ=1Wh-n=jV$f!Fd|]ၷm આ:i,2dta }]յ]I֒(dt`x2" 48=eZ׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u/[?+ ?+$WlϺ?k޳ {^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u;WtvV}_671JJY$q%<UDLsDI#b>|6&]e{|HBsL4r( ILrY#fFFm|ʹOQ,uu?ɔSFFVF,;@! ɋȲ1B_g(Ȏ݇y/ocmE4sZFJm>{i3U )ye֩6jL cYчc)#76׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺4'R^fvΫwCXrwYc3KSƌ!fM]#<i__yusFЊXS[aaՙ1 &T=)1aA|h1'zu{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^P?Mgnp}Ҋ|Ottf}W\~{ߺ^׽u~]s ?@Y}OUښ/%~ ] )jpYcL[zBd}&]f܇n՞ԓ1]rOX>>={ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽uBcoA ӻVɳCg7hQ1RDR9$Q[\2M,_)^ H8ˇ? `? J| KPP @Sh{{{^׺u{ڷ/??Nl^n?Iڿ/;_׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺGOZyd2ʷ7 ]ojv2}/ 4fḐ?4:9cNr0*GoAJ{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽t l>fٻ/?"Tg{ܵNj1 lˑqm+|̖ -'"¦tR}ک;Cl.7[SP+ $چGM>w\;O;4Smu@ -j)#q|;cP9w\Zڷ~}h?ˌQ 42^Oҹ eBÐv?A4IK?y=ieڪ"DgWqm K ƕ7qish.G<ܙ~{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u/[?+ ?+$WlϺ?k޳ {^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{3qu>ݛvo܀2XM&g$^.cF H2uG{9/pm9hݍ"fS-j+$R;tIٹ{9PIS#+\hJ{nvR&B42VB I0ҹYn~{/p&l]2Dxַ O,D MhGn](o8Y^IhZ j bo.I˶ӱϳ] i 8椯z#3,ZMVMEW 9R UF 8#h9}9dA.yEh4e5WS`T'sۮmt^A!F0xPxA`={ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^СIjK5>]>c[\׺u{{^׺Q ٞ؛swReUYQe'IbfFXhvm@sQǨ">Dqdho= ls"7)G<NI'0=;s0Z!'NiX& qQ1QTNʦji#{k˾U&ٮ|3TjPIe}`uӮGw~aI1J3烕$ HU0_[׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽tOhzhRj0n-OP`UJ/i>M< .T֒L|=}k聈5Q'~!5jy|ޔEx"֪I$I&GY]YI$v,HK;I&ͥUU uFfv.&OO\=~{ߺ^׽u~=Fտ}sg/?u܁*N<1IpB{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^wn3m"gj.ӲڳpPIq"/qa']s=Eo4?#J$SmiyR:&ƒ?@qx.6J\~6 *:xzh(*ĒǒI>nqn4ۍ,xc2pYoV%,(Tz(:쿧u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u9 )X=6CYQCY ⩦)>g\|{[+] X続H&CǶH\p|ƒI2"*?G>qn,7xƛ5&A-[D >7,/L,K4}#OA[k6qFjY;{;fvڀ9{˱IW:NW~ O+ilYjVXx,` UϪ^nO@K8|OBt㠯tBC^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺uOT_y /(XK-Fgez^׽u~{ߺ^׽t|>/ w%dTQ2G4dGz kd*9')xE.x |0:اn{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺uyVwr f r5,VAO%UeToDl 6[kۘP̮qfc@M:M{ykZKz1 3y/y_=^z|'6Le4Q+r%]IZyCT;9#mC]~9X _T3}{}u-;xIǹH=u~{ߺ^׽u~{ߺEwџoo<_x~Kg ʓ _w5п{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺ogK6Hkr+"- _ 4ory'^ʛ+8kW+mlBT.0f"<<5 )0uFNjU)5o} u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺R=6b ಴-U jWjj5=m$\3#Sk>sǵ6{m[X@ֵ*4L@43=71P}~c4?i6h-ϸH)DŶ w<ԬnuuO}y/)[w"bґ TۮQ&V9%٭XU"!r[:_Cz=*ꮌԆVVVV _}JGIPI AA : 뗻u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u/[?+ ?+$WlϺ?k޳ {^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺6_>CO-DuvꑶJm,j#HV(.$.fγI'[Zۮ(ME[%3seE^oc> T=Bz*xjijaT &E6hX2m+ cxH䍃$n#*C+)!\mh'VIъ!VE9t^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u[iO'?kV1{ߺQijh! jPOOQE<2ᣖ'*w6q_Y;Ey $n2"Ȏa{{U2y U3۽wSֻʻ WUm EF.wo/!z~`쿹r=T]*Cy:V GdnSyJnOv1~('͐7r@{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{C5j}?J+k*}?K{qљ^s׽u~{ߺ^׽u~{ߺ[ |zִ݅2TKpm0 -!huo",~gs{bXrxo0Y@uarMM[܊,#9,sƲS"x׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{|e#<{uR9ɩ^:p[ڒ V,PnU_y&kf ;WR[<%fc_)>hE ,Wxca4N{^׺u{{^׺u?kjx9ןy@'jヤk^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^я-S6֚dŐAEEJ@aȰDai='k/O)V4.쌀OI89Ԇ$_V܃ U~0u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^tG]Rst 6 #Kgp _Qs`惕9ϾQ/38s@9"6wy .Az Q.}|oxun7zQ]k4,Vji)E[)\\X~/=K~u w_t4xZ4xfJqHЫ) kye0Υ\t;鞽{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺nܬ'${w\<[]7>N?潯vnz.{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺?Fw(|Y?if+'oFyVLmNX?|ݷӕ.eѴJس,vlZLb?Bgpm+ xeVFA/@̨?5R$}u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{`H~/O g?a\}׺uD썛QJw˒I4N,"āEʨcblr{_^ '~(8$ҪL:=^xQڲ@|ɧtu y a 47)Q7hE(+ y^9Yb`0XATF# 1G84 8<돻^{{^׺u{{^׺u{{^׺u{{^׺u{{^׺uOT_y /(XK-Fgez^׽u~{ߺ^׽u~ );{'mv.f W#2TUj*F!gXQ5Ag$5UjT"RN Qɜ׸r_1sߙ!~6$L  UG[Ml}緻hkW!>.-Ĭo]E:5toRxm66~/ XPT0z˼XsomoX\LX9*ӯ{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^ݘn^Q ڸy_Kg7Etr]WhmQTu%,2݂o,\nVuJDi¯>"rsFDI r2XY۟_6O;2U|BZޢfI!ҪTVvuVh#"TPx'en;69FfcRi2S.{{^׺u{{^׺({>{_^?Txbᮅ{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~;`ɘ0V*:H%k$eV1 ]{YK~`vX.x-!i$o:(ࠑVcEEYQҋKYnRjF83ն=[[q2PêHY+ZJ: 08Q~s6]NHV(WD$ZT59g"zȭmh2?Pyt ˯{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{K;{wL٬-I;D Kbh0D/⩌@%bQv 'ߛי&+(k{3En a$ИHE{a^øpo0~_WAݻm{mT誧Ŝ0,tz* )MIɋ|>؂%프}Mj12P,q3ML0x"?yt){:G׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~V_XڷtVV.H-ut'o^;7Uf^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺV#q}_knJ;W3W)2gi$bk%qPA1ͪcZ^w$ŕלbѵJk#_VKkpɺ8^'{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺]cn[j֚zs⫤[eժ1:udO~C]]U߼_<[s!;=0]H2[\y$C{ aU<>z.{^=i4 +#0kx٩BLxWY>~x^H2so)C;`g`NK5BLpGXa7.T'ȏ/z]"׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺nܬ'${w\<[]7>N?潯vnz.{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺uTVTMIYG<5TTҼ54,TA4edhe@AV}٤mIb6)$rFDu!рee AtCu\o"u`YXQ8 rBvp4^8SqPGh7EP²$c T8},\Rbͪ׵E)_@Xe(>pnugm9\ Ʌ(*IJc[=z:u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺4'R^fvΫw{{^׺On;w+y(2ԯO!)IMY`UjhQ%h.%jݹ#ld};.7JJp34=50m3t*}TW_#wnǖ9xWbu "%-e>S:ʄa{}mڹ,ej^BMH$qdjbhH7wykK|qWZR|ss؟~{{^׺u{{^׺u{{^׺u{{^׺u{{^׺:>?SY[}3|b,w}={ߺ^׽u~{ߺ^׽u~IVRd1U50VVM%=U%],=5U42E<Ȯ2t(捡CDUFI{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u\C`^-GB8#IԠ7C|BVD&~?_%v'A|_0C@'yZ?k{P +cUvH1|CPTV}X׽u~{ߺ^׽u~{ߺ^׽tPw}[V͟r;W'}] u{{^׺u{{^׺u{{^׺u{{^׺u{{^P.Ee0gT %ƚ;\!Q/6Tʕ;ߣiΩaH=pt.rƿLE_kG\ia$PAk0ƃ8(C]\^yv%ԮƬij3XI'DHG }{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽t#ghnNݔ{nL[D\TȔc3U/QIfhe X{ΟwNV˫Wf^zQIBEIR 2GQ#wͩfϏi Bck x̄H!vJYXCE'V>rL.xdj P<[k4` Vd'Gh[I ~<侓~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽uY /cjXO|IX#x?en}䝾{_W>]{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺO{oq涎wv|: 9c˼D1to0|⬦e`H ; ;M2t* ˋXiF \CX:]~qy<-B6%WrS{H; I0A,U[\igPؼlǘg{imR쳖kiQ,u++y?_׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺU_1{ PnY׽u~{ߺ^T??MO7)rIu|3K3 ^G촿R{ߺ~e&oǾ9娓f/ >/FJ&.(C3.vbsӗ;>[٬GB;|ىcZ 0wnys?7WMj5ڊTҧ$g=u{{^׺u{{^׺u?kjx9ןy@'jヤk^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u8D$ye8YG` ;G m4̩),Āp$XPK@V}o\zZnNj#%Y48!KT:)ﯹ{ڻ[ O❆Db4lS+ld9(1]Dqa t%׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺1Ư7 Ͳ7j'QºcK~&zrb22w/qwy%-V wРA3A:-N- >?#RUWQTCWE[O ]%U<,41JVol 6cnaIb6HT2:AcaFu#ھ׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺU?ğn?s]fI׵wY׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~/:۱wVvU*WG)ocetj]|jAcTn"J>{g%K'b[Y|QJdwFVHрS9;f_XSe, :˲vj: ٶAR*|Flj6>9, Xd_K/z97ߎ@K ̦{;ĶU'KAS$lQˮw琹^_ޒrtMd*xX27rrA{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^??K?u{SutO:|c3u{{^׺ʮUрeeafVSp؃dwá*AGG2XPVwZ?\o)m<T=k'^2iBL/rm׏$$4malh|):&U@s99CY-ԍ@||ld?P-v9׺u{{^׺u{{^׺u{{^׺u{{^׺uOT_y /(XK-Fgez^׽u~{ߺ^׽u~{ߺ^׽uwco7BYͯM6G`Ty2[a zCJ+`PI4RUT{936+;|2>b'Ysr.RF~}/kO`d]=;g|DKF(/MbH-}-Y${e3Vy)^%"@hC}7@=:?7ɻ[jq%Ȩ+W!z=V?ˬk׺u{{^׺u{{^׺u?kjx9ןy@'jヤk^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽tk~1u﬽8|Fܨ"JUj`CE@x0zt !S*ALpk)YCԃț]Bh)=~LE88u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^˾wow%])cO!/=2LnZCkʗ j{]y}m9VmWjjoamKǎB!;v ?7zo}+׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺU?ğn?s]fI׵wY׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺC_FwNslyzO5vw>K<{M:cPj-O!գbGuϼ3NaKPbq80HE43/m6r|b[OLX~R/Wyw.x`qnWŒ)R@IFd H+"`#ӿ$,ʶGȂ*XA:})諯{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^??K?u{SutO:|c3u{{^׺unٵB#L=V<Ո~WmJ*wԳm~c:܍D D #鮖j9^m4*@O7׈Te%V> )k(&JKOQO#E42dD !e{iYŸXH8 2A=`żֳʔʲe4 PEQ鞽{{^׺u{{^׺u{{^׺u{{^׺u{C5j}?J+k*}?K{qљ^s׽u~{ߺ^׽u~{ߺ^׽u~)^kV;pm6[T*s _=TE"t 7 }Mol]vsQBTf ym(to#XUXpe$wZmb-J"cuUQB \wy!-#I&I9$'^zu{{^׺u{{^׺u{ڷ/??Nl^n?Iڿ/;_׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽t;_%+mbPfR!--8UAQ+HÒ9fyR"kF#?#E};q5+'psv+mcbi"@3ܽETVT;!sn=Ús.cmnoE5H* UYaegQԟ5'zg^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~9a-0requd6SVQ̡̓#h* 9y-˚9vw;y  XT0 $%C„|Wݕ펿/U50r6 3=F*"53^Fê V_vh]]Dw^M}$D5@/N[Gh>x= ~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺU?ğn?s]fI׵wY׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^Ѧ ssQ9HdWH=EV%Xe v}~7wlTM8X"B uHu ,H?TD>p*LxRLL~:I ERd))*`*:Ycex`H`n= mۍaOsK:ȡHIWGRYI  Ӯn[Y=OZl.9ME>ܤc86},r3Urf`gW"mCzREZT_<\5M6ۓT$'sYUVǨ{^׺u{{^׺u{{^׺u?kjx9ןy@'jヤk^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^Ո|f?m疧)(T;|K(d+",%u{ޛ/O2H$հRЬHf43r.6.1Gѣu{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺uG.mGfd{{~haYd" ="CrAdHuF=wom>ok) nw$H›ZѴd1 o_Qk?V,8^@^{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^^:ջrrGnvˬ;|ٺ|}0u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺uĿiU?j[k1W!++덪؈uC~Yh>}>m>];mi!żǶw=NH*}=o';_$(3qqeAAWUju~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u[iO'?kV1{ߺ^׽u~{ߺ^^o 8-NXܥ$U7JX͙rb.[mCi2&SQQ槃))*pOHum@sQ<@##ٔNhPՔЙdz*Xp *u>?=ru5_4ja1,MT5QX|wʛ5OGQ0@ Aws{^׺u{{^׺u{{^׺u{{^׺u{C5j}?J+k*}?K{qљ^s׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ğGn?r)i1*%W6#OUS )k|꒹WխSfbAA$ 렞{qnUG$KɫMbPӯ{^׺u{{^׺u{{^׺u{{^׺u{{^oϱGG욚Yly)Wڒb&AWUdE^g#s#p_{LBI\4Py$qF2T%î{{^׺u{{^׺u{{^׺u?kjx9ןy@'jヤk^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺCWFuv&kafx#G:z*94xYc!b I! גݬg5Cj<qB  9Sc;F*4?DlxD:uhꪊQUUUU O|wgbIrjI$$uz^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^X'hji yc vXfđKC$A͍wra^}yN=zGq0)q)ֽFx?jO ~={ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺU?ğn?s]fI׵wY׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Yoơ\?R”77U i`L[~F51_/q9Ot-VDE?b@[ǂ߮n0o %V;GL7YTn[ð_}rzu{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^??K?u{SutO:|c3u{{^׺u{{^|9]9rhHTQ vS$Ʃy:ՑE'M}ؽwiB4Xn>fɢ'Ôh*iDgbd,T3$\d${P*nׯ{^׺u{{^׺u{{^׺u{{^׺u{C5j}?J+k*}?K{qљ^s׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^зѽ}gU" 0JÝl0`j,5)&1=\i[5jJe}8eBIIKn`15$JKbHa&\+R:_e `m=vrdvuJWSSFP୤TSNfFF)7sۮmJ^A!F1=TT0zͻoU8%ÁLX9)˯{^׺u{{^׺u{{^׺u{{^׺uO}ӄN=VR1g,=EcF4m=IC:H%p Pfwv{zKMmU >6\U!fjN 0֭۟r_6S=rUyl}Cjfg5f҈HP3moA(y*''=s#sܯwmq}q#HxcS 1{U{{^׺u{{^׺u{{^׺u?kjx9ןy@'jヤk^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~IR`JJhWT52,0CiO׶eۍ4;*")ff>AT~C"IXbvĒhOV=K;A9@.*p@-OH!t@fw}Ż3n7ֻZ 3nBLq1PJDzmh(g=8z}u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^-ا3}s]QvҶW<zR@ +I4qwMjm[XoUFn,óq(2Z-ڎ4?OOV=u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u/[?+ ?+$WlϺ?k޳ {^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺ M4HO8)2E,R(ee ߵI!ExF(C+) +) (Uu*e"pA Ȉ;S StTE``V5G*:؅f̑}~6rsnޟ0([Z> &8kpw/pu,^ԓŞQ܃Z^/u={ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^DIrI'HZ{ϴem?\}`U&s%wOGx름EFOid4}.o%FQx^H5qdsp;s9W/Xn4!}"GP׺u{{^׺u{{^׺u{{^׺({>{_^?Txbᮅ{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽tr-uT{Ѽ;f)-`:첆dR`ǔxh_{/t=d,̧+!CF20Msܯbbgx ԯ׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ҏgjWmVȳ:JYJM=\JH?3_mhMqHȬ1*ANL B?G7[9F/9Oc ~}4uTzK^A]m<ϲ70>;56#(=E#E#F1ӗ׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^e0/uwGa='ۿbYGv5u]au~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^Ӿ;nόb*S%0hpȌURAr5s#%7rԶ\ŷ\$hȆЂU]2HudfR_[~ϴn,ue$F#!e z> GtmD;S"?)BgY>5DMtϼ/}9 wZG-5 'Y$G^ߺ^_%#1=0%?ƴp={΢{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{`H~/O g?a\}׺u{{^׺u{{^ػ"Wlhcku~Vf[zLH/ ^}nr歿S$Ohn b>U+܄,J5ͼk{ 5:7}ʿ<0TQyFCarIEYNXso$mk }7{mgM]’*8NUV:knBpHQ2 B0zmk>{{^׺u{{^׺u{{^׺u{{^P?Mgnp}Ҋ|Ottf}W\~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~KQdU9u]=}u$Um$QKUMgQKH汓ў8jî{Yϐ*Ź9QHPb䒎T=`~{{^׺u{{^׺u{{^׺u{a_#?_]4USLRoMȆUez*c)Wo2{=ɟw]ɁI7_L;|c>_l7 h[wCI0 _y{~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺEwџoo<_x~Kg ʓ _w5п{^׺u{{^׺u{{^׺u{{^׺Zv Q< Vָ6[$. `_r9n.p4Kɡw!rV5w z4ٶ;pՏ=Iϫl1V? Z\v. *8 DߪIڝؖ$O:[;Hfܮidsjh8F@Pu6[5 t^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^1ޟގLdlfPt2xx=cS'v׹_׏._wWmMzhXkt󥹯 -2@_ϣk~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽uY /cjXO|IX#x?en}䝾{_W>]{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~-zn.Xݶ*!}2!sG0[V UXI{>嗸\?5T2Y#d3s?/o볘a5%tB%Xfmm ׷$&%tj.V$Vժ aep>Ó(IEӺfk;o(bz,LPr9xe7^"N/pkJ#3{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{`H~/O g?a\}׺u{{^׺u{{^׺%*z55)1.EX&cs16>]?ܿӖŘd8 ă-"L^pc.@SJiF?SSЎ{^׺u{{^׺u{{^׺u{{^׺:>?SY[}3|b,w}={ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ѣlsN'pp M0h30S&Cn0dx|)_3`Oq9B>pސH ? 9m,ku'{Oϲ5G})cR+2q owTO[6Rd))k*`T%x٣ pJA9!JʬC(Ar8#C4W,22PA PGԟtν{{^׺u{{^׺u{{^׺Fv۽c&ub1\NPFeW:x\*}lMw Տ1o孚}EEpUgbG:ՓGqw7bn-=SaP+ E R(Q#`=؜͆ϖh6k0 y͚(sypaĕ Za#_袀>f{= ~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺEwџoo<_x~Kg ʓ _w5п{^׺u{{^׺u{{^׺u{{^׺ec1]uk".?o&;Cs80> }%ʹɫh ? j<8HХԏS&l_Z_N>~G;f{]{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^''FitnTSky2vGU .ܾ.aL?TpqebF{hH5,mc~={ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~V_XڷtVV.H-ut'o^;7Uf^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ҝŞ{V7&ˢ1j dh/M=FŔ9$Vȯ?/G{.^b[LwvRKJCXe:3۝ܞ^m1Z)OhDQQv;j</N uaIi"o]=],"~yWPJK[!E"&E=XڌTu`7^W'ط;u<=C)Œ0A(z&׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{`H~/O g?a\}׺u{{^׺u{{^׺]EIUC]M=e4˪*jvכ];#Co*#FSU#:b y-.T=Q2 zN꾷9 I1VH?^&QJkd&H蟥wC@5y$#EpV'`lj6≉MQkZPor@{{^׺u{{^׺u{{^׺u{C5j}?J+k*}?K{qљ^s׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺWFzvg}s)Ax@쾺k$-QTS^zn{{^׺u{{^׺u{{^׺u{{^׺({>{_^?Txbᮅ{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺFY~7X)ok URȤőɏzIbFO4 Hm{C{LyuV `4ʱÈ,k 3b}T[& Ǜ|}ǯ{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u'eܦCˍbf6Q94}9k*>[-oKimL#,̡2?_翭.{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u/[?+ ?+$WlϺ?k޳ {^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺3_{7 ezOt)WU)ŨQ"-Jy@,]#n}˛so-Ǵ Z@AnT* H+%?m#D> * C$ccYsV'd)'eh$Vͧv7{n!.I&H7RUԆV zݥՅԖ7W#AAzä{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^n0?3u[g~{ߺ^׽u~{ߺ^׽u~{ߺ@{6ΘPp"l ʒM\QdneM_9(|e4*|ˣ]y{ÿ:VV [Mtlm]ut028'|&r3ZV62PV*9UBRU$Rp3~kܼMԠt9G&ZTT5Sz&U90dJ"HۆUԴa :u~{ߺ^׽u~{ߺ^׽u~!i>=vGQuO"; Q_ [uX#R?%:\7ZO}#A`*((@y$+iI5$I$Ǭ^:u{{^׺u{{^׺u{{^׺u{{^3Oӛ?}yWRv8:N~{ߺ^׽u~{ߺ^׽u~{ߺ^׽td3\~TrpQkw葥;A'{v].wAi K#%ASAǂ1dח kn5M#|V/6/ly+h|J{=mloݓ*%=^nƫ6D0&"p Rr*Ǭv};sU1ytӯ{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{rs;'m|5]c}܁<+Easoq=~vn;x0/\W˥W{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽uY /cjXO|IX#x?en}䝾{_W>]{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽tx>';wssrZpJ &T& Xda7V?g=̺?q}=,bFq!=MHr5gEf!cw\_١6\@Fxׇ4e0m@ #A !FAH {{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺU_1{ PnY׽u~{ߺ^׽u~{ߺ^׽u~{ߺUsw&F tT?$kh7 &R ecV97eM>[9d6.`bclK}P )Z=B]{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ GTԾSO_Q]^SN?[ջϾ˞{{^׺u{{^׺u{{^׺u{{^׺u|D=6eVR%Di› 4Y.Wm4{}+ة`̑qtӎC(~a}-;OlSOlF G׺u{{^׺u{{^W)2m67d4P=M]]LBE<nSHU@fc@$;XݍUA,$N|gk6>*bry 3t'UU-s?sL۱,dlaRhHC2*\:u{{^׺u{{^׺u{{^׺u{{^׺({>{_^?Txbᮅ{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~}gt6bUrMEbhhI1We@apդb<de޲ ($g 4Du,rFw gm|xMԙ׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽ur z7B&gM0Y*=$9U }cť7[SRJ/z%׀t=ķy'^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~V_XڷtVV.H-ut'o^;7Uf^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺVap *=TUAlO+T*1?gXoPܡ;7boe`Jm;-HMd8'rX]{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺU_1{ PnY׽u~{ߺ^׽u~{ߺ^׽u~{ߺIqmY5Q)^(PRT-VD<=y+^D>jٚZ,q$OO*FPz'K>cٯmB+]zMѶ{Gpevb/CViU<PjzwIc$Q<{')sFΜg+ۯ!⧃hdSBFz}h7Y{uOU]He>tGW^׽u~{ߺ^׽u~{ߺ^׽u~ GTԾSO_Q]^SN?[ջϾ˞{{^׺u{{^׺u{{^׺u{{^׺u{~M}bcrWyUV{܄2TT4q}kݘ%r<=᧻XMSf%҃R v#=t#p|6M\÷=9I!5t{==N{ߺ^׽u~{ߺ^׽uQ̯ic>: ~ǩLx1avI!=GXmאʲ]rᣆɉc@GT&^{{^׺u{{^׺u{{^׺u{{^׺u{ڷ/??Nl^n?Iڿ/;_׽u~{ߺ^׽u~{ߺ^׽u~{ߺBWSWc Z\}*_2؅r!QU.Jߘn~g:pt<0 p] ey7-EE kIMAEttTB"5cQ¤qAW[4HjK31,I$1Qԟis{^׺u{{^׺u{{^׺uJ|Vw6ΗwR:<qfpc>|s?<*VO~??v{rYs% nSZxd L1BN4֦#7 ekZqt.~c?׼H7_,zCg޿߹iao^?_uog_z40qerY|:7g~v3Az{G鸲d׿O{3?;W^M#?Yܲ_mx' ^^&n,Y/<^/Uׯ~H7_,z?zc~c?׿ɤy/Km{׿s? ߿<Mŗ%޽g޿߹iao^?_uog_z40qerY|:7g~v3Az{G鸲d׿O{3?;W^M#?Yܲ_mx' ^^&n,Y/<^/Uׯ~H7_,z?zc~c?׿ɤy/Km{׿s? ߿<Mŗ%޽g޿߹iao^?_uog_z40qerY|:7g~v3Az{G鸲d׿O{3?;W^M#?Yܲ_mx' ^^&n,Y/<^/Uׯ~H7_,z?zc~c?׿ɤy/Km{׿s? ߿<Mŗ%޽g޿߹iao^?_uog_z40qerY|:7g~v3Az{G鸲d׿O{3?;W^M#?Yܲ_mx' ^^&n,Y/<^/Uׯ~H7_,z?zc~c?׿ɤy/Km{׿s? ߿<Mŗ%޽g޿߹iao^?_uoXɧ4k-Ԅca qcaj醷?cǫv5_O޶v^#%H}o›Os,ž1isJyq:uyq>򎐙_ i1{:Gb%olaG@'o7RlƦ '^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^Cgm[[67Y_pt t/׺u{{^׺u{{^׺u*+;DPYUT\16}}cRBpI>@u 4zU뭟 ur6],vZ4/Ej'П{^׺u{{^׺u{{^׺u{|52^|Yŗ@Ncc'}~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~$w>vmL <]%MTBWѺ0C>i*ݭ`we+5ͬRJ1_"<7VGṸ5~â> qf\ɮ y7 )!<5Ӧ^+r*g}g9$wR XX hj :;;p"'c~}=R&]́vaYXņ$jYQ>Uq潯IrcqiO#-fC v?{lMI/#2׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{G/}ĻZ<ʮ1eq 3#Y?ݶ <1QQxZDӜ>t 2&%5~iմ׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺU?ğn?s]fI׵wY׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽t6WWObS6骩wܸteQ!<26o#+M4ďB_y>F va/mWr߅o-Mm9FM凅,ڻmw!/,ܱ6Zyd<: / ׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{`H~/O g?a\}׺u{{^׺u{{^׺u{{^넑4rC4i,RG,R*rFUtu$AnE,HB̓#VRAR Adr=U$C20 *+~jw?˖^lCN񣎵4oli?NQ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~V_XڷtVV.H-ut'o^;7Uf^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽t؛quݯXho%u+8b 2%2uV'~|e#\}ķ@WĂU]*FIC79lsc2֌>#bM /QI0n R0HWʢ+MG9|:Ad_?\ߞyY+Hm^ u(eQ\EuWmzO':XooV:3>+.zu{{^׺u{{^׺u{{^׺u{{^׺u{܍~#AAVqԲhgUG=5DJÐ+^C"e9 (A`CGwjNYHe`| }z}|lFg-ي Xجi!C%l0A?9Zc4:x/ܥ/'x~ |' O!o3@]-Ut%F"AeQ$uJT=?`^{{^׺o 07[ͳ_ݕGSFdFJ-&c.zoK S{}+X4<$~#R(@>{U&rFZVI>LAN`/^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~=Fտ}sg/?u܁*N<1IpB{{^׺u{{^׺u{y-߸1[o+VіzȹZzJtid"Bl=y6L˾g[M%+rS *zYϹ^Gel+,A=IR~Cp[cv+mbM*`W`Z2TThMEeCn5175n̷|Ѽo,@^ W:#@V۬ ,OO̚o^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^վ|/WwuOCu?,bˠ'1@1Ѿzu{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺uKCab)]t`k:?"f '=39e#,E*Et|d[قCഊN6$䩵coT]EYd)6VxheBkvMfvK7FWFYH Y]CH>un{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u=_?t~-:q@E_{/+p:[|߼|-'Wۨӯ{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u/[?+ ?+$WlϺ?k޳ {^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺uNnmLJ-U%&<)Xzb $O0ѧﻏ>^\LLb,iֵ(M5{yr`{˯n4KZiԍȕ/ 2-l\X_UQV5utb~=}~{#`~{ߺ^׽u~{ߺ^׽u~ GTԾSO_Q]^SN?[ջϾ˞{{^׺u{{^׺u{{^׺u{{^׺u{{^׺5A|~^y7u\\$ncPT$hV[ܞo`S2qӓ@X=J>sQ%"_(AR|UfzZjjz9⪤Z$Y`exeBRH+A:S#=H Zhj#M"1vrj]Gx|EYz)w6{t}s 3<؁֬ڻsv.bjpÜwIg~խDqG-h5=z ~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^Cgm[[67Y_pt t/׺u{{^׺u{{^׺]g>A1)bF<2J2EXv#S|c[/z>19QI9&ڝS0XCf`o:?Ui/,Ts-5iy T:x|T4N]V/l?a^E{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽uY /cjXO|IX#x?en}䝾{_W>]{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~])QY3!yR yd:}*TA,L7nvuM٦tFl51Z D =ܝŴ 5Z6㉂SJlkծf9|Ee>GE5 xd_c6~f٭ya`I&Hц G\Ӿ/e۷$dQє"S{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{`H~/O g?a\}׺u{{^׺u{{^׺u{{^׺u3cсʟ"-Ֆ ZJ KSu/Q!Oygg͛^#1L4phHF`O4r+췙h3qiQSzH{t׺u{{^׺u{{^P?Mgnp}Ҋ|Ottf}W\~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽uzwojѻ^4&eTmܓ-^ךUX Qt;{oz/m歽)etLs%' &Yvy'tz6Iܓ-~s{^v釣6v^5d/k$lʏey3u){t >^$mo]\pd F Uiyu{{^׺u{{^׺u{{^׺u{{^׺u{{^׺({>{_^?Txbᮅ{ߺ^׽u~{ߺ^׽u~ }#ƞm1dr$ '608I a GSsӒp9T6UB!HrTEF%X:H"8E8P *=ZI$FV-+I&I9$x?QE\ӭ~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺVkd_=u#\Ջ.%4OG#F!׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺ulc< Uf.WxET-uPTQVX" 6oryv`][>a5U',gȊ&{yt_8:m̢8~"uT$ Q#|$s^ɻ6'o+D}TRPE%>PGEzgν{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺EP)+)*0TIq &:uimvv6JҺ5+V: JWu߿{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺nܬ'${w\<[]7>N?潯vnz.{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{K[uJ[:S)TYLYYXx/v]{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u[iO'?kV1{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽tZIky1TMǵ`uHpX-%D#q"(-'F&XT%l7_ RLC!n,}CrgdbT!+_ް{^׺u{{^׺u{C5j}?J+k*}?K{qљ^s׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ҿ`ֻnoͩXhwLuETjfRI%=DwX%t<7jɶuV"=H ɀ>];{žiC4VjJRG[Ntj덳igU4%N1LƟ-R*1ըc*g2lO_ՅԠt9G_-NA맜9r0gNLr I|с(õ$}S( DRH/WծnH'{F؇ۮO~pPRI )ATO^8Y(iI=ZQ?[,9X/U!@̋ - _@B. >s]vh f)4bRJk$`JE wfMm[l= ⮏u{{^׺u{{^׺u{{^׺u{{^> ̗;'k{e? {D={ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽uN5v1A)7.BID?:9܎ ԟ:]r>NbM6ɵ (U?~9܎&z'/Oxg⍈G{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~lkIbAG,@䒞7v f$ǿMyn[;<3VhԓdqP pN׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~V_XڷtVV.H-ut'o^;7Uf^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽ud_GqIbٺ.nn]Q,q _}_Q&I{3Miҭ?_:7XUAP>p??=Y{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u[iO'?kV1{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~V^nc)[cr Cd1\2H$l@6X~/%u_5AbQ*R()PPHu3$Ӳӂ?$_ ԃ +{{{^׺u{{^׺:>?SY[}3|b,w}={ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺV?Loڝu~沶aP±c!Qd`Q7 @oJleA,Dգeh?Ө~b=Ǐ7yBr18YI8 xrH !+Bv[tE:{6MK3#TZEr'G r\'|<#PvjU@ty~w0XmQV;d8?O!o1P!GEۨ{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^3Oӛ?}yWRv8:N~{ߺ^׽u~{ߺ^f^ދ$[r9SQӢ$*S314^efvV[SN )GF+/RW ~4z`}H5{R^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺVkd_=u#\Ջ.%4OG#F!׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{Yv^nU@3K N]r4HBk~}yYn9CVi]س]西RfX ?b{C{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u$SIQvDͮYi^ʺLT*bl_ Myț-q.fhYf4RI!W8gcŽ5׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~V_XڷtVV.H-ut'o^;7Uf^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽uYљ2YH##%dR Ђ2# GZeVRR(AGVOb&:ToM/¾!!m*£ηj+pS>6y>񹳖?k?R5Q <#cN{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^n0?3u[g~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽t쭉AػC' mp,{px\vpX>;G $BĈ@`]{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u?r49|MeF;'FE lVHPG]|9gy_|&xKGF $۷kdU]Qzo}mOEtËM%lA$3>D(h>?z#ɟI6p-&ZɨW#v]>c[\׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺({>{_^?Txbᮅ{ߺ^׽u~{ߺ^Փ|r?^)O{jdY qcih(,nPq:Ǻ׎mm2j卥_FTȠwS^Jؿu}ekSȟ rxׯ{^׺u{{^׺u{{^׺u{{^׺u{{^> ̗;'k{e? {D={ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽tVd̃?9niSO㧪_]ԁ׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^3< ut}yҖѢ,ӟ𞔾}3׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~V_XڷtVV.H-ut'o^;7Uf^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺJܸ׶+/K TDMEl!TPBJK#R6 u/:X#50K%$S!*EuWmzO':XooV:3>+.zu{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u?kjx9ןy@'jヤk^׽u~{ߺ^{ry:&/mx/B|~/LYŘxCS~'jO4nDTC )4 #P|F֤L:rnqZC5x3~+|:O|t׺u{{^׺u{{^׺u{{^׺u{{^׺uz={Fg]9JiGC׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺEoO=k=x1o%4:z5H{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~l??ڟm`WKۏw)l.ˏ9 Kg={ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^e0/uwGa='ۿbYGv5u]au~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ Gڻ7}&?.o$Ss7d#\Z@6e,8=ǸvښE{hDW.) @-Zp7\˲l{/ U_P~JԑPt.{v3vW+T5*d!GSWtȗ#YYXBY({>r=,2 xlR82FGn]W+O~ Ju? q4 ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~VG)~/{xn?;@U}f^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽twYO>۫6C P-dZj?Q[a4 {i!^TsSW) bC e&l%yr=UIA ,AA}2  {]{ߺ^׽u~ GTԾSO_Q]^SN?[ջϾ˞{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{ڷ/??Nl^n?Iڿ/;_׽u~{ߺ^ӆ'_atWUCGGOK<#A&Tl]n~õo[k I# *~:zk-3P?{n$!'Z2jʶP nVE{㿹{\}bA5?SY[}3|b,w}={ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺEwџoo<_x~Kg ʓ _w5п{^׺u{k1KݜOC9T _ӝTHy OlvY?J22-H3b(éWv-*wjGW{Τ{{^׺u{{^׺u{{^׺u{{^׺u{{^׺σ_% wŞYt?(?z97^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~? ?='۶}G;ou u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u?jᵂ]/_n?l?h.?'/L~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺU?ğn?s]fI׵wY׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽t{#pom3;nuWQEPqxC o3GpLQN@ly1m6YwIZ^$b5H7oϴrѶ'ڣhY^9dMdol쵢ՂKЇ{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{cTeeڷ}[Pڙ:F"Fw,&K~Om}fru\s:"f#꒴ lr{^_m*?TJ??7ɱE,sȓC2$K9# WGR A5—6β[ȡVVVVH 9@VE$E#r8 럷z^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^n0?3u[g~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^W{mfmQ*fH- bzU 0O/Nryh?6RX$dBT g̻45)R25jA=T6-mܼ& &ZJhAU ^DF}j^mM*+#PKGkDpt dblDkQ(8g^EIKCAM 4BSƱC Ih' mqwrY$zuE,"~^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~[{_;_s}V,s=}臯{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u̟{{gwc?>ߣJi7tKk:u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺fǟ?/6R4]EۿsҗϦzu{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{`_xV{OŹ.s$kf׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺;??o?Fُu7$>e[NP5O-~8UY ]P=$vM_S*)I _Q-@ZIHz8Y Lu#fĚw%XtuqM-6MvR!^ piKLǨL㷎jOU8/DْI~{X׽u~{ߺBQ'k?u/W_v׬Tӥn3ﲽr{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^3Oӛ?}yWRv8:N~{ߺ]X$I'މ 1Vh:hDܹ ~h*icC!2}{n|_n- CQ|VQ<[딶?h3 _MFQ)H*t7u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽uoK~]5=X PLr=o{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺+2AY{Om~v)/@׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^a~k_`ӼKehu\nOJ_c>׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u/[?+ ?+$WlϺ?k޳ {^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^ f?k)s:Oo;/]Ch}[%U-5u-Mm<5tuMKWKSMOSMQC=<9a'*RA~olK6눞)bC$r)GсWGRU =` k:\;GsFRUYH ATuM&>TxOn>HfVVF%Jgpt[߻5l=ᾝvI%dvibg~!m_wvTHLj0ct`U*7{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u H }? `EpxulG.\?xbLKS!> \I@OS{䧺;T{t?"Y\@;cf4 {nB1"Nh,!޳Dő7(´`}?u{{^׺u{{^׺u{{^׺4'R^fvΫw{{^׺u{{^׺u{{^׺u{{^׺u{{^׺uM UOQM ʡ)TH HA`kiݙ.#`Hee5VR2 FAGR0 *"UtukdCjphd(^V,Z)5fMyObЇ.GriH/PP~NZH|2zqy>NOsVH'?9],itu~{ߺBQ'k?u/W_v׬Tӥn3ﲽr{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^3Oӛ?}yWRv8:N~{ߺFwGZ ӹw`ׂ,r&jhnjVZk1 [*=<#64dHTѡ?#9 @Jj)w"qOϫ.{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{|52^|Yŗ@Ncc'}~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^E[S_[oYtmW%:_@~.G>?ra8-c+AqJy˫qu{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{gyS }qNa-EYq?=)}g{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺nܬ'${w\<[]7>N?潯vnz.{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{3fmޟgSC?]h>u]m~L{mw~ )9<6bJ:9ǦH2R<8tȠ#/ݯد4j(&唭IJ }=𣡟^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ[X/J[/FwNzR^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽uY /cjXO|IX#x?en}䝾{_W>]{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺGgg1>_ćL2}Fν{eM;V%OdI)Մu4F~#g{7ɾw:Ū~fP5ʆ][X0#6'9g%s|0It54dqHj֌GnCinH4R5 |I"f1\=Pd!}cZ6)^}g}tpN7vH`5F 1ȯр/$Ϝ0lXdá#\25E je*㵇Hq/B޽{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺Qm=מv|"ji*PSIi"oEE%L,F s5{aZ>SggZ|Q%5kSe+fyб%;GX[;{mI]o1̖]>c[\׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺({>{_^?Txbᮅ{ߺVa߹zm9LJLsX-"Ma?zt~k-vMWEN$#ۂAp±)`=mr %|\@z6~{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺σ_% wŞYt?(?z97^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~U-ĵRomcY2~vzf&48gYǾz8.ɐhv}~ r~~>} yj-61OGBN{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺uٟO6_ ;͇_Qe3鞽{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^^:ջrrGnvˬ;|ٺ|}0u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺ucz}樂Mww=dӻ?wյ럝{ߺ^'ޝ+ Ɖjv},Q<O(.~vXrl8Ym+VXֳ$+J$ʦH7s7?m9njgsq"y2XJ6+$+7Tog{kqPKaꞒm$""h& $e$}s%7|}\i%2mӘJYIWGR9] Vl[T*϶ tqAVR *@ -ѯ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ WwuO#k#)*[TObaUc3{9}P?Nٙ7A`=i'1J"Ϝ{;ҽJ vȄ^Qx{fmLn^|}zi e|jԌ0VR7^we$OkGFuŶ@N$*VHtv7;ܽ&NWDkXR84Ue`΃={ߺ^׽u~{ߺ^׽u~{ߺU_1{ PnY׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~2<7u`{1{慈!*)U6Eyt`]=e#QԔuH<.=li]VsQxh|Co}ػ+2MF:5"G_k*k\k)E\{v|״{PZ)F$pEh5 8`zd/9gzf @r&ZSǢ.OT_y /(XK-Fgez^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^Cgm[[67Y_pt t/\~- W%&$E*V|!=n?Gosb˒eaa!^5Y G#o{R:45qJ.N((4cH5 QF#@UD@HM4<řf&rI&'UQB @{ou~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺVkd_=u#\Ջ.%4OG#F!׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{ڻ߷_5$~>K_H>,I)_zOs?a&%y5 t{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{gyS }qNa-EYq?=)}g{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺nܬ'${w\<[]7>N?潯vnz.{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{3fmޟgSC?]h>u]m~^׽u~o){wsa# lĈ=gk 5;$s1F*3.}>?xNTrqnE(R]fowcbLDzvf~Q'A%Q@WSC]M=mUE%=M-M<J$3C"e`XX6mu[mWXFHr#+T] KWI-@C++ + Adu>׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{/ۨU~<0n|"8hT)BL8HY?H/2ew3A?v|wț? sop&]2qG^?O0[1I@Ы]=lf"Sce,HCu7 $nT^[T7txUqPh@e`jx4r*dnVSMo!IA VRI:/׺u{{^׺u{{^??K?u{SutO:|c3u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^vM;Z %"[͑e\y&!P 83ĺyowUKnVlC!/ F=C>gWuRHd̦]~Zan'XСIjK5>]>c[\׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺({>{_^?Txbxiizc,L8y$P9$.. ;hو f' $q]$>ޭczٴx6W :eG%i~{"*sm2c6qHT;G y[&QŌ,P|8\RvmwO2oϪԗ׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u?jᵂ]/_n?l?h.?'/L~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺU?ğn?s]fI׵wY׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽tv~}5oH}˿c'޷ݗ.k\׺u{{^7]%_glzәw>5 qsBP@T *ʏ}=]!/DncQMW \*h&ϻn#~hLOdcxD槱J˯{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{swDU]*ehdv#m#+LűӒ>0 PjVYz\ci$tΟWT}d`E5swy3@0.c.|4(IVKx*h)"zz$I IhX2=Z[_[G{e$sYr#GGєee!A\)`Y&F*Vr8 u{{^׺u{{^??K?u{SutO:|c3u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^"A Z{zt=z6|tS+!(4C8H{Wo䥶{`HnkJ*h/XU&U7-f%8RA_2zLu}RڦR3|`/,w}={ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺEwџoo<_x~Kg ʓ _њrrv^~gʾ[O^dopj|k_n6i)ޠ{§1ײ-+(=N܅}Dy`}#|]׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^> ̗;'k{e? {D={ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^X~ȹn QSIbT/.GO+ #\{'jؔ]8kF6&ѵ?۫w&|g׎; q|M׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽uVܧJ]GMӡr ԹPX`uvXXmHHQA4V4WuJuJx?؏u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{`_xV{OŹ.s$kf׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺;??o?Fُu7$>e[NP5V~u~{ߺ^׽u~U˟' Q]5\Uo,-$l+&}rgMV SHuvǖnԞ\,R~W5kTWZV$ [H|U8_߼s.Bݔjg#QEO*G&E=3l.w$ͯ]vw+f5-]~O'_Z/bZڨ@?P?Q{5o׺u{{^׺u{`H~/O g?a\}׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^= G;%j{};Z5ù9"5-Ԁ$DmA s+w/ʹKE#\N]'9V53Co><<e4ASMUO 8]$B8=6wc"ͷ\ı:]KVVh 1.,=+1Ep?a}{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺEhdgmb6C;X'Ӫ:y+kQ㥧p]͗@'9u5zЕVQ/VJj^>5CS*jcÉ6 0ͿEGK=r sHZIĞO0n|վs&y3HʭTy"-eŝ6ۊC?/3'$OO>zU׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^վ|/WwuOCu?,bˠ'1@1Ѿzu{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u_Rl<嫎3E;ilz9>Œ[!VC,9H#v>~_wjpwbnrhnV!t 0A`{땁~,}| W%ԽfO/_WUry*jfkX$aTݹ~mf7 nn%oIvG?6v&RTh@|M^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ\8bRh*(con 3% Zy*ij>du@<:(BE iH !#B"I$*9mZ*EP=Ejxk{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺nܬ'${w\<[]7>N?潯vnz.{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{3fmޟgSC?]h>u]m~^׽u~{ߺ^x*jija$ &Fh'EhXث+-=ݥG=rG"Htt`Ue`CAimYfIу+)!e"EA#xCn̝Ѷ)囯l+휔ڤ8A5nt/Οߗs/2zd=vۗ}$SS%&DI'迱Eϻo-y< /کdpeP@= b4Y";I_o쏹U8troqf5kj =fG>W$~WL-oEb ʨ{OU׺u{{^׺uw#|c_=7YD?s>?{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^v'{OO_WbZx2 Ca6f>\^|KqX[`kNL34Urr .ddK9CpVP[j 6c/̘ܔJRGԎ>Ϻ#4ϷF2)pE@!ܛF1}^uu~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^e0/uwGa='ۿbYGv5u]au~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~7#l?My2{GXweKk?:u{{^׺u{{^};;jdv<5n.5qK"H 2-b#i9;C vH|kiͻh-BYp~O7yc}q23MQH*+P@e!HԻjn84_QQfR;6Jk, C*J{9{ !sutKkR!f";k\5 ȕ-1%jzȜ{o3Q[XZ7U 0ise׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺOcsfv|jj(xF&9c`RHٕbGng,yߓnMq,C+)xBF̎ o&{L&ͶܡGC8e 2V;J<'pT%Zev3IZCFļo叼kzbܬCU#F .ֳдxI0tטq]jĉ_< CסLu~{ߺ^׽u[iO'?kV1{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~7]aE^'FK5Ȧ 6#vJ\RMB2CC?{Gf† bVxو{/'&#ۙim(=AF)O A%ܟ~ۄsAEV0( o^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺVkd_=u#\Ջ.%4OG#F!׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺SUMEO5]eDSS,pSM+$qbmq+{#F8̒`:TOE3cJ 8ƑaUB^|1ߐ(֧UY> Gڹ_o6T%a[4r/qY˂aE%=:~=۹ARHP)X*$*l8P'_xu>opom9kk5GchYakHP*i{?OAo'{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽tf bwϑTۇ)*m,HWYqI#6c٘Vw > Jq,Ѷ5V+$lTƁ.u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^e0/uwGa='ۿbYGv5u]au~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~7#l?My2{GXweKk?:u{{^׺u{{^׺;0]ʹfd|Ty/-^x͉ȲC*cBe"2kyodmE2[jmW" e",Q2m+msh%U*Z7 T3HjvFm}A.;3jji9RE*yG&Y"nF܅^}#s1XہY#nE*$R-VH]ID*Ͷ daՇu5WS`A;^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~=c;wn?vȞ5$u%# .bJ"ِr>󗰾Z{ɒ{vf[1-8UjUI{7>ܿ/8r5 e 8Բ7k{nvnP^|UrFJ$FƤi#$l!(O/wR갸&.T)u #$Dc˾u9n^җ2Nc'X20 [^׽u~VG)~/{xn?;@U}f^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~#-2:զJ߷CAj>4ҧH=>Tځ%*!s,J*5S9l_dzDZsAfMV8r[5'59,\r+qHKx@4\_*lVF"X_:(1ƧsWv9gfczsw]O!v?30:\}RڦRg>EuWmzO':XooV:3>+.zu{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u?kjx9ןy@'jョδuw-4lirH~P*SH"xƤpq_/o}ۮZҗl1hh,6:~F :㞚5)#R0؃w=fgw8VDn*J#$uM+q2Ao=;׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺF˥>T:ggKive~9sU5>=VE:J(ի_N=ݟ}olnv)w{vhBQEAի:AL*BHxW=z?qUB۹{/l]!G??p,g_e;w0es9틯V#z8g}3B釲׿={ 3>EWY}N?_bՈcТ'nag/u^ϱQU_~s0_3غb?ygÁf?(/۹{/l]{׿?qU߿L=.XY8 *var^G??p,g_e;w0es9틯V#z8g}3B釲׿={ 3>EWY}N?_bՈcТ'nag/u^ϱQU_~s0_3غb?ygÁf?(/۹{/l]{׿?qU߿L=.XY8 *var^G??p,g_e;w0es9틯V#z8g}3B釲׿={ 3>EWY}N?_bՈcТ'nag/u^ϱQU_~s0_3غb?ygÁf?(/۹{/l]{׿?qU߿L=.XY8 *var^G??p,g_e;w0es9틯V#z8g}3B釲׿={ 3>EWY}N?_bՈcТ'nag/u^ϱQU_~s0_3غb?ygÁf?(/۹{/l]{׿?qU߿L=.XY8 *var^G??p,g_e;w0es9틯V#z8g}3B釲׿={ 3>EWY}N?_bՈj~}T+R@u<"Xkhܒn$ur 4ͧuhjiMi,As?wu(*vWCap+<[ƻ~B~4ysf6EjlC8z+ 3U/mt]_ao(w%kTq>' w}-UK bqFZ[ @`x㎔u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Xomݜe#g37'R'CM.\74[.'^]iLQpNT7̲R8J:}׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺU?ğn?s]fI׵wY׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽tv~}5oH}˿c'޷ݗ.k\׺u{{^׺u{{^׺,%:<ƭMEbAP}WW轶ten;`5qU ԂAT ikq?„Ͻ}c3׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺU?ğn?s]fI׵wY׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽tv~}5oH}˿c'޷ݗ.k\׺u{{^׺u{{^׺uC{>ɡSkc>60D1x#1wX6_s־YC[X;J?Q5FlSIɜ/'zA+[H'䓦':T b_| C:ϰkï{[׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{Y%ORgFS?_g>YnWDC9E,~܋ ؑK@G_#yY%v_w9ۧj(?EhVHK$ 0~S#>|?bhJ iii(熪ijiI5 2,3DᕔAll+(wX"I"6GGRUԆVhz Mm3\#Gqe`C+)+Ar:WMu~VG)~/{xn?;@U}f^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽tz mGsndc}3PQ $UJ|*^Q#)u,[jo7:S_PT(zI7<'Ou{] GTԾSO_Q]^SN?[ջϾ˞{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{ڷ/??Nl^n?Iڿ/;_nFcj$UAYGSTȲ"J {eOquMe?hfp wRS`F/bU׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺU?ğn?s]fI׵wY׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽tv~}5oH}˿c'޷ݗ.k\׺u{{^׺u{{^׺u{u!;-fRDmE3Z"K 9S/'Q13~柺e_Y#4ՔK"bS_.<'o16^}Z5K4%~00-|z0u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺>~Guh|UӈvfP#1Ua琘fK1-W9tnɗR%j-7+m4jA!h1C$VK[Pfjq5eA?v{`H~/O g?a\}׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺q|BRcTl*'َӵnomQ4ەHY݂d@%۬忼q(] .'{/}v.JqSkv1vqGJ-IHᦑȰ6ٯj^W(#j+)/CNh:Apyr?$hx;߽u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^n3UcT1kru8ʪ㦧OYf{&};ͦ]Eo :voO:؇۸,.ߢx׽u~{ߺ^׽u~{ߺ^׽u~{ߺ\%9I)bHE)$rFૣ  jx!QdJ (j H 4=Yq$dAFAdr@'X{N`fLA,qUrj4ZP}C~r9oiw+4j+L;dk3j6Ri b=Nx^[ h%D*/W|#:u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺#₋7}ԐvnB[7Ip#3)bD)*/tyدtvhگelăsK]Ŀ9r!M |tS> },p.Ƞb?7:ᄈ]VG)~/{xn?;@U}f^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^U]#WbpCIb9?MLa?>^GK{`8 #1A1P^^Oߴi{?f$aчDzu{ߺ^СIjK5>]>c[\׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺({>{_^?Txbᮅ{ߺ^ՎmûZ&E]{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺGgg1>_ćL2}Fν{{^׺u{{^׺u{{^׺u38l^d0y2X$9 M,RHIᔆFI̜s~yk/ۼH*E xRXRJѕPEA:}}ydmf.lehD@Hi}9l&rn`MI`"(' )#aVBs-s41RLOcz'^޽{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺OQ=$UR55U4STO<.C<F$SE"VRX Y E9#b 2  A741\DN:e` B ApFW<krC`U%ܸGj$8-T)± c3}=|4q{ n6EȔ*ʤ0Dс"'>ﭕFN'5> I(hLNx˩4'RẔ>u[g~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽t덝5$XZ7#1Qi]JZ4XC[r__{ּmlkH?ЭB5DqP\T:E(۴7 +}J3NG#W25UjIjdi#fcmm;dkm E.*=`}ԗndݏf5$u~{ߺBQ'k?u/W_v׬Tӥn3ﲽr{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^3Oӛ?}yWRv8:N~{ߺJ[s6,>:4E]E PN@a㪧bp5(M9/9Wu:+ҭ^Tj]HN# qi#ɔ{TcԑIƠxePOUh_HO#>TnwyhyTpe>hCpdeaEYk屬2(# }t{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^>l?{HzʚMIR"e3: z1qhHntO?7%mvE?NWI+aRA kv_Պ{?A^{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u/[?+ ?+$WlϺ?k޳ {^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^ f?k)s:Oo;/]Ch}[_>׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽t{n>m-F*y(t-]Rt}}#H ~{[r=l.6;(%Q_oxGQwFr4A-CAqDG={w}^/+.^(tKt O I,ZTt|}=ϸwKC-ڮmKhjDXR+-YtHD6pz=j:_j,Q@cq׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺO;{pf6o|": v$3GpAcbF##n>Ck.q+lyofhq!Ԕ6hY-~'x}2#p 0A+ `H wN,I}(^=A(맏gI/()h Q$}~tZvERmn|9IJ&,hOٿl.a-ѽNjHxjPeppOO{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u IrO$O{AǭuV٧ gn`5k.e,g8F}lT=䕸 [Yj;Z~RYT }/g05c׾_U8${>{{^׺:>?SY[}3|b,w}={ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺEwџoo<_x~Kg ʓ _w5п{^׺uq٩$RJ5f(/aP(-)>_o=٢b.A߆Z-1LLK0X@F-rߣ)d?!VR^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^'$cAzWѻ#uNB!E][c3feٜմ*MX{c#÷b˲nnU啢RhtFg}U0?Bǹ]{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~V_XڷtVV.H-ut'o^;7Uf^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^3q6O!S.uzwv_Ѯ@}s{^׺u{{^׺u{{^׺u{{^׺ Ǵ*6\-.B^ͤz0MRePgGQ 6>FTu~wSy7&njo K;P8Q.!&GVTD= v㘓y%.!&4UF2o=6mMB|T uj"<[E3* ȭ$R!p>_盽KB{V J:䤋9rG@d"[=6X7Q.p)HKu#P{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{c[dm,M@]厡p%5uI.ISm<d_y]V-2yohY^ 4iVI PǼfbo 883|~Z 귽 A׺u{{^P?Mgnp}Ҋ|Ottf}W\~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~=Fտ}sg/?u܁*N<1IpB{{^׺O<ԳUM,TSK9aE,N2I #7]it%e`C+$pAAqduj9ذFͤ3xNWNB(ԊMҧCAr"lm%c\Vk755o7̍@:Zޗ{v#ꓶA 82?1Э"C׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺCƮ=_4>lNFt-$NL_zzfXMǼJ6-/y`{&WTzS{oln_I`?n ˫s׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽uY /cjXO|IX#x?en}䝾{_W>]{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺGgg1>_ćL2}Fν{{^׺u{{^׺u{{^׺u{{^׺-#zܻl㒞~`mm%|@deөj[Q&CJ?|~ϭouvX\(kn0k?7G~{oxKQ(x N4d0FV|~OW= } Tf*JYZynRHB5;ɻuݕĐOVHIe`A.Yc{iYŸXHXn`|>U׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~2n=ڸ,OM$u/v eTQU3,q-r:ϳXכӖHMa!\3~VcâuɷmYf> =Y ͈}T6=۟*֩TY**8 _HiV7cb}3j,ShֱќYI^G%(@9~m}PDD_ԜWâ>{{^׺uOT_y /(XK-Fgez^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^Cgm[[67Y_pt t/׺u{{^[Ɨ7-|I' [r/1-[Y{^L}ҳY${ NczmsYA&T֕}ZRN?潯vnz.{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{3fmޟgSC?]h>u]m~^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺDo5T:[ɏa=}{S9䶮-\|0k޽{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺uNzfat9얼vާ`z#%d7Y!}̾{_7\2ZMx⢑;v.A tNozK5cp{ˑ汎N?UA4T-EDOQ]+ohFm3uwnlSޛ%L<%KGVYC=RT+!5 ǝCtNhmZ@jQd9S2! :?]dߛ:UFeyJk ׇ_ϗs'Ԟ{slOIUԇIS|A# S{ƼdrU}ʉ|4cAu($K[׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺uM4TQQ*C4J"(BFI'`kY$` ff4UP2I$IIQ($h=UsDPkmwԺhc]lTdV27{|><;\꧘n5R5) .GH{TMz }̽u{{^׺u{C5j}?J+k*}?K{qљ^s׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽tPw}[V͟r;W'}] u{{^׺u{7?p;4g`#ϰH)f1dQV ?Dދo,r#0m15H6*D%#w\K)po#Ռ{SO^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ }3Y 6ѤE+|k7*nkX!y "w{#}=}Ƕ;xXT/԰\)$"/էoV<Oc1|]4TX]6>*j:8R x#c0Uɵri˻ mGoH( Esj9]I5{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^e0/uwGa='ۿbYGv5u]au~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~:j~FRV/Xhվ{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ /b2t^Nz*ZjJ)6Dbcf`c TGGޣ׷ɢ0ėTkni4TꈎO{9}K]S!e#?GWマ{ߺ^׽u~{ߺ^׽t(u}RڦROg>EuWmzO':XooV:3>+.zu{{^׺u{{^׺u{{^׺u{{^׺u{{^׺uNܛ .*nYBTŎQB%#31TEgeRO-mR bMT*d Aܳ~(kىT"*U&@bHpymmF/7br1Qd(*$2mqdzK{XmIk*FXTM{.|2:0WRCOK׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽tPw}[V͟r;W'}] u{{^׺u{*A bG DXUO^ǫB~#!?sm?54C?}dHx(Ko/~ti6ZkZGo6AxK[ ol:= u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u"<ƍ$2")gwbQAffc`$q4 *+ I&2I8d'WSW62M֔X~6FlvOMs3OoS#+~X/6 x ⸒AWH">7t[~}yG^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~V_XڷtVV.H-ut'o^;7Uf^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^73 =o%'bu?z?vK8?[£޹׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺIm6`mK] =(2^ Jx {x^{ɽm%. & SoJ22Q}׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺIMle>Ui`VH|tt0^obҤ!f5?nXWh S*՘G{,7aJ]A'PpdVw)yYz*\jЅ 1X[Ǿ-\֜'YBM2 ŞFnϻn y 1࣍EG`yulԙ:*L>:hk(b75#pE>X^mWmm :#ӬXgG=K:u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺? (;guLECsjbsUl 5Qh jx,gѽE>goEх 4!pWs=>z}׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^e0/uwGa='ۿbYGv5u]au~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~:j~FRV/Xhվ{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ݻ{6^(Ib EUPjQu. { xon9#W¼T4W@vʕoxXIr ~=(ͬ $$*ތ>(ބhTozs4Ld (sK[LĚl>gHMdcTo`~e/{anm)Q4fŻ*ʮ>V损v89cık|Q:dC> ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u[&;;ݹVXhܘYʎ6^V}սxdV@Gtfzi~H V/]b$7G笂C׺u{{^׺u{{^P?Mgnp}Ҋ|Ottf}W\~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽tx |r=1gzz<z\_[MQLR}f6g}?m,)cN/ (WIMyua}&^dY8E$jSBzpîFa^ 1w LYcTK$B) ASgwsawpÊ#Mtme6D<FzoH:s#ţ Lmj%\J!( ti';/-`v?**~PZ森e{w|WpH!bt7aBLV(HU{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{ڷ/??Nl^n?Iڿ/;_׽u~{ߺ^׽u~{ߺ^ٞX|GB'Hܼ_Qĩ?N(*x=٣7)Q2z9|ԧd9hœW{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺1_z'!UbTQ˹2TpxH:ȷvhn1,:y.z׹,mR#n7+d',m`**-ᬯ$;|=7-gc1lu'I7K 2i)X*G=0.˛E[i@ j8B"" =G#rKR}OS^{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u/[?+ ?+$WlϺ?k޳ {^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^UNws7_}W;Dnn\׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺/!'\C.˲_$Y\SPA$_c11dnuMep„GkiHS4r}囦!'yHDAC%*f0=`TSت25*jZi sD,RU$}s.|^r2Kg\<H(Ku44# 2q!6Vm5xrTЃ/Kzu{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺{G^ sn5ME+~>9㼈lBT}eܣʻaIj;ej`K‘Ae2K)ѩ݃T9ANM\XI$I&$I'[ MO׽u~{ߺ^׽u~{ߺ^СIjK5>]>c[\׺u{{^׺u{{^׺u{{^׺u{{^׺u{s{qڠ)Y:}t>ˑf`HTOf˹{R] = zuώ]W=Vfmb@jrxSUo0@N [ #RHL9S]6kjC^CA\إy4S*dkl̎]IVVYYMYM GQîuAq׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺEwџoo<_x~Kg ʓ _w5п{^׺u{{^׺u{ VR *!3ꊢE6їRٔpx>˷}}6m5nGHhE=oq-s+20`}V懰3rQd|5?+ =L"yh]}B=XܓW "dFɺż@Q\|Cz]{tm׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽t0L^&q #&BxRl:FZh 6r/nO<'.5-2>~LVyIs( W`YBȅ+O/Ws^{^P1PBJc%MmdIP}8i({GV<֢ח#ҀRHuI4σ$ҹ/#&ǗLgFUt<鞽{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{`_xV{OŹ.s$kf׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺8tᓝ'u$_.GN?g[};;u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{ov^*Miwݘh- */|/q1<޽W} m@@"HH&aSqecxBXz9")bvHVI#>y[yZ Ւtbe`hUBA롨" #! PA {ou~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺP9 ,MnS#Q%>zʙ,PSSF+*"vٶoNMʱEUP=I tK۷ąݏU$uQ{xG:2espIM.[b8i}ÒmyjK_Sźܸ#VZDhIu拎n ij-?%'HMK7jb侂}{ߺ^׽u~{ߺ^׽u~{ߺBQ'k?u/W_v׬Tӥn3ﲽr{^׺u{{^׺u{{^׺u{{^׺u{{^׺uj|rvR*G\RGgŖz\Tqt&toY۔60 J2Ÿ_bQuwo>ݹu SosXrte^׽u~PG:'lmz- \L>K0! :sWX׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺEwџoo<_x~Kg ʓ _w5п{^׺u{{^׺u{{^}ٟ=ژAM^,ȇ]VEGNx,oQ'kW4mjA4Tz@.OBNtn>3_5<VmN{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ '{siG s zLr2>֖2}VhDNOWֹ+_AYr]/7MQ[(~UJqDqܡ?oή`l ֛fjmJѮe}/[uQQTCU]TPjkU җrOk\j-kaVcFfIss < M@cR$DXkz@?՞䞓~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺU?ğn?s]fI׵wY׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽tq ';O}IIX]cޏ7Vww~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^t*![ 5QS .w5( EʿmBRu𯹰eȭ.e$@*۔()oQA2YL`h}AK)MO O|&0=f^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽tI>Uvvp>WbbOL;S0!~\{?#[]+a&gt1S1^N߸{=>(#u{{^׺u{{^׺u{C5j}?J+k*}?K{qљ^s׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~mQ٠ӜhAQtsF* kL^gZJa"ILJFy!_q47!$ |ՇR\q.\ruSJ6~,^; bq4f>1-  MIIOZkH噎K3O&GghHP<Ӈz{^׺uM}tE1R ze|l,190NdF`N6 77Keu%OF@=6m.ys|цRE8 <5b쾽]U7_휜AVAe'FǗPD$ؘ 7=jxj΁<OVLu.c7W.v t[HT0⮿u!EhqٯD{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^Cgm[[67Y_pt t/׺u{{^׺u{{^׺uf|{)Q[Zii^dpPzILc7o'`}oiO,n~)be`P(GvGpսg'o|-tc7Cn{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^t}rQ]*ֲIO_q><,S#,4R cGd!}Ny7 4TgMx LC# 4{oiոJ q|.6/ "0^IO#GfG%I|ʾͧ'rUw-G"Uf$ļ9gwfbO@iy 1iz})龽{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺nܬ'${w\<[]7>N?潯vnz.{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{ogMW9{]JO_~pչCs{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u A,A~>@aCz$=T'bWTv&ɠ#e*AcfMo"`3G)oҏ ~ڽ_x}#[ZĤC鱸[9-g^ZS_+[ȓx" ye$O~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽tA[C'+4IQ}"IeΔ~|`R9Xcv=p7-y^Rۻx_dHJ褀I~q{^R&.(eQ$?蒰:fsJuQLvk%_=^C'W=umL5ML,@Tce*{ѴoQ;m)H8*"Qp2MI5$I/nwo\w4ljf5'`czK׽u~{ߺ^׽u~{ߺ^׽u~ GTԾSO_Q]^SN?[ջϾ˞{{^׺u{{^׺u{{^׺u{{^׺u{A=T-MMLOOo43c $#UPKaϺhdEM'3u%-#$8o0|:<4CK@#KOTm&U ԻvfB52j$SEO87KՎE{WG].VHnT~-yd5#xF:6ޥn{{^׺u{c_*>>M׸#ܐSxzaڲ%JԒ b/َsѺZzm׎<2x$%_8UX9kJ|dL̖O%ǪGT3,z{{^׺u{{^׺u{{^׺u{{^׺u{{^3Oӛ?}yWRv8:N~{ߺ^׽u~{ߺ^׽u~{ߺJr;t64m5T_xhg$@{{;o\w۞#+eRZ,яF[N6PO8z.n<>7;jqZHk)&UEsIT9o;Fb&hSJx 20) 0GYiu ]۝PH_GN:Q׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺOkk㗥|=vs/Xւ]:Zi[b.<`ݘ}/^a9n9 *w8XZr`wQh3yqqͺ;l53IskV});-qtF-8?uG{nwl]2E-on#Z DmB#D!MXly?ggGUQUU*"ʪU@HcPP0A=rn׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^e0/uwGa='ۿbYGv5u]au~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~:j~FRV/Xhվ{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~Ccr5̝6CC9b6!>˷}k{gQdXR+)  oG#) / BlD^奓R}ݍLh$o@6|^ql+-ǴS[Hu9cFr!R20^{1կ?_-x* :ҁY ,🩷{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺ugރQl}Pd+IIwA-abײַvjG<kU%PC+ r QSQnsY޴y} H҇"@,׺u{{^׺u{{^׺u{C5j}?J+k*}?K{qљ^s׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽ugˇxIݛڻC꡼9,E6I6n?d_p_n/ҳrӎ\TwKscb0ė:2J^׼R8zu{{^׺u{G ѤJHWD`UԐA}Y)!#=i]J8 ִ4>J:2b3ã 74nƨd`(FnI!J\@e<4}0 }禇gX%[T$rT-GK25~mZO_?*VU:d$af  ʰ]{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺGΚ2sѿĔe8',suoz^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^owm,۹MhS!Ӛzu Q맫VH^Q`py}D[Up!Hf9bpE"C뗹um dù[n a*pTWNgwMVyjyb)O ֺAJ/x؆v9 Riݰj Ҫ1T-lUƨeG~m>r4psjJ GWUR/^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^j'ݭTxh5fᮭnY<'yR=swwW,l+хV{(iFF#FR{=wR1[NuZ~{ߺ^׽u~{ߺ^׽u~{ߺ^׽t(u}RڦROg>EuWmzO':XooV:3>+.zu{{^׺u{{^׺u{{^׺u{{^׺:7q&뭱lGl؜dW+b(tK*^blofy? T7cEPJЋkp`\Ho(eo袂҂v뭃zdm)& lc ѩo%^FH\RI**$ 6ܳz TQ{;-ŵh/ 6v*Ļ6bzZ{,{^׺u{{^׺uɮʨps;3-2b=2/,YI—DÑ~P4= q1?p hO@ry"ןyZmM+~o!(:jxz~=H_38|09fg qI㩡PO%5e,rX'ؑqǼ붹;Wm*FXT ׮g^YmrޣGy SgM׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^Cgm[[67Y_pt t/׺u{{^׺u{{^׺u{5u4IOUI׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u7`ڙX娣eS;_Hq:dܟDz{y^lDElx m |YPq5ݵê?!߁ٚ75>&wm7CU12k丹uO1_[Vx&l$y†H8OAe{lбl22:>[i7lc*1FUի3UB-Vfxٮ|j @8i!e얖7Ld y0n/1Rj|0tF nc nu~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽uY /cjXO|IX#x?en}䝾{_W>]{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺGΚ2sѿĔe8',suoz^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽tvWWpF F'+qn+O_H>&& B{xo. SIԴUS5҃?nc+29 wo-hj,DD~(ޟC` (h6MՓ;8Oh5&JFoceS5 lk ,>^}=oyx~Z`b|+w keNT͛?:0s&9 qM"/kЗ{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u3n67I-e\ڴF=1D$B#/wO]Ӛ^]c2,qiV@4DwjQQYtۥh2~Cz*6 }9mϓ%eN~ڛV("*X91oy#klSjm|f̲N]@кP*:ghޯpj֡a|iSAՎI#k^׽u~{ߺ^׽u~{ߺ^׽u~{ߺBQ'k?u/W_v׬Tӥn3ﲽr{^׺u{{^׺u{{^׺u{{^׺u{/]ק7E}M QCQPauHM*sir'fQ~z$0=>V667PGtVd_Ji#5 2V/!:u{{^׺u{{^׺u|rjJ*ޏ4CUh[EU5mťbdJ#Κ~ x/Ay(~xr/?mIcpl,SfFu={ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~=Fտ}sg/?u܁*N<1IpB{{^׺u{{^׺u{{^׺u.:xJNj);O XX(¾{c%/T֍ 4H#cKw%y 9hA\7ڧ?e@էG"J,NG",Idtu%YMG$R4RYTA A é2zo{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^G۷s:Ƿ6ϻa7F>&[Z}Ccq@pIL<WEHo۟qx,)y<~3E?@'.U}o'y7cˣj] m>}?ˈ4.|bljD>>X)l~R\kŘ˚O 919m0)ki+ŦlZ)BW73 xl#LSGu>wNsX%pXc肞>65Eu-' Mqn7/}_ϣ MKMEO %<AMMpS,pqPVVV[mvt1[ġR8R4QQU@Ed:OY]{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^e0/uwGa='ۿbYGv5u]au~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~:j~FRV/Xhվ{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽twGb{j5(Plk܏&xd\ a}3"VIcOt7mw9//\[ATԑ81 ec%3h5+ZzMF̎D! ^jn`c.j˘ 9Џ0EII22պJ2hA4 ï{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺ );J>A:$MWSg;@hWGvcVޤI5NF<|%$u~^RV pGQF XЧDm{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺBQ'k?u/W_v׬Tӥn3ﲽr{^׺u{{^׺u{{^׺u{{^׺ug{N,3u3ޗ7236$rf[PHK1TLoMQ- ?+u3%~gW髗 5Gl_Ӄ笍N?jYO[:ߺ^׽u~{ߺ^׽u~{ߺ^&ۘ]߷[[qPœnen/A=uT"UcKfRUyuta2x0q-oݬ&Dȧ+8# g[CtgOpuSQCĶZD3ZYdRQgG U.';;awR4ʃJk_!YO&r'3l7UhC q`#Q/{^׺u{{^׺u{{^׺u{{^׺u?kjx9ןy@'jヤk^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^Ճb6عjY}N$w,kߖ4`C|{[]?lfQ$[ Uޣ7߬;U;+SR=F" ~{ߺ^׽u~{ߺ^׽u~{ߺGŎ1se !D~T X9|A{G0{[1ornP 2j Ay0DA~?u}S}?Wޣ-Cu}ɩy?+?Q׿o O~6a&lG^?.gPG?Gs{}!A=dljoyu-Cu}ɩy?+?Q׿o O~6a&lG^?.gPG?Gs{}!A=dljoyu-Cu}ɩy?+?Q׿o O~6a&lG^?.gPG?Gs{}!A=dljoyu-Cu}ɩy?+?Q׿o O~6a&lG^?.gPG?Gs{}!A=dljoyu-Cu}ɩy?+?Q׿o O~6a&lG^?.gPG?Gs{}!A=dljoyu-Cu}ɩy?+?Q׿o O~6a&lG^?.gPG?Gs{}!A=dljoyu-Cu}ɩy?+?Q׿o O~6a&lG^?.gPG?Gs{}!A=dljoyu-Cu}ɩy?+?Q׿o O~6a&lG^?.gPG?Gs{}!A=dljoyu-Cu}ɩy?+?Q׿o O~6a&lG^?.gPG?Gs{}!A=dljoyu-Cu}ɩy?+?Q׿o O~6a&lG^?.gPG?Gs{}!A=dljoyu-Cu}ɩy?+?Q׿o O~6a&lG^?.gPG?Gs{}!A=dljoyu-Cu}ɩy?+?Q׿o t:ne+kᕔX *)v2iO[!Yቭd|F)OΘG/~G҆wDfgk 4LTkK{mݱVmW;fv(1.r8hM[4[7#}?^X `q!_˯,岵>q$g}v6eɶGO{v8uu098$yߌ?B.#U gbj M"Y{LĒ<{{Og9L*r儫J<mOdȐcڧS1$I${˹F6'-UT**T =ɊN߻u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^^:ջrrGnvˬ;|ٺ|}0u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u?5_d? u)?+~ˬqӴOYV}{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u'?f֥.>$Ct`Mª[M')XK(s>ۗsk4UA_7(9R`6_bᗑ7!o3rԂ,ijxx5Wii" %6hfK8WXHe E{k;,#xvGGR#REzrG4k4, *"0A`c~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽tbֻ2.fkuc3o.Jdb*2jl|`%Ɩ.'Ǻ\&qQ"5#JdTC#mzK ~ sYThnzꦒij*j&viIF%IdbI&mmoem,V""@UUTSYn%ib;f&II>aM~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ GTԾSO_Q]^SN?[ջϾ˞{{^׺u{{^׺u{{^׺u{{^m]hfwNJBSSBj5;f!A>^mr޸gv<?'=-۶oC-īj81ApN:CKa_58W$*cJzukRJp?ywy"c$&"Z_%ُ]7lj45 &`5ـ\U] :u{{^׺u{{^׺u{{^|;۫%m!7cl3;XD34.okRl-0 W *Lg9U7 Se!%o/RHo19*:}5s i!ć1T cַH bЏy;H ~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^Cgm[[67Y_pt t/׺u{{^׺u{{^׺u{{^׺ۓ'7+q%W⪒ )QK0=];n>Xw9ck>\5mpoUyGEAhfۘˉ}TyJTFYZZYǢOZ78s*n̷|-/m%+Z'J  AdVe)0|=)=z]׽u~{ߺ^׽u~{ߺ^׽uq ?F˸|>?v[\N\%z6^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^e0/uwGa='ۿbYGv5u]au~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~:j~FRV/Xhվ{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺUho.ryw j;Fz_^Ex?߿Ȃ2oQ//v D[k:8eϻ߼߻y_^g?18sbI6?γ{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u,PE$I0,QFIH$ۑE-ī ;UU14 dp8Q#C$,j $I$8{&Nu5?MjT,֛'2y a)}轮xY/txޒ?H2aޱ?t^t)zͽG{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^СIjK5>]>c[\׺u{{^׺u{{^׺u{{^׺uoirJiBnT%niPgJć>J bB5e4~$5*$y8F*GYq‰uOpق8.VYf$$9 ~e^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^ֿ;/[bű&jM4*Y1gI+i UT˿g9 O  lxFh$OXYĦɸK1ɫ:((s{Ǟ{{^׺u{{^׺u{{^׺u{{^3Oӛ?}yWRv8:N~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺF7fv lA\&O#`AbEVəb&齬r睞:\gf[1Vn-nIp3/t>]o?uY۷2pt|f׺u{{^׺u{{^׺#h?FݟiK'@.aHo}華{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^^:ջrrGnvˬ;|ٺ|}0u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u?5_d? u)?+~ˬqӴOYV}{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^YpfǠTxzHMszXbL8و /ji(nsSqg+n6.6"gaZ[EwyVO4KJ[7͉53xȂ\YS׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~] P_qA%,4M : kS1~Xz6&`ҺFV4dO\m`kYH֚~FRHOU=b^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ GTԾSO_Q]^SN?[ջϾ˞{{^׺u{{^׺u{{^׺u{ 񏢲 _ HL bhE`BBvNY`J#<(rP7d( +v Vt?ג.yaٓR/\8{USxl>3ob1x%>GcWE5;O%$ I+=)G9 0z$=ZGFvJ&΅fBnyX%CȣXiE-t ol^u,RjЎ+l 5$$ 7mbgAQ׽u~{ߺ^׽u~{ߺ^|(C.7EO]nq:sOE{D}{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺU?ğn?s]fI׵wY׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽tq ';O}IIX]cޏ7Vww~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺQ(24V--eTQSUSTFOOQ ѱVV06>;ua>պs,Rȥ^9WGRU S.,+2:„@ Pr:j>3(gK b>~fwF~f%7'GgohYjsK1Ҿe%Fn'm?VDD* P *M$E5]@+[ׯ{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{X͓ۛ.ht7X,u,Dt0\߬uIl*Py,SfZQXwI+;y(b& ˗6yKxfਿv!GN=T.y=ݸ21/!z8)i)b~NR}9rϕDѶ}XwoW;6bq[ϻ w,}?b.u{{^׺u{{^׺u{{^׺u{{^P?Mgnp}Ҋ|Ottf}W\~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u(X9&i(aIeF qƀH$aLʊ] I83ՑF& O=l}e;y{LBh𝴮KP,iX Ig87s=ՎE{d ^{;*wjy[榝) C}:Z׺u{{^׺u{{^׺u{{^׺u{c:.ڔ^¡hwe=4-^ԙbl/C,Řb_sr<=6D$48Z=cO#{@-+X%'fKa_6տ=ObWu{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺEwџoo<_x~Kg ʓ _w5п{^׺u{{^׺u{{^׺u{{^׺u 4I;FMCfw)!Y{ͧq>$? I1IErU/ol]l|C)#IK_CQUm<5t00xj)#Y`'4r7W{m}o 4r# 2:< QBE$sFDCDGAhO9׽u~{ߺ^׽u~{ߺWdmm?u?Q?m=~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~V_XڷtVV.H-ut'o^;7Uf^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^73 =o%'bu?z?vK8?[£޹׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺL;l7)7Y,.b+%HR ydE!UIqN9;[`1O +7 $R)2Ѷǽ|AlҴ;GGpe`JRT ꐻ˦3}1yp5VΘǒW eqjc[Xb>_vg{\x^]َBERk@ $<9cO}+k+q  9T&6>Iԭ-zu{{^׺u{{^׺u{{^׺u{{^׺u{{^~NvwqmzCp5pEuWmzO':XooV:3>+.zu{{^׺u{{^׺u{{^׺_dmA;c41 #-&֊T65ȾyN9j%e Gj$>~O8n]#\?5PN_'O9zu{{^׺u{{^׺u{{^׺u{{^׺<55L1TSE${{^׺u{{^׺u{{^׺u?kjx9ןy@'jヤk^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺGf},q&Jļ` Xf>$p^{G+;,WF\,3 1,rFvKPZ*OWz9^3{^׺u{{^׺u;G9woѿg.p /F@z#׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺nܬ'${w\<[]7>N?潯vnz.{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{ogMW9{]JO_~pչCs{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{nvm-0ԯ4j6N5?kwIbC4mb=^-ɾ{w9[i.TgrW01MjXz:?:<FI4g@8(]{uF{nKhZqe)*屲;<^M:k+2 Kryzy:&2Oݟ.l7^<َBJ{6ܿ4Ey(4m=}:cmڔm8h)Ʌֲ* lTNX uwUXU}TQ]>݃o}aMaG,Ļ6&q쯣{{^׺u{{^׺u{{^׺u{{^׺u{ 3Zh7-lN?JH7-5-P/2^l-j6-:9 y|K >3JI2@M #UlVKa34UܾVFd('&G=5DLΛ{.ٕ@2|h]Iex.Œ)DA'׺u{{^׺u{{^׺u{ڷ/??Nl^n?Iڿ/;_׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^Ӗ/qUCRqpΜî>fSue$}{6;Eǻ%.hC(hx8 E?ms5][3Fdyw`mL^_HSPd+hܟUB@/+[67V>byepWLZ"vmoja,>%7 ӯ{^׺u{{^׺#h?FݟiK'@.aHo}華{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^^:ջrrGnvˬ;|ٺ|}0u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u?5_d? u)?+~ˬqӴOYV}{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{;:O ;Mhw6)j*vqGY"/2>+$bE@%Y@/}켷ٮv`I^e k[*( 2+ۙA>33tr*ߢjy! Iceu%H'?;}LIln *xsU{bOgr?XP:ßwT!XGc`>#s~v=TOzu{{^׺u{{^׺u{{^3Oӛ?}yWRv8:N~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~_c 1$4FFXx&gx6_9@TzjV(;UD3ެO\>sK[y7Ȃ~.zu{{^׺u{Q26][o?t(Ѷu{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u/[?+ ?+$WlϺ?k޳ {^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^UNws7_}W;Dnn\׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺uߏ4ݵ;nSO8*Y +q+Hpu**ծhr996(wjڃqo9\GfBJd}??s.R c'4:  .j*:&Zjj +42VV+ͶmqH7 y^9cJI]WFYHH 9—6[ȡ*` {Kӽ{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^,SoO[a`ިnPbK)K<{ίUrqZ+a+5dF 3YS:~ +{=K"ma!#)c'^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽t(u}RڦROg>EuWmzO':XooV:3>+.zu{{^׺u{{^׺u{wjCYiza=sv4~-RefmA-Quws)8|˂u1+h_%y~Ĭׄ_ӇnKՅA+ʪUTUUUU@{²I5{_^?Txbᮅ{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^Ֆwl&׊ J! Ǐ݉i%TO |y?k|w=<3I0D041E'Ǔ7޻Nk}׋/oɾb{}{ߺ^׽u~{ߺWdmm?u?Q?m=~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~V_XڷtVV.H-ut'o^;7Uf^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^73 =o%'bu?z?vK8?[£޹׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺD;KcPtpw^6i34i/C j/5ƣ~Z^Ox'ǾZ+xnq)-}kO cQ3Կ{6I^i4KY v5'Knh{էu{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺHnyrLMFͤ2 CFziHQO\VRV]>c[\׺u{{^׺u{{^׺x ߠ)du2!_:SRr Hf!TrHi.-$q(]UQRg|W]ȱƃ;'[FsN?潯vnz.{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{ogMW9{]JO_~pչCs{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{qQ[{ Y7vai#ULmC(&"Vdf?i mMZr̚[8e4Cy (ZLxͭBB ο缿a9_og9bNf@;hk!ɾϯ{^׺u{{^׺u{{^׺u{߽،]Gly'hTcC( dx`7`cBm!fo4o[ZyvU@ 0g!|IF±{mY=vk"Q(q$OB1FzD{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽t(u}RڦROg>EuWmzO':XooV:3>+.zu{{^׺u{{^׺uKi|rG]A-v)c">J,02.>=tJ?l5{i{_$+e*uGf2>ć4ʞ8={ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u {$ꚙ᧏M.n+ E=&iCү74UH{gY !)oI,zllWd̈=|E*?u~{ߺ^׽u~{ߺ^׽u~=Fտ}sg/?u܁*N<1IpB{{^׺u{{^׺u{{^׺u{{^׺u{{^׺9_3*@կ5nؖV!a!fҩX ccԙ;!.[hn,>ua}K{ߺ^׽u~[vY՝cޙԌ3#CEldË?SeOL̷̩ʠBͨS3Pb$?(?z81^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^e0/uwGa='ۿbYGv5u]au~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~:j~FRV/Xhվ{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~a jii"IdhxdVXe*H"Z_KasG$r(xWGFYIVV0$AfUd2XR(ATr:j:6wۧ~԰#+m+%wGuRԓ915眹.%tDjc\5[bYLkY4/Y"%zu^oNc2 %@( h ~׺u{{^׺u{{^׺u#iQrng#ŐN7:ٝM#N}}<#whr#\|PÜ2,=Q%sGlzoUhs\$Bdz}T u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺:>?SY[}3|b,w}={ߺ^׽u~{ߺ^׽u~7-{)}㘁G2#-usȔ ۚC`ut{!C`t~@=䛮}6H-K*F?{4M0xLNb8m*c㥣Ib"T\a' ]]\^yvK]fcV'I]2۬Qb54^ =Ү{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺ 6tu9"IUS>+5CzjՈ`@5Fއ`O9s~ -ã|T|d|8/nC'N֠-_čCSGZv]gtf?2us "I&tH"Mu6wmZNSƌɁs [n6=4_[JQhi*d4ʐ|G{1裯{^׺u{{^׺u{ڷ/??Nl^n?Iڿ/;_׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺRh*t3KYEQ ]%L,RjzyX&+$R `{M{eiY˷ƓXG"08*2=^)$E"VU 82VK:88pgiKD%Zje.M@V }.uSmoKw'H' &Y  {ڗbq q?q+_^׽u~[{_;_s}V,s=}臯{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^^:ջrrGnvˬ;|ٺ|}0u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u?5_d? u)?+~ˬqӴOYV}{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{]Ƿp۳{ 1JuU 9brYOx%U)2\}ɼk.n043 G0Y#hȩ"uR mr hܭ`%YNIV:,KnKVkgot]kq™|xuY@( Ġ޷v~m둯/H {TvLudV@ӟj=۽i} P9tb5 BIZ3-)u~{ߺ^׽u~{ߺ^׽tכ##T-.7I5md.R$* ;=l;̻ͮFf/&XAh*x*, -pl&%s<$2z޹yMϑ,_ &I@JP_ً`Vw,}~D=NT(,wVl($$u!X|w[GEDq8MLYRzEt׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^P?Mgnp}Ҋ|Ottf}W\~{ߺ^׽u~{ߺ^8i#Yeu(Vy$*G(,@ LʪY I<ʬ.M$PQ:)5HvZt]]C# ..X>8Ѓ#EFA뗺{{^> ̗;'k{e? {D={ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺU?ğn?s]fI׵wY׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽tq ';O}IIX]cޏ7Vww~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺHNͻO%w-0 e"@HE\#s<ò>,fyFPEZn. >Jv3r1ZL*Ii2gS:j- oR/^{)>wDK\*1\Ó騴RNFmz`۱ƙ#& q? pԇ\ya׽u~{ߺ^׽u~{ߺD7Gg}d]o&>Hw<0)=}jYeI@%\3 6hϺ'ʴV*)V[R q ; >s/(m$pgHpT`N{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^P?Mgnp}Ҋ|Ottf}W\~{ߺ^׽u~{ߺ^Ֆ.?|o&迀AS|EJVQ$mԛ^&J,T$?sC,~)J;{<ۺG]oq1-ȡ^"as3u}/׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺uSSIW5TPMUKSOOSO:4SA<2G43FYXe$ovGxI+"AAIsFJ`C)F8#fa|5}NfF&>׺uz={Fg]9JiGC׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽uY /cjXO|IX#x?en}䝾{_W>]{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺGΚ2sѿĔe8',suoz^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~8>2|TyrUm|KɌȼj 30 UkϽݻ>~{mvbIv;EQ%`^'o-BcQkQ9Gܱ˻5ܿZ"wjgJ(,ti띹muԧa::(dujkX qQ$K%DI 7iw,}pTZP~C/mܫlZ鳶(8,N,K1&{짣޽{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺-*:L%<;;i$4s[Tx.Ե7Ԫ,YK y f`pɰӝG⌑R!_3BQ߹oܫ.E]?նs24R|AT zְn-p m;i)JIWрet`UAGmsoynvZʁԆVVVR0AF7]?׽u~{ߺ\]4y$u8՝*"(,*'=q`@ Ndpʊ] I83Uec)棕tMqyB I0ÀD)T׏$G fjeZo\bH240 !3[ݏj"@sQ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽t(u}RڦROg>EuWmzO':XooV:3>+.zu{{^׺u{]?~4-ؔ;OҴUTd\_d2x/9]βqP~(zο߷n7DŽvƌa>Pʽ]do^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^)2_2tq1=MM2Ï4eUi*ʢacJ>s.t(nĜd_jegA}}KϻRLU.Y0͎L# aZBz/y )u~{ߺ^׽u~{ߺEwџoo<_x~Kg ʓ _w5п{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺50mlFF_$tY @/cƫFzkO1l 4uvpf[AR_ZܒI$(AMr=>L=󋩏{^> ̗;'k{e? {D={ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺU?ğn?s]fI׵wY׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽tq ';O}IIX]cޏ7Vww~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~otu=( | ۋK4K%f;5Pjt^4Wm|{ck+H-"R[r(%誡@ ,Q$Mpӗz[1F$ ' u{ߺ^׽tTOwsoTinX%w ͔Jy}NmϻUضvyMJG$?Ƌ:?Px GjUOU]bW^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~ GTԾSO_Q]^SN?[ջϾ˞{{^׺u{jɛizc&sx~$Gl>-c08fR9K`+jn;h2Ez4+^?pi$qjhUDUUUE ,u6IT`u޺^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^{vml9ݡb7&.5t- 7*"ծ)V2m[][@ɔ} l޶4nXPЎ*FTEj=;}wNhiU#aEjȬ>ǔӚ(wZ'UTV똼܏{VXTOJx> Q @'A{{^׺u{ڷ/??Nl^n?Iڿ/;_׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~sI!%)bu)cfI#2::A>$qC2C)F8#!էtd'cl*jC0,n~ @y'Xɲa+R$/ &Yp+ﷶ2{gZ)^ѳ@௛@/|6/޶Ȩ0c Kվ|/WwuOCu?,bˠ'1@1Ѿzu{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u/[?+ ?+$WlϺ?k޳ {^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^UNws7_}W;Dnn\׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u-j焥@S+&ᡅml}LWِGsU鮽֛ d,Pjۄ?$c5ѐx/'?wyr5K[lcw'GL~5 ׺Nόٻw+shҵDDR@EeKQ@,{w*n|`Mtг w#:Tҧ͗/m3XવN*wno=ŕܹIɼ7,ʺl, c=8YʬiAA03HQQzNQ׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺBQ'k?u/W_v׬Tӥn3ﲽr{^׺u{l-Xf2C$ q:Hz?fu^{{^׺u{ڷ/??Nl^n?Iڿ/;_׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺBQvG[,iWl~~ "ldH:h$h1RS/rT( ?i!34BqDV7в/GܹɹMv=T{Nk4GSKWU4xg5urᔎ> ukscu%4WpŒ+:8%E268r/WwuOBu?,bˠ?1@1Ѿzu{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u/[?+ ?+$WlϺ?k޳ {^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^UNws7_}W;Dnn\׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u UKG< ȲE4R)I"7 G"1 Ażp=$GGՁ AH"^9$E&+*A5A# O(:չwݻRYz5Ti;W%; oQ9?s17J/ߣyqW0<{G,v,ĕAZ$/(oȫ}Tj?Q_d2=d?U{6>"l3e^63o6h[O}s70-7ɮH?PN:zy9jٹH8yD*)޽{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺uOT_y /(XK-Fgez^׽u~{ߺW8jПyJZgy؋$=#Z樑q{{Ҁr~ܐ8?~!]}nݣVYGbr񷬽׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺׷|rDv`vUeMbioUÀT0zV4H h9òzJx_&{SMqr\|RG˨N׽u~{ߺ^Cgm[[67Y_pt t/׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^|[brJUF6J`-fyq11v&`,|[qOrXcv4IG9G8*5iR!o4'eoՌ>kj'^?52^|_s}V,]PLr=o{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{ŲܛWin}?ql̥#Shԕ+N,LT=6|l>_m73XX^hPJmF\+BZ0~|a&Xؼ[ p6G]@TVExDoϪ?Z?0\rMٺʟ/[޽}Wd 8}?n׿#ۿ?l]m[z]6_ I7^\numkvO۟~.s&l{r=eU?nx`䛟u/[޽}Wd 8}?n׿#ۿ?l]m[z]6_ I7^\numkvO۟~.s&l{r=eU?nx`䛟u/[޽}Wd 8}?n׿#ۿ?l]m[z]6_ I7^\numkvO۟~.s&l{r=eU?nx`䛟u/[޽}Wd 8}?n׿#ۿ?l]m[z]6_ I7^\numkvO۟~.s&l{r=eU?nx`䛟u/[޽}Wd 8}?n׿#ۿ?l]m[z]6_ I7^\numkvO۟~.s&l{r=eU?nx`䛟u/[޽}Wd 8}?n׿#ۿ?l]m[z]6_ I7^\numkvO۟~.s&l{r=eU?nx`䛟u/[޽}Wd 8}?n׿#ۿ?l]m[z5 m~ܩn]69fkk552*qU_EOED)ϏݎGMs+ϳ]O}ZBdi %[bQ ?w7Ylyx7[Â_HIm16H҂׫MݞG{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺u{{^׺usd־kȟhk7;џuI_^MOS~T4|5ؿ{Ϻ$~&r')*?>@_=g׿zo9{ߕ ?Mv/cFo3I?k߽vI~oʏƏ&~s_$޻_D?^e7GG]QOZ]Rm"s߿F}'}{)6?׿MQxDb9nv>d־kȟhk7;џuI_^MOS~T4|5ؿ{Ϻ$~&r')*?>@_=g׿zo9{ߕ ?Mv/cFo3I?k߽vI~oʏƏ&~s_$޻_D?^e7GG]QOZ]Rm"s߿F}'}{)6?׿MQxDb9nv>d־kȟhk7;џuI_^MOS~T4|5ؿ{Ϻ$~&r')*?>@_=g׿zo9{ߕ ?Mv/cFo3I?k߽vI~oʏƏ&~s_$޻_D?^e7GG]QOZ]Rm"-W{Q@qQ)24S6DMm.~[^ʾ˹EnvkFimʧ(DB Z j[\s1_&ඈ6H@H|Wqd?(m?>F=?_^ec.Pe0}{o{־˷]{߿\G]`s#}{o?⻏E'\CoA1GqZ:osv/c흭:.جY&ܛ7pm,v")ks(U@HsE̾Ͳa{gw|HhfsdDܟ8s1[mݸXiR5i)lc5b*4F7˓)–)cy=A# >j}ioˍHYbV*Ȉ2YVimյW1Ĭ+VEzae?7W_=.z.dwO^eyouSS5ws߿\F]?_^+̿-noƮ7ؿ{ ˻kEy׿OMx&~swo#}{?2))Db9.z.dw}_7^e?7W_=_eݿ|j}P־W[{]o=їv;׾/M/cB2GqZ^eyouSS5ws߿\F]?_^+̿-noƮ7ؿ{ ˻kEy׿OMx&~swo#}{?2))Db9.z.dw}_7^e?7W_=_eݿ|j}P־W[{]o=їv;׾/M/cB2GqZ^eyouSS5ws߿\F]?_^+̿-noƮ7ؿ{ ˻kEy׿OMx&~swo#}{?2))Db9.z.dw}_7^e?7W_=_eݿ|j}P־W[{]o=їv;׾/M/cB2GqZ^eyotnf1O]\UR5Hn$3!)"(~fnyywYv˸Z9.80SFF &ervʳˁG[K| I6ٻaf6Zmo,[mf0I(!dP:}}i=y/nma{׈fx,)dU%\  "uԍԓ_^*- N &>}y^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~{ߺ^׽u~parsec-service-1.3.0/doc/images/partners/rancher/rancher-logo-stacked-color.png000064400000000000000000001126061046102023000256560ustar 00000000000000PNG  IHDR$"yվ pHYs.#.#x?vtEXtSoftwareAdobe ImageReadyqe<IDATxMvF`0'sib ^We©)&QVjșW .+·ՉmI<ԁӝLfUUeLlY*GuXUYKkys^;{? *׶ 5[na֛o+aUYJ Hmp;v))Ἤ0M4ۗWܶȾ_FSXvn -7yewn`<{^VrtK!`bbA! `綗RHuXoΛo\fKm<PHCW\J62)$LDa+/. }=h-FhBte.g%J`E uftO!`] [3[[0])8-QH/R@#7hB@-ߴ|`i8|vI($oe̖2]D PHHX;IGKuU{khB@V^DG2B@fMuaI-/>ݙtKQf' x5͖y8pUYױ3B@}>5#pu@ҴzfͩQr@< cas)$yBp4] pD4|3=$|Y^N! m0zm($cCmRHHl9oܣ@\2 i~#l\wgRHHHy\| iȇfa s]6|rSK8M{4H\.x>"`# 'vSH_v0s;B@r.B@fi8I 8=< q&b,r3pv*p1RH[.p1RHl9I0s|d7 ۇ!s 9ǜyHi Gv{d̖$pRHByFK! ^F$p -7`P 8E:-7boSH\08$w($) N!C! Ns1B@NDC|B@dfM.Ep4[n_`4ߠ\0 `s" ;$7($DdRQQHoQs̖qD܆uUY'r#8txswJۤ()$9oVK7Rx? k[mwe>?~ڀçyI7Ou?.Zu& AG뜳l_NC Lrx p٬#r/ }1!]:s77=6gD5[n.20mowx}nWى OGa-D€NE4[nNê=[XmSCUX?N1($W($4\wXoUuao5&swջ|)&#|SH "-ǹYb¿Q;]/`~Kl1W*5RB!xzhG H\]6G80p<[[-q)$@78XJ ' E]x.EAG -7uvԥP߮@k ϩ Q Hl_$Ъz@=?@kbJ 6 j%ЉY;=@*%D($@~Jq57 sN))$# f$:U?zι: QHDЋyw"rNby$ B$LY:nեK1֩ QHD43גU_$E ЋƥQ$7@)YeB\ Лu#yxک qRH̖/D{bMM1JlAh($@"IߤWosJ Gv{k~AFzl\{H"~nk~1^;|͗`0W"V8^Dxq[`a.^͖c!({@!eTt9Ra.^A|xfTù$&iu$)$30y]E @֙Q Sf'xxȺٰ-Lʝ xQ ԪrwI1 PH]eF5L]d,b c r[ILޮx]QHo0Gn#|`<"`*VaV0 UY("0 ͨ_$@cWe1LG0% F5W=}UfTqlE ɻSWz iwUY($@èq XH̨ٕZ)$@^|Ů+ʢ 97nH0) _SmW:$IRH`fM$*ufHu8)$0iF5@׻=$y)SH`";nfO])SH`jnK@X)SH`jvC]qUu⃇STESR׿ʌn}uBcTt7qփѨSH`Rf<3ٍFTeqy1 1BSH Yl S{<*{ eRΔhO}2fׄf~4ż9z=ݻپT}ޅjm|m΍@GHQ t~J5zo:w{?@ZS1t>ݎ 6F6 ͨ2 A$b)#ЍKg^Q tF:B@ Z$Po3@p8b2]ΖRSȾ ՌjE% m,DA!( 10{L!BZeF5пuS4?s7b@tj`@v[1гu DH! K&@ڹ$)$UA R] ?cgM@Q R]Yb@LDԥf);|;$5(xyX1 @"ٺp) |Sf@B~XlYeU[*3iQH`Ԍj Bײq7"{8ORQ UX7UY18pV=A)Weq+ `l~]-7y8"ZŕRH3F5t!, D TB0uaUYDZgTCUe!O * taІl+­(@jh]Xse VvHmkUY\B-7̨רG4\Ve;Z1[nN$9 #T Z>u Z3$*($*F5!EU7Rdd݅+#)SH`F5c/#܊H ĨZŕ)PHPkuaD ^ߧN 6b>Te!XM?2x,vD]XyOx,zbwuO($bUY,D #| "ۏ1HCXlv8Hc@]JxHEpi($p,v'v:mRHU wܝw,<Ue߅zO *U8\Gx?5ovz%?]u֛HnUY\zgZS}8a}MG4SFBjJ C1yk@wh]UMisB&ލ&})$Йf,7s-+c@Xç.٭ %S1c@./TPn$(1Qt*rSrQWk+ nv=;$Ћ;yWSO2(e * 𮣋s~j)#QHWUYC[bT Q@]UpnRH`(~¡q E{X)$0,!2n% $.a_4`_]d_=RH"ىBjv9ȳwJK ¿)5 quF @\SJ8 CbBVbRf*7`FCǪ,bRp) % 0UYD!֕($Z@`8UYH!*,bRø2Z@[1)SHmEN!:V@_UYN!u+` _ $($@ELBk+` )$@l&A!zTŭ)PHZN!hB:u @)$SHZN!hB:u @)$SHZN!hB:u @)$SHZN!hB:u @)$SHZN!hB:u @)$SHZN!hB:u @)$SHZN!hB:u @)$SHZN!hB:u @)$SH)$@>s/SHZe< g+SHx2-V0,PDaw"xBD*zϒmE4V;N!z4[nN,nᣴV(:zou?*+1N!̼<.,: Я+b"lJ=BwI;gRH͖!o@SJ)C.Sen)$0C߀<8 SqwF@`cJ &3PFB d܌p_Hsfiw)_? 0ŘnLU70룇|?ۧn hVU rGUa։H} k1ƿ_Saj7*mXwa}0+#   UY܏}"ۗ'XEU]8!utǪX7;&G#Qza+CpBC1m/އ"EXo<|.!lú2H)XEI}9!4SP}8۰ɍ8"y  ?akxgrB]Vț_Szpyh݆}B$pBWHT|*{QA0'a]H  Fl-m| rWN<|_?6iIft0 ]!bq:9tK1YGRZ]^̽f~ `Z_儺#S]fK | `~E"0kEOD. ]!SX ;"0׌p̌rux]{QHcQzwD#}8H#:J @|C B{wƱ d+[xV`#FuV`DYiQZVj}UfDI?4(Kd=d7~ nkIx8UJ:{%݄XHQ9MaN!C4)F_)GJtU1d ;a:-M $l|>V `70=B Tsy(Rm%Rh @qxC%: ly~Tm`{/)y uخ{y@|w 77C}߿)=W?Ub0^)4`;W_N K7tb-ali w6`Δ6l3I /a;W.>D/, ݛϓ$@m4t goP_@Ɨ/۰Mv6T:H $@~\c^*k1iXNIsK@Tce``cwPN,mw:);-o P߿y p?P ̻0+}.*Hn߿)5Uv%Y3Xυ˩*+XO Uv!)7Sv!d'kX;`7 @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v @v sB u]$@?*@L>H~)TF X$@?X1S~Ur @L > PK{ S%T2F` [H2E% 5U{"د`k JY_%s8ؖ@Y`J\ֻ|՝2w _ƋNC_>5Q+ 2Q\ X*" `$@ċ_6Q/gJ@y @HZ-ϻtX `/l @b~ ^01k;SA Tmq{0֜(C"=t4<?¾s X3^l%v*HF`#q_)6suxJʴ @ԃ;׾n5K5͸tG'Ӊ ^oo>iTt'x35OTƚ20T:$@ϥ BxL]!Cy)FIx8U 0rPX0k`bW0+< $o>d;rX05OUX̃wJK A/CA\m"k4T Z?Ϗsǻ0wY{Qቆ"}0`pgwnv'<..>M={y88mOgi~YUPA ZAׂǏEႺ!Q7Xի_2l3/vs.-"l0*8ע´f+0T^\qQys̻f^|X>wp\?tTҒa;} Yg B:quA0g~XwvpKڛ6ipB<4,,so< aG:$]m5Hx%UZa&4)~OLG `pVucrt~[5&Ln|`]ivgɡIkhveD -CK3"&i _:! @:l̃6:CEۧ@`C &hU*dwnR`F[̃'*B=/'8-6ME?kbHbm1&Aͼ%+J[@*z+Z,]gK4{^SrӥP%5e~. %=):tbX̗q#,n)ov..>vW^ X'A}J*"P0 G+6S},Bں%oR %_V=U@ w8*\C;KmJ'#gqlI1;-a:͒_\|G2=jA5..6܍N@e$8IPû<7t0@G͖| myuU a$@13(PxRtC,}+}M^hontq_ ΃:V bVlTK߸,acXׁx4uy ra;˺Y^\|w ^odboR 2+@0%`s@f}!0u1Ks,ې|i?/W|r?HvBҋJj#PיNǹaַ74L{ ehxz8I+JaW-9YAI& -β P@g2q|; sE--@/{L]U/+>'3TN8Sԩ. =i\;++!|Q(} % W+Xa^!A;΁\C?mZ<PZ0-KCf|CN,NlbWh}i=Zt@cМF'Ε0'!lg| ܻ{-/2uK﹠$*`N#q y0co}[̃8r /_q*C?Ye6gBzPX0 _15V(W>.@(*3Q:$@.`{>ݍK>tI  37G -(CYeVz}|cӾ 0D:D Z8W*A/ $@JY0$tہG`&JP=p@\SwAH`J@/vG0`nꭁ@ 3vzO5V26y+%\ %:J%53!%/U?ȑz%=Sqߞ AJ;w ؘ̉@ ˙a:zŻ@w$7*~)0P>@ . o\Y+`NDn 0<t `3lgR̃@ sA `ԋOh/5 N '^cl4&QUgi{0m 1Ty|;Mp0;3N 0LGgalC'juK4.\L?p=cċ<0J wVHU:e%Ljxex$^@~^ŧ6ҝZtxqo;y uDgD|0J#xށy?ΕSR \\|ʶ'tr9ac.hcS`J%G I& EQhĢK8>Q֋>Oq9I~Gᄟ[hF`J%G I\ @Cis0tGhx]S7Y~{-O'Ь#Dʐ@8*C `ce 6/.>eq򛧼PJJPxiұa~:tB.v= ezZ4i HAշ*JWa; Ǩi<;؜9eD%P?GK>61'6Ӿ2]a؎ӁA{B -X r&hlA K vb0V>KQ>4 @{I;a.dE:~_mpGh+u0QhDpn** =tI) wg}&i;0sUs\ើW*8N1S1_VeZmyc)= 4C j[O #|3׼Ns?T:8]'a /|>̉zM :1pjkR 1' i_^kD/q+wGըųT#n qN9Nkp yp]XUۏ#%hPJ+^4 Fqޤ/x*a=G0\ٓ@MXi91ዊ^*Gi=1$0#F lҒl>yNB)47ΜhA5B H άS*4nllV#T7:TH 9OY%4=$"p :m=+\ܢgR9eG5IxC%*9vTPBN`w f3jd64Lz4i̿\R?nKVbмJĮew'@R%j`Z/ل 4/^%>znas %gAB*nhto*QwJ MXAz!uhz'Jey\n :cNz tp"g, ,yn[TfNQTJd%NT6H .P$mePfQ*©%tYz>Wڽ s242##UV>H NHK7\j$ 6@{+va.,m2&V'J:$Tr HAT5Iؾ(C6eRwuZP2(ZݶtN DZ^-Њyd[R%)U5np@1}S`mxnA D+ޑ uH]nU6HkKYJZ.Q:lTh#ҝcym5S$E%R..> nTh]0`0q},/˞@ .(A\`J|Km>V< # Lh86`iλߨm#@WmEN{8 x{@k. IZ-F -t}՝ ;sjap t3t@:$xA׍8&G̡y@Ya tT Pr 0C,'Keż>#G@;2N $US%;o+>fs@6J5Dm&J` λ* tŧxWJ! =X'%a x0G(CqE%vjS%;HfJ@X..>]/X,MFJPt{J'$ swߖu6qnJ%V9ۙ2cKe茩="*TO o3. xIZRHƙ6 t*o,;yOc/p+OA% /ZwdW.ضXH=WpȽL٧ k $PZI;yN 㓼u_*]RiD@sl+X-P{#$V"؟JLm`0M-ǔc1 pOWv%$tI:$g;y+E>8r%)H4Ec0ݑ6k%a{a_WVam@D"Hf! !{WI"  "Hf%6|Át?tHxCQY`?S-dV"8ѽ'$nVܸrvC k. 4"`VzPr\ϕ3)l~>Wϯs6sNxx:=O"4c2Ug1F$,@$XE I|뒰\^:_~$0{g: (O(Fx#$M SQ;m_w3?ߏׅ|~zt:KeOT6+ґ{p>lD?> ttGHY 0rk/^vh+n3҅"o6Po$iDE_r"uvL ۫ꎐ$P`5lt. Dڱҽ(6 NlS0>}}wg'Jwk#fbڑϫ ! dZI"v>6*!jN 94 E- ^XzsȀ Ϭ`Ee"55D ҧb]-D35n{:s4 ZaﴑPns[ ҧb]]K-W"?q}Yr'}L =\ue վ\  4*FB;GύV?t FIu#3o)H RЧv0]ag&r7WQ_܇=/u3L"N59Ar"`hO ĥ 09#LWU¾Tٶ!۫u=??`,OO㶟U3iƂ~aO蝽ov?V*E3\%ƉB!*v~^x0Ji?31̑T u*[ލF"OAC Im_^PeoJMuՎnnKyxiО[;po]˜( :G"(hU>nL (z\b^< زnW҈`/"x!qJQٶ"p `?i~YWraAo k M7j<a-߰*$^uU"]?իE,jbJ{0Xi @r+fY)x(#$dRG헺z\]n^#"بUb`[gQ%;w[gU)u O6!!H5~fqb 鎰?+_wnEh bZ w$) Ϻz\ax#$C1Ѱr'oo0mİqx~ݪϕ>bG87TgJ-\bL ) sQ.n?%STӵR`EOP cB7O ̝uWyG8lj0pw S` ryyǿ[y}-o6(X"j; ɝ9XW|pT=Ν?{]$ʷ lې8W6~ מzSj$L[02'Z-خumZVQ >1N>=1e `܃Z ]V.D:WIQ$>鮲W `nF Eb f}뻢/"ks"Qm/ 0TbuwC3mCm 8sO[r%~)H́lװaOuףkI(,tl? 0.׶jFLlF~. 31٪a `<*+c*m+ko@k%]|8 `<>:r1qD$҈'$Lw-|]./uqVqES`:jQ|}_/7ރS ]Buw5mCm`CU]\@]|x l*"]V}FD&\T$5LY˩S :UvŽ8` ~%]L|~\`\^ZИ  PP0kӵ(2|qsBmH}-~cc!4"]e\! LDfZM?ƈ9 龈K¹PP!/ <̤ٕdBJz>?}+qiGR[yRe63ɘ}C0(wm 9tIKhK/nL yRPr79xǎ⾑douI`ľ#,ȓ(bjl azt %w%W$0V !=v'mH¶ ֈ7K‘PyS|~:lװɕd" ]z$4b`$+Py +#F KRkDЛ3]/b(Hbbޘpp<]ÓRmuKBP_Z eQyyF秡$^U$WE*H{sBb(~7;fW1Y%X{s! :Sgr)H\twҞ31PYx\\ `Xa忴R~Wcۆjùk~/cg(ʦ TeE0tG }iMN%\żZ./kAA #lpkP&X7\ .QmK#Y0Ya1o芰x(H~ڢaXkJmH=AZ%<-Ÿ(H ңv ]bp#lv4"Mzh`[aWyGLA3W;~M6Ln* !wVSӇpO~Q~m~-7 @jr4kҋg_etpN#@.bSi\H;fc)YU:(S{\_H$po ryZt1  =ÿAtf ͖ ͮMep=OR*~\^^bZ$@as;t!<ٮa+_Dz[amۆ$9"'k[1O8̷B걢GLn6 RE  wbH.Z`a{.L_ TtDfW~6m鮯$zsT7_ ]V혶mC2 <^T$]{T D[ !M7)BLn6nůD;luBE-/!^Tux&P7kAʯ6:6ЫZɽo|:z(~?DcKq]Vxo]N5GΫ+1УzbH/ 0>Bx'!܈md?A\(F+7"gCI$w6Z߄"N˷VtHFfc_O~Q}۶?{Чp>Cru5uCU|~ZalװcUlUG؇u$;oo0iӕDJAe]WI$e+ /6~Mzxj+KM~FWm_(r΍iBǨn a^s<{s"paϛBFAlAu(NȦН=b`szݞӿ$d=oLiDZ&kE#|Ƶ_(xN'Txբ(uI۶ H]qҞG;Yt[)|3ϐP+y؞3]7~~ 4i*L~_ Vd[s, ~"3Լ鎰+ö Q  wbHw~wE0%`;C7 `wW^g̈́f <"tI$fkZdY|~jQ{W=$3kؚG+$ᆓ,q_Q8wWrXMOb>?=)Hʒf&0y8 zo&&DM貹)H-&ώ-[<ڻDtm`s{~D2CBzG"  ?U^]tG14y1?(ٮe Y6- z&ڻ&fQ4 ޵Ty,N HlE ED&aI#~""H 7A—g9/`|s E]SY2 {1&K#-J o[ z_w$0y?Vl\ "ww(i4 ]H o1D$*PB2{\vܕbF Љ"Pp%I\V|_[jyf9[;23z x>~mI\u{ <2ٶ܄ͻpSߺR$H仯Ϟ}—]`MxdQo|K¹qSqȞTmg((V])H #*X+ VE<CRiM…ou[ilV EgED=1P﻽p_ȵdm^WW تj&{&u'#a{w~pBg QDu>\0^ nO庲 (6mgd6}Ϻ|.)G6b8=*ԇDuy^p>E ޸B. #'}"8| ynELLI {`{x̟^PW/?oŰZP]'Թ#wyTDGoOo&ɰxM#t! P{P+U^{]n'($)H^'= nQ. 5"' 6+]RE`=a5x>tu ~D8V w$6Ͷ dNф*ݍ;~WRLXԛL-Q՞i4"؞`DCw+f"ӈ Գ(H`T/tIdk=)Jɽb@dPuIH" YbCDA/ՍLDwbqQtIH3:5" G]FP7$$ӈ`<$$\I":]6g__$;+5xAtgCAa}0nTF˶ d, A/M$w$$뀪L|~v 0 Ș-(J_D.D{>?}+)H`WveVmYއҬDЋx\^kID%a$뀺I"ѳm;IPcֽ*{r_At$OAv&Կ]LJ-@~5"0O=`+ $EeP5':ixb\^ա_$A$EgؙA{$|$`@-b?P@IJK|Ow r"Z0EES WQ@1L>;]uO֞gZd;U pҗ|{xY |϶ &$XN(=g^ފ!kl[UI}Qo$`@-Ǜjk^ +[}TW;B!GXvf@:?xQo$BɌk @AEY./C _ܧ'{Y:F[젽^UyΛ  P iԑ~5Ķ \x0g/N$tIH',n.C He!d WcAllAAE 3%A纲N<8Jj'2 jRUU(H>(0+~I$.Đ PוW2*//e7&p(>h>O۫ gaưgO8-0W"H}3^F$B/ZGk` (p(t]^[b^|jk1AAԲ R_R@A˯ SV } d((s0 jNĜM0۲mjσȍ*buyD+@ E+ 8j1mAA&}D'WM|&h6^@{ue@%d@Apj.`G4RPSŠ<͛˵$ܿY/ z:)J [!/5tvAؤt&V"xpm!K?% HA(H{EVO)FHv -(a 0Sxdwz=}pHOds)9H1B|ݖ@\ JQ$`@uj3` arג c3ܧ뒐O޿! AL=߷_HW(au 0YXŠj  jD(vzyHv y{=עHOA}jD0396mZ]+$csQ钐M{b6PxFkFgwi)Hum@tPP`e3`,a 2=6Khˋtcz¡E?5w1 ȚU. /5lV'.0#Ja9lܯeS"h%?-eBB syX|0OBsq)H5пZ[+LV}+W8t-$mۆ\ h畉4A~Cv˨߻{)_"U'sTCE%R. x*LE"aPJ5b};.n#UUe&%džn|?v]V.4IG㋹3&!IF2~. .I"np};TPj8G%|Uٵ@{Yx}fڻ[ -n1| E 1 o8()HHö A]%3k圢Ҽ}_Hl(]q8  Y<͟ݵ0}u7;|t$0nP5yq$=/6$K&~U~W Ex*LOV ESvix@>uI(ǻ[58Nj@J}yVn(Fޫ_%QP8?ixy:ZUm's)FȃuB3·!!i;2a=߯mmcx]#PSY^?ZHv#CUcΠQn+|t-V10a}]mʜ_E1.|) k ]P@ϕ"M;6C:|Dr+Tm?OЫQ =ەlR9-/ג cs~U0Z"l5EcgI(H 1A=!mH#{ȄcaV0:}_Ww|3OA9labi7 60{Ux|"5;[S, dkSKS$)HHǶ z*[0V@>oߪ yTi"`DcźEdz|^La&=1@1ݢ^ 0I'cwk%Ҥ)FIö M(Ԋ\}t|Bj@q/@{wF*s*8kM:H:;+ s\;p:t`W78s#JCRș/I. ԮW $a+> h\(=Xj@)fH9!N L $؊ ˅J@q>duHM1r\PJm*)dɒhlBvP.eŇ`QoM'P݀VDe qj. ,[+{v$fQ/H`޽#nS& 6)57-Q7B`ٮc.Q*CQ R*ъ$vj\ nq|cX[K(Cph5ҤqKʼntiPDD :T[R^ ytJ@}F\.i\,k>m6v>@tP25-djQ 0 ,E LGe(‡H6R f{~rl5$B%(vA(N](T?zP)@i5(,c0Vo(qA@,-#`(]J/~?S*mTB:%=]4M-@q(0Px(l@JhB p\N=9p2#@ EYPH :uS˴CvdQܖۍLe30aY#6*Am8YOוj!n"w9kXV>@5v3Vrj,cheb)Dc ՀOywa[4ö@5Lr*Jwx^ 销Λx]N߅cϹ pjM-HuMLNW~Km>dPC[^i4(aϧkS[ ޡ %p*mh@+:%| B$ЊHK5^kjVvE4* $bzn^JzrlC> 43^^07TBıv>`I~z-6ahS키+&/mp51>ܝޞ;\ qWA9ϳ=9v-}^Za{ȡWlh]hal?bJ28(kush@:mjWJvrQBSy$R_UF{?"&pZݯS|xnׇVܩSg$mű f46m~89е 'Pۅ( ex;gQ$8'NfL5crDj_ӵ޾6iCkX T΃mٟې@(iUp'2~7~:LWT؇@X@k1ؾrR ܺ ۅqx>@uN{YIܥ> A H5oi0Rep}mfj;ORUѹqLx _z%F{}O6 47 PNʳr-ݥlj+g( _j[ Nu^I$lWR}P@Z@cMU=^ܤ-'W$ש-ұ 7Jyp/Ϸ40UخEe4(G V,ڳ(|5ΏcX4Co&1Ms8O]@k|VΗpBS?s اćq /Հ~Oư $JA.'Hs7)<NgCht@sk?M7,pWm. ؆ԏCK6_epB=ӸJ :λv 45- A|1HMl6Sdz=K:¹^r6'chq2LP. NXAdߥ+R[? ds1SU"xOmLWoޘk~ۏ }p.z4ٹކ{[ )a+\a֏ 'O *;}l,ӊiqmtǸeӹۙ )DsiTwէ,A ->+aa8ܼ4sUg^0vp{u?TXB {_Gu ; i#P\{(U1켟 $Ug($4CH=-1PXTp[\5n\w(@ŋBH:?3ᅵ?C؞—p4,OYIENDB`parsec-service-1.3.0/doc/images/partners/redhat/Logo-RedHat-D-Color-RGB.png000064400000000000000000000365721046102023000243140ustar 00000000000000PNG  IHDRrVsRGB pHYs%%IR$YiTXtXML:com.adobe.xmp 1 L'Y;IDATxm%ykkze[2NߵH@ZX鶂s8sW M^Fp!ؿi;Zsqۙ+?(" PDh+Ź O C=$G'h4^ڰ6-mdE>괁wT*4l#ptuw7S y¯8^_P`X(} ۨ! pf<"/N,4nSzȴ"QIG'= /DeM;XarVJj~aۤ5 8`L 0u0mp1Ωہz oD@ TZE%l_S@cW.:ڸGADJPe9,GV2S'[\ : T'a zvyVզ'xXYUVT%C'L8t6˯! 4+OX*Ka3! =ʚSadE_j9LKiԋ-0)dN\urՙH`vI U=5F"E :s TgfB/#acD+YD@jЇja;M$P])CIqD&2'T V7` 4XEА=XD*[?Q) ^@ r5LrX2<@=ria$EBC@ES"@Tj5[-r9p^z}<[7҉#y7TD'PXoPw &B O~ O||˾Zi#y0ﺮ]& pw h"p9 q}3Aւx,"`n}=(`bgTw׮Ni(O ,U1SZ㾂@{B* w8ץڊC@/U㬏U{/ xoVeIh> W&Kʉĵ qWooѱ4B {T!@&SJTK uV69>OpiX#![}N]AD5F [SErͳWٸ3g|wg `_ ]+Kopo(KD.V<@\7=2Bh؜JҨpp a d8ϼwFY%IDJ6UG@'/ ްokԫT'*~\'~M" vH(o{V<留>ϻC8t?=&v x ph9&ɾ>>ӿ/}ه^@cہyvl4!XM?ٗf pB#іk?@?Fmgq-i=_'v9"`G 1?:uɓ >)u'A ؈Uv]&$P$>'wA𝦬h^X cX` P]O5b I:ޱjʌ" 4_'L pnL$mkƟdw{h?@v6{]šwc4/ݚ(Y=|!]\chc>ЯS "OB&-BAt8V,N4/"ϝ*zMt OK` Sppω#$İ+3Ǽ59<PMZ] =HXOpR99 U0%3DYhBgȰJ ma^Gv퓎7Ts82@&y˕S?.KcNul_zSCAA4E9I(}!GR%R(A`\f'09> = LB;T٨wa^r{tFsd+IM ޔłpEM8,CpW1'=qn-a7:i7ƃMfṜe݊~jbH5o %s6o9:0(_?5+Og_c/:V=e9:Cx7Нs=otѧuwqXI"?NF>d{;0.P::tA_ӾNK /`w 8ԗUAy(8Q̗bH Be|0kamפ[*A9ATs/ A݌s$)t:O ιD;ϰg#i`F rwKC}k*mXlc?:LwMx߅'A wTkAu.|c$|gR0nPՌw6܅:~~/ؠ*Umv?fyv x #fS%PhɆ5}m`qзۤw?ŧZxea}8pPAbs `X%uѹWE" oyۇb\f|< IN x9$]-$i.eDd8,8i{1/׍}\ TTJeG+GwRbEL*}cXByOb윉bg,! tKIHCD -&Ul:xz'|tOҩzϭN=mUqE0D$W-'mAmK>J3v%KGâȮ]g3Slٸu~)8c_o/lBuPߟqHfZ+kAn y*-ŪW[T t zU }& L<=PѰNlFӺ~3HkY\pfYC$zVXZzݚ@]όD`(6 `we A- 5˺v3_w|j %>[VHR5dHhY5aS3 k*3GYMgJ[cXjYgigU3!_ *E^x+F{~kew)#ShIх"wfO]ٵo!YErM0a_lH/=}N@|b|/Rx^NhX!@yP8$t@k3]+GֺgJDGO݄7 (R; xݿGW@9$d^x ߵkZ !&6s1%k?s Z|?%v ؃)?;p}N`g[.91 t̤.~Gd.=A-a{Nd:?ox·2˿ ѐ:g1Ͻ]r49ྂ>G0?kC7V"úqڛ~l,JusW{1KahXjm!wm,0~%o :Z,Ud ~~7c+&K'G mYZ!Û27 BnTPAֵi\@F o@+b_%l_#)|V"Z?ck>B*Q$; oX  ͧPaVzmx?'^_ U"x@WbTAiUPC9-i+Po%˭W'P3ҕzD^@ 1,5!W IF Iu/z 3ǜ݌S*w8H4G0s)0Z{)0x){#F]dڮWk`O/FQ Go>A= R ?ҽ%-@飲3s( 2i >഑fU 2vUf5ַԟ W|TA!A| A Qҫ8 ʳPCCxMzL^q֨aCAqB+ L'SU߮vh%ZsN@®*wcvɜ~hZh ': '0EOz?o'q`5wK-ҳA2ƥF/`WXoAռuMR\£ZJRքVƒvi,En038_ٖ΋@ K:"ʂs8E]_rƗOiPnîE8q x_;_*"￉< F~\@ZW|/c@Π&Ğ'y1o늠F[gJEBV]+ ??gиYrp{9g5\-~ι]'XyG خR$w Sݼ^/~̭J`t._GX*R9miGCmh/- m@rU >*Gs#W3|SPonX7 8\/A W y-^5 48Nwh@=ూBs#z)hq| CC3+%CBxyNAD@DGsi{sڊ< [" &ԷD@DVa|c`èrRD` <% M'y틀ύ,}oXe`H_[(Hy -Zo@+R(ڊ@p@gpx?(-Lpz0-V BCg;eU,fJ: `58qC@)-c/%P/"=-rQ 9F7W (X#Tz) KdxOjTMۥ,rYT,d ^$<u, Lb["O mЊ6(+ 0n;?TE;DGpܤ]D@N?@ihpZD.|3 zYTZ8e~BBD`@ж̊8 .PZ "ci)RlG09$3D@<#);-PDZ5xe+.A/S 92:#G!|e-F}6IM",},vrd8FlOpA`|kqw`"ۅA4*"szU=F&Pi'5N %@"`J yU?h`K_*{ 2YD& >ǹ:C4h:y"`@q|il(8O۳8"0tрUVl$c80q=-%C"I ؤr&1Ud9,*:'Fn6Z_d@!]$G0…t^f$EYK Dr&;/ Q~C> U"R* 8_0ōa*iWNC>.˶8Ez UZ0<>`*ᓼ BQ*D%p=_KO7 dU+ l# " BXow7#r,pX S r QX@ґ{,UD3c-4W "$?lQ"@1+@ArKDr+)uS(8L@Zp8h^-b 'S p@KT" "9zuA*A7oprfAEAD@$`#YK'0\*!m9"ĉp_AJQFV88S5 jR283=&Cb00 ^-\!Q"w5ecK*FkU?e !#H$e"1n#!(ƺeVH2z"XHk;"c]L{1 S(8eA\,!&e;F)$ !vR%sk1}aLsy?>W;.s A9d\T Uu7*seOM1\@L>!sH14, ߄eXgsHWB  C@x?>w >!sB CuDANc5"M ͺ !̫?;+ dtK=%U.'12 Ύ +n\!=̏ X?¼ PC6t;M` r27EȴK#\ lؾUSCh<0CH P m`rwIEQ!^z:gAqil -!jm[a3ӆƆlp#NA6.ӆ†4PG90U!@IհacWʮ!;6b\ .4 Rڊ@Ba]tCiRk#?zl ӚZfZ%&<ճUmޘALW596D,-:8O_q"'K"}DV(¶aZ5mC@sn2?Rga#2 bŶ:/ W.B`bSv!6%G[W=} "r#acco|CrA%2NɸU.iOw VAU/d.a&Q qQ!2xZZd596& ?[D8?##`>Ա1.^͋P{O 1$lNLÇA{{JYABqr976杆v g{kuo 2Gw֥UIE0 1NuM$4-!j"A IFDCi]|Jr sD\/bFHh]wLW54~X3OE(A [cfInX aݼLcOStv, :s(coiXβ!o"$Bxl+PW /B:!Byu2C t-rlPΣ-: d45Лu ꭺlg㪶U"W;E|;jpWTTyʹɦ8:0'Q޵d痞|giBZ"7OԠ(_շu%z|G* N닂8.5u"ٶLu^ i4!* * YPO # zAm+ :Ϣs"9C*'jsҕ-]Od!fT%%$^x%$N)>(W%% a wxBdm% d 6vxuvamq쩲S<ar)#jac +rXp>7kZY.pdYCrizrQGM쭅7PE "+ hk8@@2#Rcx4G 9fv C~jlCZ}x) rbȊO.SCJnt&F5չ>8scKLKˋ6ϻkU So"$j=>:up˰g^AiBVF r;4H~`]kU_uvjm 6?-ٟJK-I|!NG LJ؝C͋v!]UˌseO5, %0Fq"'G] :oy;*A_+2o8mq:D|7.CV~k I&JS龶5 u8"MüqxC [HAA_E;DEʹJf@-pa =& {ytŤ\fF7Rg5,4Qt>' ]4 KXTO 1b#,ĤwCX2 ,W. ,6aT ' | D~ Ne6nqfHAv!X@l# JȐeCFa !{ /BVNp!8 m%b^"NQ6rlRؘN!1cHP"H7M]@}$, S8!srr(!bzOH:C6e?B3_3iM LlHNKY+s}9ߐ4^ޖF0G<|I;53}aLlYWg{r2, &`:L]OOQ" !y"`UCKvnV&XmďamX҆M&JMlf6ضsj3FZ/Mxؚؿ*Rhz?laCJ-0x%.>k7LjBiʹJ_E=yM7(ܧ w w2D]fDCr1ژ AϨ2pfA..ӁE)N: @۸gF} YWb ;k1 (LvQ~>MMk=G:fJ 2ͮeҢV*G P6n+3.SB\t4 L.˦.j7r[ gӇΌxhQ *+.;` ϻzyum]N-lCz(ۨ@O%7X>{Bmv y9UfK|> Ҏ 6VTV2LCR8ҕ'fHu[6 DP4{fytuXL{ Jw yBQ|2,m(VF] BN M Bn4YȦu iWTgys1m24Y[0)H! nX¼ ziOp4秐AhB^3i 3(6-M;70pc IlWгB #d>D:I1A⠎> M9ֵ阾0G&m~ Y&?"ڹضDd i4<Ԩ3lF }QҰ(B봏vLs qV%$Jְ,!d6C"B+Zѹ"l[6)uCP )u DXUa}qnpU['jD8bb] " " " " " "N̓IENDB`parsec-service-1.3.0/fuzz.sh000075500000000000000000000045311046102023000141000ustar 00000000000000#!/usr/bin/env bash # Copyright 2020 Contributors to the Parsec project. # SPDX-License-Identifier: Apache-2.0 FUZZ_CONTAINER_NAME=parsec_fuzzer CLEANUP_CONTAINER_NAME=parsec_fuzzer_cleanup set -e if [[ "$1" == "run" ]] then # Set up fuzz folder docker run --rm -v $(pwd):/parsec -w /parsec/fuzz --name $CLEANUP_CONTAINER_NAME ghcr.io/parallaxsecond/parsec-service-test-all ./cleanup.sh # A copy of the config file is used because the file is modified during the run cp fuzz/config.toml fuzz/run_config.toml # Stop previous container and run fuzzer docker kill $FUZZ_CONTAINER_NAME || true sleep 5s docker run -d --rm -v $(pwd):/parsec -w /parsec/fuzz --name $FUZZ_CONTAINER_NAME ghcr.io/parallaxsecond/parsec-service-test-all ./run_fuzz.sh elif [[ "$1" == "stop" ]] then docker kill $FUZZ_CONTAINER_NAME elif [[ "$1" == "follow" ]] then docker logs -f --tail 100 $FUZZ_CONTAINER_NAME elif [[ "$1" == "clean" ]] then # Cleanup is done via Docker because on some systems ACL settings prevent the user who # created a container from removing the files created by said container. Another one # is needed to do the cleanup. docker run -d --rm -v $(pwd):/parsec -w /parsec/fuzz --name $CLEANUP_CONTAINER_NAME ghcr.io/parallaxsecond/parsec-service-test-all ./cleanup.sh elif [[ "$1" == "erase" ]] then docker run -d --rm -v $(pwd):/parsec -w /parsec/fuzz -e "ERASE=true" --name $CLEANUP_CONTAINER_NAME ghcr.io/parallaxsecond/parsec-service-test-all ./cleanup.sh elif [[ "$1" == "test" ]] then if [[ -z "$CONTAINER_TAG" ]] then CONTAINER_TAG=ghcr.io/parallaxsecond/parsec-service-test-all fi # A copy of the config file is used because the file is modified during the run cp fuzz/config.toml fuzz/run_config.toml # Run the fuzzer in test mode docker run --rm -v $(pwd):/parsec -w /parsec/fuzz --name $FUZZ_CONTAINER_NAME $CONTAINER_TAG ./run_fuzz.sh test else echo "usage: ./fuzz.sh [COMMAND] Commands: 'run' - builds the fuzzing container and runs the fuzzer 'stop' - stops the fuzzing container 'follow' - prints and follows the log output of the fuzzing container 'clean' - clean up the fuzzing environment (does not remove artifacts or the fuzz corpus) 'erase' - fully clean the fuzzing environment - WARNING: this will remove all the results of previous runs" fi parsec-service-1.3.0/quickstart/config.toml000064400000000000000000000010021046102023000170650ustar 00000000000000# See https://parallaxsecond.github.io/parsec-book/parsec_service/configuration.html for a full # example. [core_settings] log_level = "info" allow_root = true [listener] listener_type = "DomainSocket" timeout = 200 # in milliseconds socket_path = "./parsec.sock" [authenticator] auth_type = "UnixPeerCredentials" [[key_manager]] name = "sqlite-manager" manager_type = "SQLite" sqlite_db_path = "./sqlite-key-info-manager.sqlite3" [[provider]] provider_type = "MbedCrypto" key_info_manager = "sqlite-manager" parsec-service-1.3.0/quickstart/construct-build-details.sh000064400000000000000000000015451046102023000220370ustar 00000000000000#!/bin/bash # Copyright 2023 Contributors to the Parsec project. # SPDX-License-Identifier: Apache-2.0 cat << EOF ---------------------------------------- -- Parsec Quickstart Build Details ---------------------------------------- OS: $(cat /build-env/os) Architecture: $(cat /build-env/arch) Rust: $(cat /build-env/rustc-version) Cargo: $(cat /build-env/cargo-version) ---------------------------------------- -- Parsec Service ---------------------------------------- Version: $(cat /build-env/parsec-version) Commit Hash: $(cat /build-env/parsec-commit) Dependencies: $(cat /build-env/parsec-dependencies) ---------------------------------------- -- Parsec Tool ---------------------------------------- Version: $(cat /build-env/parsec-tool-version) Commit Hash: $(cat /build-env/parsec-tool-commit) Dependencies: $(cat /build-env/parsec-tool-dependencies) EOF parsec-service-1.3.0/quickstart/docker_README.md000064400000000000000000000112021046102023000175340ustar 00000000000000# Parsec Quickstart - Docker This Docker container is constructed specifically as an introductory quickstart for the Parsec service and client tool. It is not intended for use in any production system. The container is started with the following command. This assumes that your Docker system is configured to pull images from ghcr.io. If that's not the case, or if you'd like to build a local image, see section [Building Quickstart Image](#building-quickstart-image). ```bash $> docker run --rm --name parsec -it parallaxsecond/parsec-quickstart bash qs@319b139eb85e:/parsec/quickstart$ ``` ## Directory Layout & Environment Settings ``` parsec ├── bin │ ├── parsec # The parsec binary │ └── parsec-tool # The parsec client tool └── quickstart ├── README.md # This README ├── build.txt # Information about the Parsec build environment ├── config.toml # The config file used by parsec └── parsec-cli-tests.sh # Standard parsec-tool tests ``` ``` PWD=/parsec/quickstart PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/parsec/bin PARSEC_SERVICE_ENDPOINT=unix:/parsec/quickstart/parsec.sock ``` ## Usage The following describe standard quickstart usage examples. ### Start the PARSEC service ```bash # This will execute the parsec binary found in /parsec/bin using the config file # found at /parsec/quickstart/config.toml. # The socket path will be placed at /parsec/quickstart/parsec.sock qs@319b139eb85e:/parsec/quickstart$ parsec & [INFO parsec] Parsec started. Configuring the service... [INFO parsec_service::key_info_managers::sqlite_manager] SQLiteKeyInfoManager - Found 0 key info mapping records [INFO parsec_service::utils::service_builder] Creating a Mbed Crypto Provider. [INFO parsec] Parsec is ready. qs@319b139eb85e:/parsec/quickstart$ ``` ### Ping Parsec ```bash # This will execute a ping command using the parsec-tool binary. # The container has already configured the environment variable # PARSEC_SERVICE_ENDPOINT=unix:/parsec/quickstart/parsec.sock # which will allow all parsec-tool commands to successfully find # the necessary socket. qs@319b139eb85e:/parsec/quickstart$ parsec-tool ping [INFO ] Service wire protocol version 1.0 ``` ### Parsec Tool Examples ```bash # List Providers qs@319b139eb85e:/parsec/quickstart$ parsec-tool list-providers [INFO ] Available providers: ID: 0x01 (Mbed Crypto provider) Description: User space software provider, based on Mbed Crypto - the reference implementation of the PSA crypto API Version: 0.1.0 Vendor: Arm UUID: 1c1139dc-ad7c-47dc-ad6b-db6fdb466552 ID: 0x00 (Core provider) Description: Software provider that implements only administrative (i.e. no cryptographic) operations Version: 1.1.0 Vendor: Unspecified UUID: 47049873-2a43-4845-9d72-831eab668784 # Create RSA Key qs@319b139eb85e:/parsec/quickstart$ parsec-tool create-rsa-key --key-name demo1 [INFO ] Creating RSA encryption key... [INFO ] Key "demo1" created. # Encrypt data using the RSA Key qs@319b139eb85e:/parsec/quickstart$ parsec-tool encrypt --key-name demo1 "Super secret data" [INFO ] Encrypting data with RsaPkcs1v15Crypt... RuPgZld6....brHqQd7xJg== # Decrypt ciphertext using the RSA Key qs@319b139eb85e:/parsec/quickstart$ parsec-tool decrypt --key-name demo1 RuPgZld6....brHqQd7xJg== [INFO ] Decrypting data with RsaPkcs1v15Crypt... Super secret data ``` ### Run the Test Script ```bash qs@319b139eb85e:/parsec/quickstart$ ./parsec-cli-tests.sh Checking Parsec service... [INFO ] Service wire protocol version 1.0 Testing Mbed Crypto provider - Test random number generation [INFO ] Generating 10 random bytes... [INFO ] Random bytes: 24 A1 19 DB 3F 3C A0 82 FE 63 .... ``` ## Building Quickstart Image Building the Quickstart image locally can be accomplished by executing the `package.sh` script located in the `quickstart` directory. Running `package.sh` will also generate the Quickstart tarball and place it in the current directory. ```bash $ quickstart > ./package.sh Packaging started... ... Finalizing packages ``` Alternatively, you can execute the Docker build command directly ```bash # We use .. at the end so the entire parsec directory is available in the docker build context $ quickstart > docker build --target runnable_image --tag parallaxsecond/parsec-quickstart -f quickstart.Dockerfile .. ``` Image construction requires cloning of https://github.com/parallaxsecond/parsec-tool in order to include the `parsec-tool` binary in the built image. This will be done automatically as part of the image construction process, but it does necessitate your system having access to Github. parsec-service-1.3.0/quickstart/package.sh000075500000000000000000000036221046102023000166670ustar 00000000000000#!/bin/bash # Copyright 2022 Contributors to the Parsec project. # SPDX-License-Identifier: Apache-2.0 # Create a quickstart package # Avoid silent failures set -euf -o pipefail PACKAGE_PATH=$(pwd) ASSETS_DIR=$(cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd) PARSEC_DIR=$(dirname $ASSETS_DIR) # Usage USAGE_STR=\ "Usage:\n"\ "package.sh [Options]\n"\ "Options:\n"\ " -o {path}: Output absolute path, the default path is the current directory i.e. $(pwd)\n"\ " -h : Display this help menu\n" # Flags while getopts v:o:j:h flag do case "${flag}" in o) PACKAGE_PATH=${OPTARG};; h) echo -e $USAGE_STR; exit 0;; esac done check_release_tag() { CURRENT_TAG=$(git name-rev --tags HEAD | cut -d "/" -f 2) LATTEST_TAG=$(git tag --sort=committerdate | tail -1) if [ -z "$LATTEST_TAG" ];then echo "Warning:No tags" fi if [ "$LATTEST_TAG" == "$CURRENT_TAG" ]; then echo "Packaging release tag: $LATTEST_TAG" else echo "Warning: The current HEAD doesn't match the latest tagged" echo "Warning: Please checkout the latest tag : $LATTEST_TAG" read -n 1 -p "Do you want to continue anyway [y/n]?" choice if [ "$choice" != "y" ]; then exit 1 fi fi } build_runnable_image() { docker build --target runnable_image --tag parallaxsecond/parsec-quickstart -f quickstart.Dockerfile ${PARSEC_DIR} } build_extract_tarball() { docker build --target tarball_builder --tag parallaxsecond/parsec-quickstart-tarball -f quickstart.Dockerfile ${PARSEC_DIR} # Extract the tarball out of the image used to construct it and place it in ${PACKAGE_PATH} docker run -v ${PACKAGE_PATH}:/opt/mount --rm parallaxsecond/parsec-quickstart-tarball bash -c 'cp /parsec-tar/*.tar.gz /opt/mount/' } echo "Packaging started..." trap EXIT check_release_tag build_runnable_image build_extract_tarball echo "Finalizing packages" parsec-service-1.3.0/quickstart/quickstart.Dockerfile000064400000000000000000000105231046102023000211160ustar 00000000000000# Copyright 2023 Contributors to the Parsec project. # SPDX-License-Identifier: Apache-2.0 # --------------------------------------------- # Docker Stage: Base builder used for both parsec service and tools FROM rust:latest AS base_builder RUN apt update -y && \ apt install -y llvm-dev libclang-dev clang cmake jq ## Track various build environment things we may want to use throughout WORKDIR /build-env RUN echo "$(uname | awk '{print tolower($0)}')" > /build-env/os RUN echo "$(arch)" > /build-env/arch RUN echo "$(rustc --version)" > /build-env/rustc-version RUN echo "$(cargo --version)" > /build-env/cargo-version # --------------------------------------------- # Docker Stage: Temporary stage to help dependency caching FROM base_builder AS parsec_service_scratch ## Copy everything in COPY . /parsec-service WORKDIR /parsec-service # This just adds cargo dependencies to the scratch stage so that we don't need to # download them each time the builder runs. RUN cargo fetch # --------------------------------------------- # Docker Stage: Executes the build of the Parsec Service FROM parsec_service_scratch AS parsec_service_builder ## Run the actual build RUN cargo build --release --features mbed-crypto-provider # Save the current parsec version and dependencies as defined by cargo and the current git commit hash RUN echo "$(cargo metadata --format-version=1 --no-deps --offline | jq -r '.packages[0].version')" > /build-env/parsec-version RUN echo "$(cargo tree)" > /build-env/parsec-dependencies RUN echo "$(git rev-parse HEAD)" > /build-env/parsec-commit # --------------------------------------------- # Docker Stage: Executes the build of the Parsec Tool FROM base_builder AS parsec_tool_builder RUN git clone https://github.com/parallaxsecond/parsec-tool /parsec-tool WORKDIR /parsec-tool RUN git checkout $(git tag --sort=committerdate | tail -1) RUN cargo build --release # Save the current parsec-tool version and dependencies as defined by cargo and the current git commit hash RUN echo "$(cargo metadata --format-version=1 --no-deps --offline | jq -r '.packages[0].version')" > /build-env/parsec-tool-version RUN echo "$(cargo tree)" > /build-env/parsec-tool-dependencies RUN echo "$(git rev-parse HEAD)" > /build-env/parsec-tool-commit # --------------------------------------------- # Docker Stage: Extracts build results from previous stages and adds in quickstart configs FROM base_builder AS layout ## Add the built binaries into the image COPY --from=parsec_service_builder /parsec-service/target/release/parsec /parsec/bin/parsec COPY --from=parsec_tool_builder /parsec-tool/target/release/parsec-tool /parsec/bin/parsec-tool ## Create and configure a starting directory for quickstart operations WORKDIR /parsec/quickstart COPY quickstart/config.toml /parsec/quickstart/config.toml COPY --from=parsec_tool_builder /parsec-tool/tests/parsec-cli-tests.sh /parsec/quickstart/parsec-cli-tests.sh ## Grab all the build-env values COPY --from=parsec_service_builder /build-env/* /build-env/ COPY --from=parsec_tool_builder /build-env/* /build-env/ ## Generate the build details file COPY quickstart/construct-build-details.sh /build-env/ RUN chmod +x /build-env/construct-build-details.sh && /build-env/construct-build-details.sh > /parsec/quickstart/build.txt # --------------------------------------------- # Docker Stage: Constructs an appropriate tarball containing all binaries and files FROM ubuntu:latest AS tarball_builder COPY --from=layout /parsec /parsec COPY quickstart/tarball_README.md /parsec/quickstart/README.md COPY --from=parsec_service_builder /build-env /build-env ## Generate a tarball containing all quickstart items and named using the version, os, and arch RUN NAME="quickstart-$(cat /build-env/parsec-version)-$(cat /build-env/os)-$(cat /build-env/arch)" \ && mv /parsec ${NAME} \ && mkdir /parsec-tar \ && tar -zcvf /parsec-tar/${NAME}.tar.gz /${NAME} # --------------------------------------------- # Docker Stage: Constructs a valid Docker image with Parsec Quickstart FROM ubuntu:latest AS runnable_image COPY --from=layout /parsec /parsec COPY quickstart/docker_README.md /parsec/quickstart/README.md ENV PATH=$PATH:/parsec/bin ENV PARSEC_SERVICE_ENDPOINT=unix:/parsec/quickstart/parsec.sock RUN apt update && apt install -y openssl RUN useradd -ms /bin/bash qs RUN chown -R qs:qs /parsec/quickstart USER qs WORKDIR /parsec/quickstart parsec-service-1.3.0/quickstart/tarball_README.md000064400000000000000000000116571046102023000177240ustar 00000000000000# Parsec Quickstart - Tarball This tarball and content is constructed specifically as an introductory quickstart for the Parsec service and client tool. It is not intended for use in any production system. See [the Getting Started documentation](https://parallaxsecond.github.io/parsec-book/getting_started/installation_options.html#option-2-download-a-quick-start-release) for more details. These tools were built for a Linux-based `x86_64` system. Please see section [Building Quickstart Tarball](#building-quickstart-tarball) if you'd like to generate the Quickstart tarball locally. ## Directory Layout ``` . ├── bin │ ├── parsec # The parsec binary │ └── parsec-tool # The parsec client tool └── quickstart ├── README.md # This README ├── build.txt # Information about the Parsec build environment ├── config.toml # The config file used by parsec └── parsec-cli-tests.sh # Standard parsec-tool tests ``` ## Usage The following describes standard quickstart usage examples. Calls to the `parsec-tool` assume that there exists an environment variable `PARSEC_SERVICE_ENDPOINT` set to the path for the socket created by the `parsec` process. By default, that socket is placed in the directory where you've executed the `parsec` command. It may also be helpful to add the `bin` directory to your path. The examples below assume that this has been done. ``` $ quickstart/ > export PARSEC_SERVICE_ENDPOINT=unix:$(pwd)/parsec.sock $ quickstart/ > export PATH=${PATH}:$(pwd)/../bin ``` ### Start the PARSEC service ```bash # This will execute the parsec binary using the config file found at quickstart/config.toml. # The socket path will be placed in the current directory quickstart/parsec.sock $ quickstart/ > parsec & [INFO parsec] Parsec started. Configuring the service... [INFO parsec_service::key_info_managers::sqlite_manager] SQLiteKeyInfoManager - Found 0 key info mapping records [INFO parsec_service::utils::service_builder] Creating a Mbed Crypto Provider. [INFO parsec] Parsec is ready. $ quickstart/ > ``` ### Ping Parsec ```bash # This will execute a ping command using the parsec-tool binary. $ quickstart/ > parsec-tool ping [INFO ] Service wire protocol version 1.0 ``` ### Parsec Tool Examples ```bash # List Providers $ quickstart/ > parsec-tool list-providers [INFO ] Available providers: ID: 0x01 (Mbed Crypto provider) Description: User space software provider, based on Mbed Crypto - the reference implementation of the PSA crypto API Version: 0.1.0 Vendor: Arm UUID: 1c1139dc-ad7c-47dc-ad6b-db6fdb466552 ID: 0x00 (Core provider) Description: Software provider that implements only administrative (i.e. no cryptographic) operations Version: 1.1.0 Vendor: Unspecified UUID: 47049873-2a43-4845-9d72-831eab668784 # Create RSA Key $ quickstart/ > parsec-tool create-rsa-key --key-name demo1 [INFO ] Creating RSA encryption key... [INFO ] Key "demo1" created. # Encrypt data using the RSA Key $ quickstart/ > parsec-tool encrypt --key-name demo1 "Super secret data" [INFO ] Encrypting data with RsaPkcs1v15Crypt... RuPgZld6....brHqQd7xJg== # Decrypt ciphertext using the RSA Key $ quickstart/ > parsec-tool decrypt --key-name demo1 RuPgZld6....brHqQd7xJg== [INFO ] Decrypting data with RsaPkcs1v15Crypt... Super secret data ``` ### Run the Test Script ```bash $ quickstart/ > ./parsec-cli-tests.sh Checking Parsec service... [INFO ] Service wire protocol version 1.0 Testing Mbed Crypto provider - Test random number generation [INFO ] Generating 10 random bytes... [INFO ] Random bytes: 24 A1 19 DB 3F 3C A0 82 FE 63 .... ``` ## Building Quickstart Tarball Building the Quickstart tarball locally can be accomplished by executing the `package.sh` script located in the `quickstart` directory. Default execution will place the generated tarball in the current directory. If you'd like it placed in a specific directory then use the `-o ` option. Running `package.sh` will also generate the Quickstart Docker image. ```bash $ quickstart > ./package.sh Packaging started... ... Finalizing packages ``` Alternatively, you can execute the commands directly ```bash # Use Docker to generate the tarball # We use .. at the end so the entire parsec directory is available in the docker build context $ quickstart > docker build --target tarball_builder --tag parallaxsecond/parsec-quickstart-tarball -f quickstart.Dockerfile .. # Extract the tarball from the Docker image and place it in the currently directory $ quickstart > docker run -v `pwd`:/opt/mount --rm parallaxsecond/parsec-quickstart-tarball bash -c 'cp /parsec-tar/*.tar.gz /opt/mount/' ``` Tarball generation requires cloning of https://github.com/parallaxsecond/parsec-tool in order to include the `parsec-tool` binary in the built image. This will be done automatically as part of the tarball construction process, but it does necessitate your system having access to Github. parsec-service-1.3.0/src/authenticators/direct_authenticator/mod.rs000064400000000000000000000127731046102023000237270ustar 00000000000000// Copyright 2019 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Direct authenticator //! //! The `DirectAuthenticator` implements the [direct authentication](https://parallaxsecond.github.io/parsec-book/parsec_service/system_architecture.html#authentication-tokens) //! functionality set out in the system architecture. As such, it attempts to parse the request //! authentication field into an UTF-8 string and returns the result as an application name. //! This authenticator does not offer any security value and should only be used in environments //! where all the clients and the service are mutually trustworthy. use super::{AdminList, Application, ApplicationIdentity, Authenticate}; use crate::front::listener::ConnectionMetadata; use crate::utils::config::Admin; use log::error; use parsec_interface::operations::list_authenticators; use parsec_interface::requests::request::RequestAuth; use parsec_interface::requests::AuthType; use parsec_interface::requests::{ResponseStatus, Result}; use parsec_interface::secrecy::ExposeSecret; use std::str; /// Direct authentication authenticator implementation #[derive(Clone, Debug)] pub struct DirectAuthenticator { admins: AdminList, } impl DirectAuthenticator { /// Create new direct authenticator pub fn new(admins: Vec) -> Self { DirectAuthenticator { admins: admins.into(), } } } impl Authenticate for DirectAuthenticator { fn describe(&self) -> Result { Ok(list_authenticators::AuthenticatorInfo { description: String::from( "Directly parses the authentication field as a UTF-8 string and uses that as the \ application identity. Should be used for testing only.", ), version_maj: 0, version_min: 1, version_rev: 0, id: AuthType::Direct, }) } fn authenticate( &self, auth: &RequestAuth, _: Option, ) -> Result { if auth.buffer.expose_secret().is_empty() { error!("The direct authenticator does not expect empty authentication values."); Err(ResponseStatus::AuthenticationError) } else { match str::from_utf8(auth.buffer.expose_secret()) { Ok(str) => { let app_name = String::from(str); let is_admin = self.admins.is_admin(&app_name); Ok(Application { identity: ApplicationIdentity { name: app_name, authenticator_id: AuthType::Direct, }, is_admin, }) } Err(_) => { error!("Error parsing the authentication value as a UTF-8 string."); Err(ResponseStatus::AuthenticationError) } } } } } #[cfg(test)] mod test { use super::super::Authenticate; use super::DirectAuthenticator; use parsec_interface::requests::request::RequestAuth; use parsec_interface::requests::ResponseStatus; #[test] fn successful_authentication() { let authenticator = DirectAuthenticator { admins: Default::default(), }; let app_name = "app_name".to_string(); let req_auth = RequestAuth::new(app_name.clone().into_bytes()); let conn_metadata = None; let application = authenticator .authenticate(&req_auth, conn_metadata) .expect("Failed to authenticate"); assert_eq!(application.identity.name, app_name); assert!(!application.is_admin); } #[test] fn failed_authentication() { let authenticator = DirectAuthenticator { admins: Default::default(), }; let conn_metadata = None; let status = authenticator .authenticate(&RequestAuth::new(vec![0xff; 5]), conn_metadata) .expect_err("Authentication should have failed"); assert_eq!(status, ResponseStatus::AuthenticationError); } #[test] fn empty_auth() { let authenticator = DirectAuthenticator { admins: Default::default(), }; let conn_metadata = None; let status = authenticator .authenticate(&RequestAuth::new(Vec::new()), conn_metadata) .expect_err("Empty auth should have failed"); assert_eq!(status, ResponseStatus::AuthenticationError); } #[test] fn admin_check() { let admin_name = String::from("admin_name"); let admin = toml::from_str(&format!("name = '{}'", admin_name)).unwrap(); let authenticator = DirectAuthenticator { admins: vec![admin].into(), }; let app_name = "app_name".to_string(); let req_auth = RequestAuth::new(app_name.clone().into_bytes()); let conn_metadata = None; let application = authenticator .authenticate(&req_auth, conn_metadata) .expect("Failed to authenticate"); assert_eq!(application.identity.name, app_name); assert!(!application.is_admin); let req_auth = RequestAuth::new(admin_name.clone().into_bytes()); let application = authenticator .authenticate(&req_auth, conn_metadata) .expect("Failed to authenticate"); assert_eq!(application.identity.name, admin_name); assert!(application.is_admin); } } parsec-service-1.3.0/src/authenticators/jwt_svid_authenticator/mod.rs000064400000000000000000000053221046102023000242760ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! JWT SVID authenticator use super::{Admin, AdminList, Application, ApplicationIdentity, Authenticate}; use crate::front::listener::ConnectionMetadata; use log::error; use parsec_interface::operations::list_authenticators; use parsec_interface::requests::request::RequestAuth; use parsec_interface::requests::Result; use parsec_interface::requests::{AuthType, ResponseStatus}; use parsec_interface::secrecy::ExposeSecret; use spiffe::workload_api::client::WorkloadApiClient; use std::str; /// JWT SVID authenticator #[allow(missing_debug_implementations)] pub struct JwtSvidAuthenticator { client: WorkloadApiClient, admins: AdminList, } impl JwtSvidAuthenticator { /// Create a new JWT-SVID authenticator with a specific path to the Workload API socket. pub fn new(workload_endpoint: String, admins: Vec) -> Option { let client = match WorkloadApiClient::new(&workload_endpoint) { Ok(client) => client, Err(e) => { error!("Can't start the SPIFFE Workload API client ({}).", e); return None; } }; Some(JwtSvidAuthenticator { client, admins: admins.into(), }) } } impl Authenticate for JwtSvidAuthenticator { fn describe(&self) -> Result { Ok(list_authenticators::AuthenticatorInfo { description: String::from( "Authenticator validating a JWT SPIFFE Verifiable Identity Document", ), version_maj: 0, version_min: 1, version_rev: 0, id: AuthType::JwtSvid, }) } fn authenticate( &self, auth: &RequestAuth, _: Option, ) -> Result { let svid = str::from_utf8(auth.buffer.expose_secret()).map_err(|e| { error!( "The authentication buffer can not be parsed into a UTF-8 string ({}).", e ); ResponseStatus::InvalidEncoding })?; let jwt_token = self .client .validate_jwt_token("parsec", svid) .map_err(|e| { error!("The validation of the JWT-SVID failed ({}).", e); ResponseStatus::AuthenticationError })?; let app_name = jwt_token.spiffe_id().to_string(); let is_admin = self.admins.is_admin(&app_name); Ok(Application { identity: ApplicationIdentity { name: app_name, authenticator_id: AuthType::JwtSvid, }, is_admin, }) } } parsec-service-1.3.0/src/authenticators/mod.rs000064400000000000000000000114641046102023000175170ustar 00000000000000// Copyright 2019 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Request authentication //! //! [Authenticators](https://parallaxsecond.github.io/parsec-book/parsec_service/authenticators.html) //! provide functionality to the service for verifying the authenticity of requests. //! The result of an authentication is an `Application` which is parsed by the authenticator and //! used throughout the service for identifying the request initiator. The input to an authentication //! is the `RequestAuth` field of a request, which is parsed by the authenticator specified in the header. //! The authentication functionality is abstracted through an `Authenticate` trait. #[cfg(not(any( feature = "direct-authenticator", feature = "unix-peer-credentials-authenticator", feature = "jwt-svid-authenticator", )))] compile_error!("Please provide in at least one authenticator"); #[cfg(feature = "direct-authenticator")] pub mod direct_authenticator; #[cfg(feature = "unix-peer-credentials-authenticator")] pub mod unix_peer_credentials_authenticator; #[cfg(feature = "jwt-svid-authenticator")] pub mod jwt_svid_authenticator; use crate::front::listener::ConnectionMetadata; use crate::utils::config::Admin; use parsec_interface::operations::list_authenticators; use parsec_interface::requests::request::RequestAuth; use parsec_interface::requests::{AuthType, Result}; use std::fmt; use std::ops::Deref; /// A unique identifier for an application. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ApplicationIdentity { /// The name of the application. name: String, /// The id of the authenticator used to authenticate the application name. authenticator_id: AuthType, } impl fmt::Display for ApplicationIdentity { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "ApplicationIdentity: [name=\"{}\", authenticator_id=\"{}\"]", self.name, self.authenticator_id ) } } impl ApplicationIdentity { /// Creates a new instance of ProviderIdentity. pub fn new(name: String, authenticator_id: AuthType) -> ApplicationIdentity { ApplicationIdentity { name, authenticator_id, } } /// Get the identity of the application pub fn name(&self) -> &String { &self.name } /// Get whether the application has administrator rights pub fn authenticator_id(&self) -> &AuthType { &self.authenticator_id } } /// Wrapper for a Parsec application #[derive(Debug, Clone)] pub struct Application { /// The identity of the Application identity: ApplicationIdentity, /// Whether the application has administrator rights is_admin: bool, } impl fmt::Display for Application { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "Application {{identity: {}, is_admin: {}}}", self.identity, self.is_admin ) } } impl Application { /// Creates a new instance of ProviderIdentity. pub fn new(identity: ApplicationIdentity, is_admin: bool) -> Application { Application { identity, is_admin } } /// Get the identity of the application pub fn identity(&self) -> &ApplicationIdentity { &self.identity } /// Get whether the application has administrator rights pub fn is_admin(&self) -> &bool { &self.is_admin } } /// Authentication interface /// /// Interface that must be implemented for each authentication type available for the service. pub trait Authenticate { /// Return a description of the authenticator. /// /// The descriptions are gathered in the Core Provider and returned for a ListAuthenticators /// operation. fn describe(&self) -> Result; /// Authenticates a `RequestAuth` payload and returns the `Application` if successful. A /// optional `ConnectionMetadata` object is passed in too, since it is sometimes possible to /// perform authentication based on the connection's metadata (i.e. as is the case for UNIX /// domain sockets with Unix peer credentials). /// /// # Errors /// /// If the authentification fails, returns a `ResponseStatus::AuthenticationError`. fn authenticate( &self, auth: &RequestAuth, meta: Option, ) -> Result; } #[derive(Debug, Clone, Default)] struct AdminList(Vec); impl AdminList { fn is_admin(&self, app_name: &str) -> bool { self.iter().any(|admin| admin.name() == app_name) } } impl Deref for AdminList { type Target = Vec; fn deref(&self) -> &Self::Target { &self.0 } } impl From> for AdminList { fn from(admin_list: Vec) -> Self { AdminList(admin_list) } } parsec-service-1.3.0/src/authenticators/unix_peer_credentials_authenticator/mod.rs000064400000000000000000000230301046102023000270140ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Unix peer credentials authenticator //! //! The `UnixPeerCredentialsAuthenticator` uses Unix peer credentials to perform authentication. As //! such, it uses the effective Unix user ID (UID) to authenticate the connecting process. Unix //! peer credentials also allow us to access the effective Unix group ID (GID) of the connecting //! process, although this information is currently unused. //! //! Currently, the stringified UID is used as the application name. use super::{AdminList, Application, ApplicationIdentity, Authenticate}; use crate::front::listener::ConnectionMetadata; use crate::utils::config::Admin; use log::error; use parsec_interface::operations::list_authenticators; use parsec_interface::requests::request::RequestAuth; use parsec_interface::requests::AuthType; use parsec_interface::requests::{ResponseStatus, Result}; use parsec_interface::secrecy::ExposeSecret; use std::convert::TryInto; /// Unix peer credentials authenticator. #[derive(Clone, Debug)] pub struct UnixPeerCredentialsAuthenticator { admins: AdminList, } impl UnixPeerCredentialsAuthenticator { /// Create new Unix peer credentials authenticator pub fn new(admins: Vec) -> Self { UnixPeerCredentialsAuthenticator { admins: admins.into(), } } } impl Authenticate for UnixPeerCredentialsAuthenticator { fn describe(&self) -> Result { Ok(list_authenticators::AuthenticatorInfo { description: String::from( "Uses Unix peer credentials to authenticate the client. Verifies that the self-declared \ Unix user identifier (UID) in the request's authentication header matches that which is \ found from the peer credentials." ), version_maj: 0, version_min: 1, version_rev: 0, id: AuthType::UnixPeerCredentials, }) } fn authenticate( &self, auth: &RequestAuth, meta: Option, ) -> Result { // Parse authentication request. let expected_uid_bytes = auth.buffer.expose_secret(); const EXPECTED_UID_SIZE_BYTES: usize = 4; let expected_uid: [u8; EXPECTED_UID_SIZE_BYTES] = expected_uid_bytes.as_slice().try_into().map_err(|_| { error!( "UID in authentication request is not the right size (expected: {}, got: {}).", EXPECTED_UID_SIZE_BYTES, expected_uid_bytes.len() ); ResponseStatus::AuthenticationError })?; let expected_uid = u32::from_le_bytes(expected_uid); let meta = meta.ok_or_else(|| { error!("Authenticator did not receive any metadata; cannot perform authentication."); ResponseStatus::AuthenticationError })?; #[allow(unreachable_patterns)] let (uid, _gid, _pid) = match meta { ConnectionMetadata::UnixPeerCredentials { uid, gid, pid } => (uid, gid, pid), _ => { error!("Wrong metadata type given to Unix peer credentials authenticator."); return Err(ResponseStatus::AuthenticationError); } }; // Authentication is successful if the _actual_ UID from the Unix peer credentials equals // the self-declared UID in the authentication request. if uid == expected_uid { let app_name = uid.to_string(); let is_admin = self.admins.is_admin(&app_name); Ok(Application { identity: ApplicationIdentity { name: app_name, authenticator_id: AuthType::UnixPeerCredentials, }, is_admin, }) } else { error!("Declared UID in authentication request does not match the process's UID."); Err(ResponseStatus::AuthenticationError) } } } #[cfg(test)] mod test { use super::super::Authenticate; use super::UnixPeerCredentialsAuthenticator; use crate::front::domain_socket::peer_credentials; use crate::front::listener::ConnectionMetadata; use libc::{getuid, uid_t}; use parsec_interface::requests::request::RequestAuth; use parsec_interface::requests::ResponseStatus; use rand::Rng; use std::os::unix::net::UnixStream; #[test] fn successful_authentication() { // This test should PASS; we are verifying that our username gets set as the application // secret when using Unix peer credentials authentication with Unix domain sockets. // Create two connected sockets. let (sock_a, _sock_b) = UnixStream::pair().unwrap(); let (cred_a, _cred_b) = ( peer_credentials::peer_cred(&sock_a).unwrap(), peer_credentials::peer_cred(&_sock_b).unwrap(), ); let authenticator = UnixPeerCredentialsAuthenticator { admins: Default::default(), }; let req_auth_data = cred_a.uid.to_le_bytes().to_vec(); let req_auth = RequestAuth::new(req_auth_data); let conn_metadata = Some(ConnectionMetadata::UnixPeerCredentials { uid: cred_a.uid, gid: cred_a.gid, pid: None, }); let application = authenticator .authenticate(&req_auth, conn_metadata) .expect("Failed to authenticate"); let current_uid: uid_t = unsafe { getuid() }; assert_eq!(application.identity.name, current_uid.to_string()); assert!(!application.is_admin); } #[test] fn unsuccessful_authentication_wrong_declared_uid() { // This test should FAIL; we are trying to authenticate, but we are declaring the wrong // UID. // Create two connected sockets. let (sock_a, _sock_b) = UnixStream::pair().unwrap(); let (cred_a, _cred_b) = ( peer_credentials::peer_cred(&sock_a).unwrap(), peer_credentials::peer_cred(&_sock_b).unwrap(), ); let authenticator = UnixPeerCredentialsAuthenticator { admins: Default::default(), }; let wrong_uid = cred_a.uid + 1; let wrong_req_auth_data = wrong_uid.to_le_bytes().to_vec(); let req_auth = RequestAuth::new(wrong_req_auth_data); let conn_metadata = Some(ConnectionMetadata::UnixPeerCredentials { uid: cred_a.uid, gid: cred_a.gid, pid: cred_a.pid, }); let auth_result = authenticator .authenticate(&req_auth, conn_metadata) .unwrap_err(); assert_eq!(auth_result, ResponseStatus::AuthenticationError); } #[test] fn unsuccessful_authentication_garbage_data() { // This test should FAIL; we are sending garbage (random) data in the request. // Create two connected sockets. let (sock_a, _sock_b) = UnixStream::pair().unwrap(); let (cred_a, _cred_b) = ( peer_credentials::peer_cred(&sock_a).unwrap(), peer_credentials::peer_cred(&_sock_b).unwrap(), ); let authenticator = UnixPeerCredentialsAuthenticator { admins: Default::default(), }; let garbage_data = rand::thread_rng().gen::<[u8; 32]>().to_vec(); let req_auth = RequestAuth::new(garbage_data); let conn_metadata = Some(ConnectionMetadata::UnixPeerCredentials { uid: cred_a.uid, gid: cred_a.gid, pid: cred_a.pid, }); let auth_result = authenticator .authenticate(&req_auth, conn_metadata) .unwrap_err(); assert_eq!(auth_result, ResponseStatus::AuthenticationError); } #[test] fn unsuccessful_authentication_no_metadata() { let authenticator = UnixPeerCredentialsAuthenticator { admins: Default::default(), }; let req_auth = RequestAuth::new("secret".into()); let conn_metadata = None; let auth_result = authenticator .authenticate(&req_auth, conn_metadata) .unwrap_err(); assert_eq!(auth_result, ResponseStatus::AuthenticationError); } #[test] fn admin_check() { // Create two connected sockets. let (sock_a, _sock_b) = UnixStream::pair().unwrap(); let (cred_a, _cred_b) = ( peer_credentials::peer_cred(&sock_a).unwrap(), peer_credentials::peer_cred(&_sock_b).unwrap(), ); let current_uid: uid_t = unsafe { getuid() }; let admin = toml::from_str(&format!("name = '{}'", current_uid)).unwrap(); let authenticator = UnixPeerCredentialsAuthenticator { admins: vec![admin].into(), }; let req_auth_data = cred_a.uid.to_le_bytes().to_vec(); let req_auth = RequestAuth::new(req_auth_data); let conn_metadata = Some(ConnectionMetadata::UnixPeerCredentials { uid: cred_a.uid, gid: cred_a.gid, pid: None, }); let application = authenticator .authenticate(&req_auth, conn_metadata) .expect("Failed to authenticate"); assert_eq!(application.identity.name, current_uid.to_string()); assert!(application.is_admin); } #[test] fn unsuccessful_authentication_wrong_metadata() { // TODO(new_metadata_variant): this test needs implementing when we have more than one // metadata type. At the moment, the compiler just complains with an 'unreachable branch' // message. } } parsec-service-1.3.0/src/back/backend_handler.rs000064400000000000000000000464231046102023000176720ustar 00000000000000// Copyright 2019 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Convert requests to calls to the underlying provider //! //! The backend handler embodies the last processing step from external request //! to internal function call - parsing of the request body and conversion to a //! native operation which is then passed to the provider. use crate::authenticators::Application; use crate::providers::Provide; use derivative::Derivative; use log::{error, trace, warn}; use parsec_interface::operations::Convert; use parsec_interface::operations::{NativeOperation, NativeResult}; use parsec_interface::requests::{ request::RequestHeader, Request, Response, ResponseStatus, Result, }; use parsec_interface::requests::{BodyType, ProviderId}; use std::io::{Error, ErrorKind}; use std::sync::Arc; /// Back end handler component /// /// Component responsible for unmarshalling requests, passing the operation /// to the provider and marshalling the result. /// /// It also provides assessment capabilities, letting the dispatcher know if /// it can process a request. #[derive(Derivative)] #[derivative(Debug)] pub struct BackEndHandler { // Send and Sync are required for Arc to be Send. #[derivative(Debug = "ignore")] provider: Arc, #[derivative(Debug = "ignore")] converter: Box, provider_id: ProviderId, content_type: BodyType, accept_type: BodyType, } impl BackEndHandler { /// Convert a request into a response, given the result of the operation. fn result_to_response(&self, result: NativeResult, request_hdr: RequestHeader) -> Response { let mut response = Response::from_request_header(request_hdr, ResponseStatus::Success); match self.converter.result_to_body(result) { Ok(body) => response.body = body, Err(status) => response.header.status = status, }; response } /// Assess whether the backend handler-provider pair is capable of handling /// the request. /// /// # Errors /// - if the provider ID can not perform the type of operation, returns /// `ResponseStatus::PsaErrorNotSupported` /// - if the provider ID does not match, returns `ResponseStatus::WrongProviderId` /// - if the content type does not match, returns `ResponseStatus::ContentTypeNotSupported` /// - if the accept type does not match, returns `ResponseStatus::AcceptTypeNotSupported` pub fn is_capable(&self, request: &Request) -> Result<()> { let header = &request.header; if (self.provider_id == ProviderId::Core) != header.opcode.is_core() { error!("The request's operation is not compatible with the provider targeted."); return Err(ResponseStatus::PsaErrorNotSupported); } if header.provider != self.provider_id { Err(ResponseStatus::WrongProviderId) } else if header.content_type != self.content_type { Err(ResponseStatus::ContentTypeNotSupported) } else if header.accept_type != self.accept_type { Err(ResponseStatus::AcceptTypeNotSupported) } else { Ok(()) } } /// Unmarshall the request body, pass the operation to the provider and marshall /// the result back. /// /// If any of the steps fails, a response containing an appropriate status code is /// returned. pub fn execute_request(&self, request: Request, app: Option) -> Response { trace!("execute_request ingress"); let opcode = request.header.opcode; let header = request.header; macro_rules! unwrap_or_else_return { ($result:expr) => { match $result { Ok(value) => value, Err(status) => return Response::from_request_header(header, status), } }; } if opcode.is_admin() { let app = unwrap_or_else_return!(app.as_ref().ok_or(ResponseStatus::NotAuthenticated)); if !app.is_admin() { warn!( "Application name \"{}\" tried to perform an admin operation ({:?}).", app.identity().name(), opcode ); return Response::from_request_header(header, ResponseStatus::AdminOperation); } } match unwrap_or_else_return!(self.converter.body_to_operation(request.body, opcode)) { NativeOperation::ListProviders(op_list_providers) => { let result = unwrap_or_else_return!(self.provider.list_providers(op_list_providers)); trace!("list_providers egress"); self.result_to_response(NativeResult::ListProviders(result), header) } NativeOperation::ListOpcodes(op_list_opcodes) => { let result = unwrap_or_else_return!(self.provider.list_opcodes(op_list_opcodes)); trace!("list_opcodes egress"); self.result_to_response(NativeResult::ListOpcodes(result), header) } NativeOperation::Ping(op_ping) => { let result = unwrap_or_else_return!(self.provider.ping(op_ping)); trace!("ping egress"); self.result_to_response(NativeResult::Ping(result), header) } NativeOperation::PsaGenerateKey(op_generate_key) => { let app = unwrap_or_else_return!(app.ok_or(ResponseStatus::NotAuthenticated)); let result = unwrap_or_else_return!(self .provider .psa_generate_key(app.identity(), op_generate_key)); trace!("psa_generate_key egress"); self.result_to_response(NativeResult::PsaGenerateKey(result), header) } NativeOperation::PsaImportKey(op_import_key) => { let app = unwrap_or_else_return!(app.ok_or(ResponseStatus::NotAuthenticated)); let result = unwrap_or_else_return!(self .provider .psa_import_key(app.identity(), op_import_key)); trace!("psa_import_key egress"); self.result_to_response(NativeResult::PsaImportKey(result), header) } NativeOperation::PsaExportPublicKey(op_export_public_key) => { let app = unwrap_or_else_return!(app.ok_or(ResponseStatus::NotAuthenticated)); let result = unwrap_or_else_return!(self .provider .psa_export_public_key(app.identity(), op_export_public_key)); trace!("psa_export_public_key egress"); self.result_to_response(NativeResult::PsaExportPublicKey(result), header) } NativeOperation::PsaExportKey(op_export_key) => { let app = unwrap_or_else_return!(app.ok_or(ResponseStatus::NotAuthenticated)); let result = unwrap_or_else_return!(self .provider .psa_export_key(app.identity(), op_export_key)); trace!("psa_export_key egress"); self.result_to_response(NativeResult::PsaExportKey(result), header) } NativeOperation::PsaDestroyKey(op_destroy_key) => { let app = unwrap_or_else_return!(app.ok_or(ResponseStatus::NotAuthenticated)); let result = unwrap_or_else_return!(self .provider .psa_destroy_key(app.identity(), op_destroy_key)); trace!("psa_destroy_key egress"); self.result_to_response(NativeResult::PsaDestroyKey(result), header) } NativeOperation::PsaSignHash(op_sign_hash) => { let app = unwrap_or_else_return!(app.ok_or(ResponseStatus::NotAuthenticated)); let result = unwrap_or_else_return!(self .provider .psa_sign_hash(app.identity(), op_sign_hash)); trace!("psa_sign_hash egress"); self.result_to_response(NativeResult::PsaSignHash(result), header) } NativeOperation::PsaVerifyHash(op_verify_hash) => { let app = unwrap_or_else_return!(app.ok_or(ResponseStatus::NotAuthenticated)); let result = unwrap_or_else_return!(self .provider .psa_verify_hash(app.identity(), op_verify_hash)); trace!("psa_verify_hash egress"); self.result_to_response(NativeResult::PsaVerifyHash(result), header) } NativeOperation::PsaAsymmetricEncrypt(op_asymmetric_encrypt) => { let app = unwrap_or_else_return!(app.ok_or(ResponseStatus::NotAuthenticated)); let result = unwrap_or_else_return!(self .provider .psa_asymmetric_encrypt(app.identity(), op_asymmetric_encrypt)); trace!("psa_asymmetric_encrypt egress"); self.result_to_response(NativeResult::PsaAsymmetricEncrypt(result), header) } NativeOperation::PsaAsymmetricDecrypt(op_asymmetric_decrypt) => { let app = unwrap_or_else_return!(app.ok_or(ResponseStatus::NotAuthenticated)); let result = unwrap_or_else_return!(self .provider .psa_asymmetric_decrypt(app.identity(), op_asymmetric_decrypt)); trace!("psa_asymmetric_decrypt egress"); self.result_to_response(NativeResult::PsaAsymmetricDecrypt(result), header) } NativeOperation::PsaAeadEncrypt(op_aead_encrypt) => { let app = unwrap_or_else_return!(app.ok_or(ResponseStatus::NotAuthenticated)); let result = unwrap_or_else_return!(self .provider .psa_aead_encrypt(app.identity(), op_aead_encrypt)); trace!("psa_aead_encrypt egress"); self.result_to_response(NativeResult::PsaAeadEncrypt(result), header) } NativeOperation::PsaAeadDecrypt(op_aead_decrypt) => { let app = unwrap_or_else_return!(app.ok_or(ResponseStatus::NotAuthenticated)); let result = unwrap_or_else_return!(self .provider .psa_aead_decrypt(app.identity(), op_aead_decrypt)); trace!("psa_aead_decrypt egress"); self.result_to_response(NativeResult::PsaAeadDecrypt(result), header) } NativeOperation::ListAuthenticators(op_list_authenticators) => { let result = unwrap_or_else_return!(self .provider .list_authenticators(op_list_authenticators)); trace!("list_authenticators egress"); self.result_to_response(NativeResult::ListAuthenticators(result), header) } NativeOperation::ListKeys(op_list_keys) => { let app = unwrap_or_else_return!(app.ok_or(ResponseStatus::NotAuthenticated)); let result = unwrap_or_else_return!(self.provider.list_keys(app.identity(), op_list_keys)); trace!("list_keys egress"); self.result_to_response(NativeResult::ListKeys(result), header) } NativeOperation::ListClients(op_list_clients) => { let result = unwrap_or_else_return!(self.provider.list_clients(op_list_clients)); trace!("list_clients egress"); self.result_to_response(NativeResult::ListClients(result), header) } NativeOperation::DeleteClient(op_delete_client) => { let app = unwrap_or_else_return!(app.ok_or(ResponseStatus::NotAuthenticated)); let result = unwrap_or_else_return!(self .provider .delete_client(app.identity(), op_delete_client)); trace!("delete_client egress"); self.result_to_response(NativeResult::DeleteClient(result), header) } NativeOperation::PsaHashCompute(op_hash_compute) => { let result = unwrap_or_else_return!(self.provider.psa_hash_compute(op_hash_compute)); trace!("psa_hash_compute egress"); self.result_to_response(NativeResult::PsaHashCompute(result), header) } NativeOperation::PsaHashCompare(op_hash_compare) => { let result = unwrap_or_else_return!(self.provider.psa_hash_compare(op_hash_compare)); trace!("psa_hash_compare egress"); self.result_to_response(NativeResult::PsaHashCompare(result), header) } NativeOperation::PsaRawKeyAgreement(op_raw_key_agreement) => { let app = unwrap_or_else_return!(app.ok_or(ResponseStatus::NotAuthenticated)); let result = unwrap_or_else_return!(self .provider .psa_raw_key_agreement(app.identity(), op_raw_key_agreement)); trace!("psa_raw_key_agreement egress"); self.result_to_response(NativeResult::PsaRawKeyAgreement(result), header) } NativeOperation::PsaGenerateRandom(op_generate_random) => { let result = unwrap_or_else_return!(self.provider.psa_generate_random(op_generate_random)); trace!("psa_generate_random egress"); self.result_to_response(NativeResult::PsaGenerateRandom(result), header) } NativeOperation::PsaSignMessage(op_sign_message) => { let app = unwrap_or_else_return!(app.ok_or(ResponseStatus::NotAuthenticated)); let result = unwrap_or_else_return!(self .provider .psa_sign_message(app.identity(), op_sign_message)); trace!("psa_sign_message egress"); self.result_to_response(NativeResult::PsaSignMessage(result), header) } NativeOperation::PsaVerifyMessage(op_verify_message) => { let app = unwrap_or_else_return!(app.ok_or(ResponseStatus::NotAuthenticated)); let result = unwrap_or_else_return!(self .provider .psa_verify_message(app.identity(), op_verify_message)); trace!("psa_verify_message egress"); self.result_to_response(NativeResult::PsaVerifyMessage(result), header) } NativeOperation::PsaCipherEncrypt(op_cipher_encrypt) => { let app = unwrap_or_else_return!(app.ok_or(ResponseStatus::NotAuthenticated)); let result = unwrap_or_else_return!(self .provider .psa_cipher_encrypt(app.identity(), op_cipher_encrypt)); trace!("op_cipher_encrypt egress"); self.result_to_response(NativeResult::PsaCipherEncrypt(result), header) } NativeOperation::PsaCipherDecrypt(op_cipher_decrypt) => { let app = unwrap_or_else_return!(app.ok_or(ResponseStatus::NotAuthenticated)); let result = unwrap_or_else_return!(self .provider .psa_cipher_decrypt(app.identity(), op_cipher_decrypt)); trace!("psa_cipher_decrypt egress"); self.result_to_response(NativeResult::PsaCipherDecrypt(result), header) } NativeOperation::CanDoCrypto(op_can_do_crypto) => { let app = unwrap_or_else_return!(app.ok_or(ResponseStatus::NotAuthenticated)); let result = unwrap_or_else_return!(self .provider .can_do_crypto(app.identity(), op_can_do_crypto)); trace!("can_do_crypto egress"); self.result_to_response(NativeResult::CanDoCrypto(result), header) } NativeOperation::PrepareKeyAttestation(op_prepare_key_attestation) => { let app = unwrap_or_else_return!(app.ok_or(ResponseStatus::NotAuthenticated)); let result = unwrap_or_else_return!(self .provider .prepare_key_attestation(app.identity(), op_prepare_key_attestation)); trace!("prepare_key_attestation egress"); self.result_to_response(NativeResult::PrepareKeyAttestation(result), header) } NativeOperation::AttestKey(op_attest_key) => { let app = unwrap_or_else_return!(app.ok_or(ResponseStatus::NotAuthenticated)); let result = unwrap_or_else_return!(self.provider.attest_key(app.identity(), op_attest_key)); trace!("attest_key egress"); self.result_to_response(NativeResult::AttestKey(result), header) } } } } /// Builder for `BackEndHandler` #[derive(Default, Derivative)] #[derivative(Debug)] pub struct BackEndHandlerBuilder { #[derivative(Debug = "ignore")] provider: Option>, #[derivative(Debug = "ignore")] converter: Option>, provider_id: Option, content_type: Option, accept_type: Option, } impl BackEndHandlerBuilder { /// Create a new BackEndHandler builder pub fn new() -> BackEndHandlerBuilder { BackEndHandlerBuilder { provider: None, converter: None, provider_id: None, content_type: None, accept_type: None, } } /// Add a provider to the builder pub fn with_provider(mut self, provider: Arc) -> Self { self.provider = Some(provider); self } /// Add a converter to the builder pub fn with_converter(mut self, converter: Box) -> Self { self.converter = Some(converter); self } /// Set the ID of the BackEndHandler pub fn with_provider_id(mut self, provider_id: ProviderId) -> Self { self.provider_id = Some(provider_id); self } /// Set the content type that the BackEndHandler supports pub fn with_content_type(mut self, content_type: BodyType) -> Self { self.content_type = Some(content_type); self } /// Set the accept type that the BackEndHandler supports pub fn with_accept_type(mut self, accept_type: BodyType) -> Self { self.accept_type = Some(accept_type); self } /// Build into a BackEndHandler pub fn build(self) -> std::io::Result { Ok(BackEndHandler { provider: self .provider .ok_or_else(|| Error::new(ErrorKind::InvalidData, "provider is missing"))?, converter: self .converter .ok_or_else(|| Error::new(ErrorKind::InvalidData, "converter is missing"))?, provider_id: self .provider_id .ok_or_else(|| Error::new(ErrorKind::InvalidData, "provider_id is missing"))?, content_type: self .content_type .ok_or_else(|| Error::new(ErrorKind::InvalidData, "content_type is missing"))?, accept_type: self .accept_type .ok_or_else(|| Error::new(ErrorKind::InvalidData, "accept_type is missing"))?, }) } } parsec-service-1.3.0/src/back/dispatcher.rs000064400000000000000000000064061046102023000167310ustar 00000000000000// Copyright 2019 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Dispatch requests to the correct backend //! //! The dispatcher's role is to direct requests to the provider they specify, if //! said provider is available on the system, thus acting as a multiplexer. use super::backend_handler::BackEndHandler; use crate::authenticators::Application; use log::trace; use parsec_interface::requests::request::Request; use parsec_interface::requests::ProviderId; use parsec_interface::requests::{Response, ResponseStatus}; use std::collections::HashMap; use std::io::{Error, ErrorKind, Result}; /// Dispatcher to backend /// /// Component tasked with identifying the backend handler that can /// service a request. /// /// As such, it owns all the backend handlers and attempts to match /// the fields in the request header to the properties of the handlers. #[derive(Debug)] pub struct Dispatcher { backends: HashMap, } impl Dispatcher { /// Parses the `provider` field of the request header and attempts to find /// the backend handler to which the request must be dispatched. /// /// Returns either the response coming from the backend handler, or a response /// containing a status code consistent with the error encountered during /// processing. pub fn dispatch_request(&self, request: Request, app: Option) -> Response { trace!("dispatch_request ingress"); if let Some(backend) = self.backends.get(&request.header.provider) { if let Err(status) = backend.is_capable(&request) { Response::from_request_header(request.header, status) } else { { let response = backend.execute_request(request, app); trace!("execute_request egress"); response } } } else { Response::from_request_header(request.header, ResponseStatus::ProviderNotRegistered) } } } /// `Dispatcher` builder #[derive(Debug, Default)] pub struct DispatcherBuilder { backends: Option>, } impl DispatcherBuilder { /// Create a new Dispatcher builder pub fn new() -> Self { DispatcherBuilder { backends: None } } /// Add a BackEndHandler with a specific Provider ID to the dispatcher pub fn with_backend( mut self, provider_id: ProviderId, backend_handler: BackEndHandler, ) -> Self { let mut backends = self.backends.unwrap_or_default(); let _ = backends.insert(provider_id, backend_handler); self.backends = Some(backends); self } /// Add multiple BackEndHandler to the dispatcher in one call pub fn with_backends(mut self, new_backends: HashMap) -> Self { let mut backends = self.backends.unwrap_or_default(); backends.extend(new_backends); self.backends = Some(backends); self } /// Build the builder into a dispatcher pub fn build(self) -> Result { Ok(Dispatcher { backends: self .backends .ok_or_else(|| Error::new(ErrorKind::InvalidData, "backends is missing"))?, }) } } parsec-service-1.3.0/src/back/mod.rs000064400000000000000000000003071046102023000153540ustar 00000000000000// Copyright 2019 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Routing and parsing requests for processing by providers pub mod backend_handler; pub mod dispatcher; parsec-service-1.3.0/src/bin/main.rs000064400000000000000000000153161046102023000153770ustar 00000000000000// Copyright 2019 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Parsec is the Platform AbstRaction for SECurity, a new open-source initiative to provide a //! common API to secure services in a platform-agnostic way. //! //! Parsec documentation is available //! [here](https://parallaxsecond.github.io/parsec-book/index.html) //! //! Most of Parsec configuration comes from its configuration file. //! Please check the documentation to find more about configuration //! [here](https://parallaxsecond.github.io/parsec-book/user_guides/configuration.html) #![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)] use anyhow::Result; use libc::{getuid, uid_t}; use log::{info, trace}; use parsec_service::utils::cli::Opts; use parsec_service::utils::{config::ServiceConfig, ServiceBuilder}; use signal_hook::{consts::SIGHUP, consts::SIGINT, consts::SIGTERM, flag}; use std::io::{Error, ErrorKind}; use std::sync::{ atomic::{AtomicBool, Ordering}, Arc, }; use std::time::Duration; use structopt::StructOpt; const MAIN_LOOP_DEFAULT_SLEEP: u64 = 10; fn main() -> Result<()> { // Parsing the command line arguments. let opts: Opts = Opts::from_args(); // Register a boolean set to true when the SIGTERM signal is received. let kill_signal = Arc::new(AtomicBool::new(false)); // Register a boolean set to true when the SIGHUP signal is received. let reload_signal = Arc::new(AtomicBool::new(false)); let _ = flag::register(SIGTERM, kill_signal.clone())?; let _ = flag::register(SIGINT, kill_signal.clone())?; let _ = flag::register(SIGHUP, reload_signal.clone())?; let mut config_file = ::std::fs::read_to_string(opts.config.clone()).map_err(|e| { Error::new( e.kind(), format!("Failed to read config file from path: {}", opts.config), ) })?; let mut config: ServiceConfig = toml::from_str(&config_file).map_err(|e| { Error::new( ErrorKind::InvalidInput, format!("Failed to parse service configuration ({})", e), ) })?; // Guard against running as root. This check can be overridden by changing `allow_root` inside // the config file. let allow_root = config.core_settings.allow_root.unwrap_or(false); let current_id: uid_t = unsafe { getuid() }; if !allow_root && current_id == 0 { return Err(Error::new( ErrorKind::Other, "Insecure configuration; the Parsec service should not be running as root! You can \ modify `allow_root` in the config file to bypass this check (not recommended).", ) .into()); } log_setup(&config); info!("Parsec started. Configuring the service..."); let front_end_handler = ServiceBuilder::build_service(&config)?; // Multiple threads can not just have a reference of the front end handler because they could // outlive the run function. It is needed to give them all ownership of the front end handler // through an Arc. let mut front_end_handler = Arc::from(front_end_handler); let mut listener = ServiceBuilder::start_listener(config.listener)?; let mut threadpool = ServiceBuilder::build_threadpool(config.core_settings.thread_pool_size); // Notify systemd that the daemon is ready, the start command will block until this point. let _ = sd_notify::notify(false, &[sd_notify::NotifyState::Ready]); info!("Parsec is ready."); while !kill_signal.load(Ordering::Relaxed) { if reload_signal.swap(false, Ordering::Relaxed) { let _ = sd_notify::notify(false, &[sd_notify::NotifyState::Reloading]); info!("SIGHUP signal received. Reloading the configuration..."); threadpool.join(); // Explicitely call drop now because otherwise Rust will drop these variables only // after they have been overwritten, in which case some values/libraries might be // initialized twice. drop(front_end_handler); drop(listener); drop(threadpool); config_file = ::std::fs::read_to_string(opts.config.clone()).map_err(|e| { Error::new( e.kind(), format!("Failed to read config file from path: {}", opts.config), ) })?; config = toml::from_str(&config_file).map_err(|e| { Error::new( ErrorKind::InvalidInput, format!("Failed to parse service configuration ({})", e), ) })?; front_end_handler = Arc::from(ServiceBuilder::build_service(&config)?); listener = ServiceBuilder::start_listener(config.listener)?; threadpool = ServiceBuilder::build_threadpool(config.core_settings.thread_pool_size); let _ = sd_notify::notify(false, &[sd_notify::NotifyState::Ready]); info!("Parsec configuration reloaded."); } if let Some(connection) = listener.accept() { let front_end_handler = front_end_handler.clone(); threadpool.execute(move || { front_end_handler.handle_request(connection); trace!("handle_request egress"); }); } else { ::std::thread::sleep(Duration::from_millis( config .core_settings .idle_listener_sleep_duration .unwrap_or(MAIN_LOOP_DEFAULT_SLEEP), )); } } let _ = sd_notify::notify(true, &[sd_notify::NotifyState::Stopping]); info!("SIGTERM or SIGINT signal received. Shutting down Parsec, waiting for all threads to finish..."); threadpool.join(); info!("Parsec is now terminated."); Ok(()) } fn log_setup(config: &ServiceConfig) { let mut env_log_builder = env_logger::builder(); if let Some(level) = config.core_settings.log_level { let _ = env_log_builder.filter_level(level); } if let Some(true) = config.core_settings.log_timestamp { let _ = env_log_builder.format_timestamp_millis(); } else { let _ = env_log_builder.format_timestamp(None); } env_log_builder.init(); } parsec-service-1.3.0/src/front/domain_socket.rs000064400000000000000000000260741046102023000176550ustar 00000000000000// Copyright 2019 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Service front using Unix domain sockets //! //! Expose Parsec functionality using Unix domain sockets as an IPC layer. //! The local socket is created at a predefined location. use super::listener; use anyhow::{Context, Result}; use listener::Listen; use listener::{Connection, ConnectionMetadata}; use log::{error, warn}; use std::fs; use std::fs::Permissions; use std::io::{Error, ErrorKind}; use std::os::unix::fs::FileTypeExt; use std::os::unix::fs::PermissionsExt; use std::os::unix::io::FromRawFd; use std::os::unix::io::RawFd; use std::os::unix::net::UnixListener; use std::path::PathBuf; use std::time::Duration; static DEFAULT_SOCKET_PATH: &str = "/run/parsec/parsec.sock"; /// Unix Domain Socket IPC manager /// /// Listener implementation for Unix sockets as the underlying IPC mechanism. /// /// Holds references to a `UnixListener`. #[derive(Debug)] pub struct DomainSocketListener { listener: UnixListener, timeout: Duration, } impl DomainSocketListener { /// Initialise the connection to the Unix socket. pub fn new(timeout: Duration, socket_path: PathBuf) -> Result { // If Parsec was service activated or not started under systemd, this // will return `0`. `1` will be returned in case Parsec is socket activated. let listeners: Vec = sd_notify::listen_fds()?.collect(); let listener = match listeners.len() { 0 => { if socket_path.exists() { let meta = fs::metadata(&socket_path)?; if meta.file_type().is_socket() { warn!( "Removing the existing socket file at {}.", socket_path.display() ); fs::remove_file(&socket_path)?; } else { error!( "A file exists at {} but is not a Unix Domain Socket.", socket_path.display() ); } } // Will fail if a file already exists at the path. let listener = UnixListener::bind(&socket_path).with_context(|| { format!("Failed to bind to Unix socket at {:?}", socket_path) })?; listener.set_nonblocking(true)?; // Set the socket's permission to 666 to allow clients of different user to // connect. let permissions = Permissions::from_mode(0o666); fs::set_permissions(socket_path, permissions)?; listener } 1 => { // No need to set the socket as non-blocking, parsec.service // already requests that. let nfd = listeners[0]; // Safe as listen_fds gives us the information that one file descriptor was // received and its value starts from SD_LISTEN_FDS_START. unsafe { UnixListener::from_raw_fd(nfd) } // Expect the socket created by systemd to be 666 on permissions. } n => { error!( "Received too many file descriptors ({} received, 0 or 1 expected).", n ); return Err(Error::new( ErrorKind::InvalidData, "too many file descriptors received", ) .into()); } }; Ok(Self { listener, timeout }) } } impl Listen for DomainSocketListener { fn set_timeout(&mut self, duration: Duration) { self.timeout = duration; } fn accept(&self) -> Option { let stream_result = self.listener.accept(); match stream_result { Ok((stream, _)) => { if let Err(err) = stream.set_read_timeout(Some(self.timeout)) { format_error!("Failed to set read timeout", err); None } else if let Err(err) = stream.set_write_timeout(Some(self.timeout)) { format_error!("Failed to set write timeout", err); None } else if let Err(err) = stream.set_nonblocking(false) { format_error!("Failed to set stream as blocking", err); None } else { let ucred = peer_credentials::peer_cred(&stream) .map_err(|err| { format_error!( "Failed to grab peer credentials metadata from UnixStream", err ); err }) .ok()?; Some(Connection { stream: Box::new(stream), metadata: Some(ConnectionMetadata::UnixPeerCredentials { uid: ucred.uid, gid: ucred.gid, pid: ucred.pid, }), }) } } Err(err) => { // Check if the error is because no connections are currently present. if err.kind() != ErrorKind::WouldBlock { // Only log the real errors. format_error!("Failed to connect with a UnixStream", err); } None } } } } /// Builder for `DomainSocketListener` #[derive(Clone, Debug, Default)] pub struct DomainSocketListenerBuilder { timeout: Option, socket_path: Option, } impl DomainSocketListenerBuilder { /// Create a new DomainSocketListener builder pub fn new() -> Self { DomainSocketListenerBuilder { timeout: None, socket_path: None, } } /// Add a timeout on the Unix Domain Socket used pub fn with_timeout(mut self, timeout: Duration) -> Self { self.timeout = Some(timeout); self } /// Specify the Unix Domain Socket path pub fn with_socket_path(mut self, socket_path: Option) -> Self { self.socket_path = socket_path; self } /// Build the builder into the listener pub fn build(self) -> Result { DomainSocketListener::new( self.timeout.ok_or_else(|| { error!("The listener timeout was not set."); Error::new(ErrorKind::InvalidInput, "listener timeout missing") })?, self.socket_path .unwrap_or_else(|| DEFAULT_SOCKET_PATH.into()), ) } } // == IMPORTANT NOTE == // // The code below has been cherry-picked from the following PR: // // https://github.com/rust-lang/rust/pull/75148 // // At the time of writing (16/09/20), this patch is in the nightly Rust channel. To avoid needing // to use the nightly compiler to build Parsec, we have instead opted to cherry-pick the change // from the patch to allow us to use this feature 'early'. // // Once the feature hits stable, it should be safe to revert the commit that introduced the changes // below with `git revert`. You can find the stabilizing Rust issue here: // // https://github.com/rust-lang/rust/issues/42839 /// Implementation of peer credentials fetching for Unix domain socket. pub mod peer_credentials { use libc::{gid_t, pid_t, uid_t}; /// Credentials for a UNIX process for credentials passing. #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct UCred { /// The UID part of the peer credential. This is the effective UID of the process at the domain /// socket's endpoint. pub uid: uid_t, /// The GID part of the peer credential. This is the effective GID of the process at the domain /// socket's endpoint. pub gid: gid_t, /// The PID part of the peer credential. This field is optional because the PID part of the /// peer credentials is not supported on every platform. On platforms where the mechanism to /// discover the PID exists, this field will be populated to the PID of the process at the /// domain socket's endpoint. Otherwise, it will be set to None. pub pid: Option, } #[cfg(any(target_os = "android", target_os = "linux"))] pub use self::impl_linux::peer_cred; #[cfg(any( target_os = "dragonfly", target_os = "freebsd", target_os = "ios", target_os = "macos", target_os = "openbsd" ))] pub use self::impl_bsd::peer_cred; #[cfg(any(target_os = "linux", target_os = "android"))] #[allow(missing_docs, trivial_casts)] // docs not required; only used for selective compilation. pub mod impl_linux { use super::UCred; use libc::{c_void, getsockopt, socklen_t, ucred, SOL_SOCKET, SO_PEERCRED}; use std::os::unix::io::AsRawFd; use std::os::unix::net::UnixStream; use std::{io, mem}; pub fn peer_cred(socket: &UnixStream) -> io::Result { let ucred_size = mem::size_of::(); // Trivial sanity checks. assert!(mem::size_of::() <= mem::size_of::()); assert!(ucred_size <= u32::MAX as usize); let mut ucred_size = ucred_size as socklen_t; let mut ucred: ucred = ucred { pid: 1, uid: 1, gid: 1, }; unsafe { let ret = getsockopt( socket.as_raw_fd(), SOL_SOCKET, SO_PEERCRED, &mut ucred as *mut ucred as *mut c_void, &mut ucred_size, ); if ret == 0 && ucred_size as usize == mem::size_of::() { Ok(UCred { uid: ucred.uid, gid: ucred.gid, pid: Some(ucred.pid), }) } else { Err(io::Error::last_os_error()) } } } } #[cfg(any( target_os = "dragonfly", target_os = "macos", target_os = "ios", target_os = "freebsd", target_os = "openbsd" ))] #[allow(missing_docs)] // docs not required; only used for selective compilation. pub mod impl_bsd { use super::UCred; use std::io; use std::os::unix::io::AsRawFd; use std::os::unix::net::UnixStream; pub fn peer_cred(socket: &UnixStream) -> io::Result { let mut cred = UCred { uid: 1, gid: 1, pid: None, }; unsafe { let ret = libc::getpeereid(socket.as_raw_fd(), &mut cred.uid, &mut cred.gid); if ret == 0 { Ok(cred) } else { Err(io::Error::last_os_error()) } } } } } parsec-service-1.3.0/src/front/front_end.rs000064400000000000000000000160301046102023000170030ustar 00000000000000// Copyright 2019 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Entry point for IPC data into the service //! //! The front end handler accepts streams of data that it can use to read requests, //! pass them to the rest of the service and write the responses back. use crate::authenticators::Authenticate; use crate::back::dispatcher::Dispatcher; use crate::front::listener::Connection; use derivative::Derivative; use log::{info, trace}; use parsec_interface::requests::AuthType; use parsec_interface::requests::ResponseStatus; use parsec_interface::requests::{Request, Response}; use std::collections::HashMap; use std::io::{Error, ErrorKind, Result}; /// Read and verify request from IPC stream /// /// Service component that serializes requests and deserializes responses /// from/to the stream provided by the listener. /// /// Requests are passed forward to the `Dispatcher`. #[derive(Derivative)] #[derivative(Debug)] pub struct FrontEndHandler { dispatcher: Dispatcher, // Send and Sync are required for Arc to be Send. #[derivative(Debug = "ignore")] authenticators: HashMap>, /// Value used to limit the size of the request body to be that can be accepted by the service. body_len_limit: usize, } impl FrontEndHandler { /// Handle new connections on the underlying IPC mechanism. /// /// Unmarshalls a request from the stream, passes it to the dispatcher and marshalls /// the response back onto the stream. /// /// If an error occurs during (un)marshalling; no operation will be performed, an error will be logged /// and the method will return. pub fn handle_request(&self, mut connection: Connection) { trace!("handle_request ingress"); // Read bytes from stream // De-Serialise bytes into a request let request = match Request::read_from_stream(&mut connection.stream, self.body_len_limit) { Ok(request) => request, Err(status) => { format_error!("Failed to read request", status); let response = Response::from_status(status); if response.header.status != ResponseStatus::Success { format_error!("Sending back an error", response.header.status); } if let Err(status) = response.write_to_stream(&mut connection.stream) { format_error!("Failed to write response", status); } return; } }; // Check if the request was sent without authentication let (app, err_response) = if AuthType::NoAuth == request.header.auth_type { (None, None) // Otherwise find an authenticator that is capable to authenticate the request } else if let Some(authenticator) = self.authenticators.get(&request.header.auth_type) { // Authenticate the request match authenticator.authenticate(&request.auth, connection.metadata) { // Send the request to the dispatcher // Get a response back Ok(app) => (Some(app), None), Err(status) => ( None, Some(Response::from_request_header(request.header, status)), ), } } else { ( None, Some(Response::from_request_header( request.header, ResponseStatus::AuthenticatorNotRegistered, )), ) }; let response = if let Some(err_response) = err_response { err_response } else { if crate::utils::GlobalConfig::log_error_details() { if let Some(app) = &app.as_ref() { info!( "New request received from application name \"{}\"", app.identity().name() ) } else { info!("New request received without authentication") } }; let response = self.dispatcher.dispatch_request(request, app.clone()); trace!("dispatch_request egress"); response }; // Serialise the response into bytes // Write bytes to stream match response.write_to_stream(&mut connection.stream) { Ok(_) => { if crate::utils::GlobalConfig::log_error_details() { if let Some(app) = app { info!( "Response for application name \"{}\" sent back", app.identity().name() ); } else { info!("Response sent back from request without authentication"); } } } Err(err) => format_error!("Failed to send response", err), } } } /// Builder for `FrontEndHandler` #[derive(Default, Derivative)] #[derivative(Debug)] pub struct FrontEndHandlerBuilder { dispatcher: Option, #[derivative(Debug = "ignore")] authenticators: Option>>, body_len_limit: Option, } impl FrontEndHandlerBuilder { /// Create a new FrontEndHandler builder pub fn new() -> Self { FrontEndHandlerBuilder { dispatcher: None, authenticators: None, body_len_limit: None, } } /// Add a dispatcher to the builder pub fn with_dispatcher(mut self, dispatcher: Dispatcher) -> Self { self.dispatcher = Some(dispatcher); self } /// Add an authenticator to the builder pub fn with_authenticator( mut self, auth_type: AuthType, authenticator: Box, ) -> Self { match &mut self.authenticators { Some(authenticators) => { let _ = authenticators.insert(auth_type, authenticator); } None => { let mut map = HashMap::new(); let _ = map.insert(auth_type, authenticator); self.authenticators = Some(map); } }; self } /// Set a limit on the maximal body length received pub fn with_body_len_limit(mut self, body_len_limit: usize) -> Self { self.body_len_limit = Some(body_len_limit); self } /// Build into a FrontEndHandler pub fn build(self) -> Result { Ok(FrontEndHandler { dispatcher: self .dispatcher .ok_or_else(|| Error::new(ErrorKind::InvalidData, "dispatcher is missing"))?, authenticators: self .authenticators .ok_or_else(|| Error::new(ErrorKind::InvalidData, "authenticators is missing"))?, body_len_limit: self .body_len_limit .ok_or_else(|| Error::new(ErrorKind::InvalidData, "body_len_limit is missing"))?, }) } } parsec-service-1.3.0/src/front/listener.rs000064400000000000000000000057551046102023000166660ustar 00000000000000// Copyright 2019 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Interface for service IPC front //! //! The [`Listen`](https://parallaxsecond.github.io/parsec-book/parsec_service/listeners.html) //! trait acts as an interface for the operations that must be supported by any implementation //! of the IPC mechanism used as a Parsec front. use derivative::Derivative; use std::time::Duration; /// 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: std::io::Read + std::io::Write {} // Automatically implements ReadWrite for all types that implement Read and Write. impl ReadWrite for T {} /// Specifies metadata associated with a connection, if any. #[derive(Copy, Clone, Debug)] pub enum ConnectionMetadata { /// Unix peer credentials metadata for Unix domain sockets. UnixPeerCredentials { /// The effective UID of the connecting process. uid: u32, /// The effective GID of the connecting process. gid: u32, /// The optional PID of the connecting process. This is an Option because not all /// platforms support retrieving PID via a domain socket. pid: Option, }, // NOTE: there is currently only _one_ variant of the ConnectionMetadata enum. When a second // variant is added, you will need to update some tests! // You should grep the tests for `TODO(new_metadata_variant)` and update them accordingly. } /// Represents a connection to a single client #[derive(Derivative)] #[derivative(Debug)] pub struct Connection { /// Stream used for communication with the client #[derivative(Debug = "ignore")] pub stream: Box, /// Metadata associated with the connection that might be useful elsewhere (i.e. authentication, etc) pub metadata: Option, } /// IPC front manager interface /// /// Interface defining the functionality that any IPC front manager has to expose to Parsec for normal /// operation. pub trait Listen { /// Set the timeout on read and write calls on any stream returned by this listener. fn set_timeout(&mut self, duration: Duration); /// Non-blocking call that gets the next client connection and returns a stream /// (a Read and Write trait object). Requests are read from the stream and responses are written /// to it. Streams returned by this method should have a timeout period as set by the /// `set_timeout` method. /// If no connections are present, return `None`. /// If there are any errors in establishing the connection other than the missing /// initialization, the implementation should log them and return `None`. /// `Send` is needed because the stream is moved to a thread. /// /// # Panics /// /// If the listener has not been initialised before, with the `init` method. fn accept(&self) -> Option; } parsec-service-1.3.0/src/front/mod.rs000064400000000000000000000002601046102023000156020ustar 00000000000000// Copyright 2019 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! IPC front handlers pub mod domain_socket; pub mod front_end; pub mod listener; parsec-service-1.3.0/src/key_info_managers/mod.rs000064400000000000000000000427571046102023000201530ustar 00000000000000// Copyright 2019 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Persistent mapping between key identities and key information //! //! This module declares a [`ManageKeyInfo`](https://parallaxsecond.github.io/parsec-book/parsec_service/key_info_managers.html) //! trait to help providers to store in a persistent manner the mapping between the name and the //! information of the keys they manage. Different implementors might store this mapping using different //! means but it has to be persistent. use crate::authenticators::ApplicationIdentity; #[allow(deprecated)] use crate::key_info_managers::on_disk_manager::KeyTriple; use crate::providers::ProviderIdentity; use crate::utils::config::{KeyInfoManagerConfig, KeyInfoManagerType}; use anyhow::Result; use derivative::Derivative; use parsec_interface::operations::psa_key_attributes::Attributes; use parsec_interface::requests::{AuthType, ResponseStatus}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use std::convert::TryFrom; use std::fmt; use std::hash::{Hash, Hasher}; use std::sync::{Arc, RwLock}; use zeroize::Zeroize; pub mod on_disk_manager; pub mod sqlite_manager; /// This structure corresponds to a unique identifier of the key. It is used internally by the Key /// ID manager to refer to a key. /// Note: for equality and hashing, key identity structs with matching ApplicationIdentity and key_name /// are considered equal; ProviderIdentity is not considered when evaluating equality or the hash. #[derive(Debug, Clone)] pub struct KeyIdentity { /// The identity of the application that created the key. application: ApplicationIdentity, /// The identity of the provider where the key is stored. provider: ProviderIdentity, /// The key name key_name: String, } impl Hash for KeyIdentity { fn hash(&self, state: &mut H) { self.application.hash(state); self.key_name.hash(state); } } impl PartialEq for KeyIdentity { fn eq(&self, other: &Self) -> bool { self.key_name() == other.key_name() && self.application() == other.application() } } impl Eq for KeyIdentity {} impl fmt::Display for KeyIdentity { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "KeyIdentity: {{\n{},\n{},\nkey_name: \"{}\",\n}}", self.application, self.provider, self.key_name ) } } /// Information stored about a key #[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Zeroize)] #[zeroize(drop)] struct KeyInfo { /// Reference to a key in the Provider id: Vec, /// Attributes of a key attributes: Attributes, } impl KeyIdentity { /// Creates a new instance of KeyIdentity. pub fn new( application: ApplicationIdentity, provider: ProviderIdentity, key_name: String, ) -> KeyIdentity { KeyIdentity { application, provider, key_name, } } /// Checks if this key belongs to a specific provider. pub fn belongs_to_provider(&self, provider_identity: &ProviderIdentity) -> bool { self.provider().name() == provider_identity.name() && self.provider().uuid() == provider_identity.uuid() } /// Get the key name pub fn key_name(&self) -> &String { &self.key_name } /// Get the application identity of the key pub fn application(&self) -> &ApplicationIdentity { &self.application } /// Get the provider identity of the key pub fn provider(&self) -> &ProviderIdentity { &self.provider } } /// Converts the error string returned by the ManageKeyInfo methods to /// ResponseStatus::KeyInfoManagerError. pub fn to_response_status(error_string: String) -> ResponseStatus { format_error!( "Converting error to ResponseStatus:KeyInfoManagerError", error_string ); ResponseStatus::KeyInfoManagerError } /// Management interface for key name to key info mapping /// /// Interface to be implemented for persistent storage of key name -> key info mappings. trait ManageKeyInfo { /// Returns the key info manager type. fn key_info_manager_type(&self) -> KeyInfoManagerType; /// Returns a reference to the key info corresponding to this KeyIdentity or `None` if it does not /// exist. /// /// # Errors /// /// Returns an error as a String if there was a problem accessing the Key Info Manager. fn get(&self, key_identity: &KeyIdentity) -> Result, String>; /// Returns a Vec of reference to the key identities corresponding to this provider. /// /// # Errors /// /// Returns an error as a String if there was a problem accessing the Key Info Manager. fn get_all(&self, provider_identity: ProviderIdentity) -> Result, String>; /// Inserts a new mapping between the KeyIdentity and the key info. If the KeyIdentity already exists, /// overwrite the existing mapping and returns the old `KeyInfo`. Otherwise returns `None`. /// /// # Errors /// /// Returns an error as a String if there was a problem accessing the Key Info Manager. fn insert( &mut self, key_identity: KeyIdentity, key_info: KeyInfo, ) -> Result, String>; /// Removes a KeyIdentity mapping and returns it. Does nothing and returns `None` if the mapping /// does not exist. /// /// # Errors /// /// Returns an error as a String if there was a problem accessing the Key Info Manager. fn remove(&mut self, key_identity: &KeyIdentity) -> Result, String>; /// Check if a KeyIdentity mapping exists. /// /// # Errors /// /// Returns an error as a String if there was a problem accessing the Key Info Manager. fn exists(&self, key_identity: &KeyIdentity) -> Result; } /// KeyInfoManager client structure that bridges between the KIM and the providers that need /// to use it. #[derive(Derivative)] #[derivative(Debug)] pub struct KeyInfoManagerClient { provider_identity: ProviderIdentity, #[derivative(Debug = "ignore")] key_info_manager_impl: Arc>, } impl KeyInfoManagerClient { /// Get the KeyIdentity representing a key. pub fn get_key_identity( &self, application: ApplicationIdentity, key_name: String, ) -> KeyIdentity { KeyIdentity::new(application, self.provider_identity.clone(), key_name) } /// Get the key ID for a given KeyIdentity /// /// The ID does not have to be a specific type. Rather, it must implement the `serde::Deserialize` /// trait. Before returning, an instance of that type is created from the bytes stored by the KIM. /// /// # Errors /// /// If the key does not exist, PsaErrorDoesNotExist is returned. If any error occurs while fetching /// the key info, KeyInfoManagerError is returned. If deserializing the stored key ID to the desired /// type fails, InvalidEncoding is returned. pub fn get_key_id( &self, key_identity: &KeyIdentity, ) -> parsec_interface::requests::Result { let key_info_manager_impl = self .key_info_manager_impl .read() .expect("Key Info Manager lock poisoned"); let key_info = match key_info_manager_impl.get(key_identity) { Ok(Some(key_info)) => key_info, Ok(None) => return Err(ResponseStatus::PsaErrorDoesNotExist), Err(string) => return Err(to_response_status(string)), }; // The `deserialize` call below creates a new instance of T decoupled from the // scope of the lock acquired above. Ok(bincode::deserialize(&key_info.id)?) } /// Get the `Attributes` for a given KeyIdentity /// /// # Errors /// /// If the key does not exist, PsaErrorDoesNotExist is returned. If any other error occurs, /// KeyInfoManagerError is returned. pub fn get_key_attributes( &self, key_identity: &KeyIdentity, ) -> parsec_interface::requests::Result { let key_info_manager_impl = self .key_info_manager_impl .read() .expect("Key Info Manager lock poisoned"); let key_info = match key_info_manager_impl.get(key_identity) { Ok(Some(key_info)) => key_info, Ok(None) => return Err(ResponseStatus::PsaErrorDoesNotExist), Err(string) => return Err(to_response_status(string)), }; Ok(key_info.attributes) } /// Get all the key identities for the current provider pub fn get_all(&self) -> parsec_interface::requests::Result> { let key_info_manager_impl = self .key_info_manager_impl .read() .expect("Key Info Manager lock poisoned"); key_info_manager_impl .get_all(self.provider_identity.clone()) .map_err(to_response_status) } /// Remove the key represented by a KeyIdentity and return the stored info. /// /// # Errors /// /// If the key does not exist, PsaErrorDoesNotExist is returned. If any other error occurs, /// KeyInfoManagerError is returned. pub fn remove_key_info( &self, key_identity: &KeyIdentity, ) -> parsec_interface::requests::Result<()> { let mut key_info_manager_impl = self .key_info_manager_impl .write() .expect("Key Info Manager lock poisoned"); match key_info_manager_impl.remove(key_identity) { Ok(Some(_key_info)) => Ok(()), Ok(None) => Err(ResponseStatus::PsaErrorDoesNotExist), Err(string) => Err(to_response_status(string)), } } /// Insert key info for a given KeyIdentity. /// /// # Errors /// /// If the KeyIdentity already existed in the KIM, PsaErrorAlreadyExists is returned. For /// any other error occurring in the KIM, KeyInfoManagerError is returned. pub fn insert_key_info( &self, key_identity: KeyIdentity, key_id: &T, attributes: Attributes, ) -> parsec_interface::requests::Result<()> { let mut key_info_manager_impl = self .key_info_manager_impl .write() .expect("Key Info Manager lock poisoned"); let key_info = KeyInfo { id: bincode::serialize(key_id)?, attributes, }; match key_info_manager_impl.insert(key_identity, key_info) { Ok(None) => Ok(()), Ok(Some(_)) => Err(ResponseStatus::PsaErrorAlreadyExists), Err(string) => Err(to_response_status(string)), } } /// Replace the KeyInfo saved for a given KeyIdentity /// /// # Errors /// /// If the key identity doesn't exist in the KIM, PsaErrorDoesNotExist is returned. For /// any other error occurring in the KIM, KeyInfoManagerError is returned. pub fn replace_key_info( &self, key_identity: KeyIdentity, key_id: &T, attributes: Attributes, ) -> parsec_interface::requests::Result<()> { let mut key_info_manager_impl = self .key_info_manager_impl .write() .expect("Key Info Manager lock poisoned"); let key_info = KeyInfo { id: bincode::serialize(key_id)?, attributes, }; match key_info_manager_impl.insert(key_identity.clone(), key_info) { Ok(None) => { let _ = key_info_manager_impl .remove(&key_identity) .map_err(to_response_status)?; Err(ResponseStatus::PsaErrorDoesNotExist) } Ok(Some(_)) => Ok(()), Err(string) => Err(to_response_status(string)), } } /// Returns a Vec of clients that have keys in this provider. /// /// # Errors /// /// Returns an error as a String if there was a problem accessing the Key Info Manager. pub fn list_clients(&self) -> parsec_interface::requests::Result> { let key_info_manager_impl = self .key_info_manager_impl .read() .expect("Key Info Manager lock poisoned"); let key_identities = key_info_manager_impl .get_all(self.provider_identity.clone()) .map_err(to_response_status)?; let mut clients = Vec::new(); for key_identity in key_identities { if !clients.contains(&key_identity.application) { clients.push(key_identity.application.clone()); } } Ok(clients) } /// Returns a Vec of the KeyInfo objects corresponding to the given ApplicationIdentity, /// and the KIM client ProviderIdentity. /// /// # Errors /// /// Returns an error as a String if there was a problem accessing the Key Info Manager. pub fn list_keys( &self, application_identity: &ApplicationIdentity, ) -> parsec_interface::requests::Result> { use parsec_interface::operations::list_keys::KeyInfo; let key_info_manager_impl = self .key_info_manager_impl .read() .expect("Key Info Manager lock poisoned"); let mut keys: Vec = Vec::new(); let key_identities = key_info_manager_impl .get_all(self.provider_identity.clone()) .map_err(to_response_status)?; for key_identity in key_identities { // If the OnDisk KIM is being used, only check if the app name is the same. // Otherwise, check if the entire ApplicationIdentity matches. // If it does not match, skip to the next key. match key_info_manager_impl.key_info_manager_type() { KeyInfoManagerType::OnDisk => { if key_identity.application().name() != application_identity.name() { continue; } } _ => { if key_identity.application() != application_identity { continue; } } } let key_info = key_info_manager_impl .get(&key_identity) .map_err(to_response_status)?; let key_info = match key_info { Some(key_info) => key_info, _ => continue, }; #[allow(deprecated)] let key_triple = KeyTriple::try_from(key_identity.clone()).map_err(to_response_status)?; // The KeyInfo structure we return here may need changing in the future to // accomodate for different authenticators, provider names etc. #[allow(deprecated)] keys.push(KeyInfo { provider_id: *key_triple.provider_id(), name: key_identity.key_name().to_string(), attributes: key_info.attributes, }); } Ok(keys) } /// Check if a KeyIdentity exists in the Key Info Manager and return a ResponseStatus /// /// # Errors /// /// Returns PsaErrorAlreadyExists if the KeyIdentity already exists or KeyInfoManagerError for /// another error. pub fn does_not_exist(&self, key_identity: &KeyIdentity) -> Result<(), ResponseStatus> { let key_info_manager_impl = self .key_info_manager_impl .read() .expect("Key Info Manager lock poisoned"); if key_info_manager_impl .exists(key_identity) .map_err(to_response_status)? { Err(ResponseStatus::PsaErrorAlreadyExists) } else { Ok(()) } } } /// Builder for KeyInfoManager clients #[derive(Derivative)] #[derivative(Debug)] pub struct KeyInfoManagerFactory { #[derivative(Debug = "ignore")] key_info_manager_impl: Arc>, } impl KeyInfoManagerFactory { /// Create a KeyInfoManagerFactory pub fn new(config: &KeyInfoManagerConfig, default_auth_type: AuthType) -> Result { let factory = match config.manager_type { KeyInfoManagerType::OnDisk => { let mut builder = on_disk_manager::OnDiskKeyInfoManagerBuilder::new(); if let Some(store_path) = &config.store_path { builder = builder.with_mappings_dir_path(store_path.into()); } builder = builder.with_auth_type(default_auth_type); let manager = builder.build()?; KeyInfoManagerFactory { key_info_manager_impl: Arc::new(RwLock::new(manager)), } } KeyInfoManagerType::SQLite => { let mut builder = sqlite_manager::SQLiteKeyInfoManagerBuilder::new(); if let Some(sqlite_db_path) = &config.sqlite_db_path { builder = builder.with_db_path(sqlite_db_path.into()); } let manager = builder.build()?; KeyInfoManagerFactory { key_info_manager_impl: Arc::new(RwLock::new(manager)), } } }; Ok(factory) } /// Build a KeyInfoManagerClient pub fn build_client(&self, provider_identity: ProviderIdentity) -> KeyInfoManagerClient { KeyInfoManagerClient { key_info_manager_impl: self.key_info_manager_impl.clone(), provider_identity, } } } parsec-service-1.3.0/src/key_info_managers/on_disk_manager/mod.rs000064400000000000000000001137561046102023000232710ustar 00000000000000// Copyright 2019 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! A key info manager storing key triple to key info mapping on files on disk //! //! The path where the mappings should be stored is configurable. Because of possible data races, //! there should not be two instances of this manager pointing to the same mapping folder at a time. //! Methods modifying the mapping will also block until the modifications are done on disk to be //! ensured to not lose mappings. //! Because application and key names can contain any UTF-8 characters, those strings are converted //! to base64 strings so that they can be used as filenames. Because of filenames limitations, some //! very long UTF-8 names might not be able to be represented as a filename and will fail. For //! example, for operating systems having a limit of 255 characters for filenames (Unix systems), //! names will be limited to 188 bytes of UTF-8 characters. //! For security reasons, only the PARSEC service should have the ability to modify these files. use crate::authenticators::{Application, ApplicationIdentity}; use crate::utils::config::KeyInfoManagerType; use super::{KeyIdentity, KeyInfo, ManageKeyInfo, ProviderIdentity}; use crate::providers::core::Provider as CoreProvider; #[cfg(feature = "cryptoauthlib-provider")] use crate::providers::cryptoauthlib::Provider as CryptoAuthLibProvider; #[cfg(feature = "mbed-crypto-provider")] use crate::providers::mbed_crypto::Provider as MbedCryptoProvider; #[cfg(feature = "pkcs11-provider")] use crate::providers::pkcs11::Provider as Pkcs11Provider; #[cfg(feature = "tpm-provider")] use crate::providers::tpm::Provider as TpmProvider; #[cfg(feature = "trusted-service-provider")] use crate::providers::trusted_service::Provider as TrustedServiceProvider; use anyhow::{Context, Result}; use base64::Engine; use log::{error, info, warn}; use parsec_interface::requests::{AuthType, ProviderId}; use std::collections::HashMap; use std::convert::TryFrom; use std::ffi::OsStr; use std::fs::Permissions; use std::fs::{DirEntry, File}; use std::io::{Error, ErrorKind, Read, Write}; use std::ops::Deref; use std::os::unix::fs::PermissionsExt; use std::path::{Path, PathBuf}; use std::{fmt, fs}; /// Default path where the mapping files will be stored on disk pub const DEFAULT_MAPPINGS_PATH: &str = "/var/lib/parsec/mappings"; ///Permissions for all directories under database directory ///Should only be visible to parsec user pub const DIR_PERMISSION: u32 = 0o700; ///Permissions for all files under database directory ///Should only be visible to parsec user pub const FILE_PERMISSION: u32 = 0o600; /// String wrapper for app names #[deprecated(since = "0.9.0", note = "ApplicationIdentity should be used instead.")] #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct ApplicationName { name: String, } #[allow(deprecated)] impl Deref for ApplicationName { type Target = String; fn deref(&self) -> &Self::Target { &self.name } } #[allow(deprecated)] #[deprecated(since = "0.9.0", note = "ApplicationIdentity should be used instead.")] impl ApplicationName { /// Create ApplicationName from name string only pub fn from_name(name: String) -> ApplicationName { ApplicationName { name } } } #[allow(deprecated)] impl std::fmt::Display for ApplicationName { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.name) } } #[allow(deprecated)] impl From for ApplicationName { fn from(app: Application) -> Self { ApplicationName::from_name(app.identity().name().clone()) } } /// Should only be used internally to map KeyTriple to the new KeyIdentity /// for the on_disk_manager KeyInfoManager. /// Structure corresponds to a unique identifier of the key. /// It is used internally by the Key ID manager to refer to a key. #[deprecated(since = "0.9.0", note = "KeyIdentity should be used instead.")] #[allow(deprecated)] #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct KeyTriple { app_name: ApplicationName, provider_id: ProviderId, key_name: String, } #[allow(deprecated)] #[deprecated(since = "0.9.0", note = "KeyIdentity should be used instead.")] impl KeyTriple { /// Creates a new instance of KeyTriple. pub fn new(app_name: ApplicationName, provider_id: ProviderId, key_name: String) -> KeyTriple { KeyTriple { app_name, provider_id, key_name, } } /// Checks if this key belongs to a specific provider. pub fn belongs_to_provider(&self, provider_id: ProviderId) -> bool { self.provider_id == provider_id } /// Get the provider id pub fn provider_id(&self) -> &ProviderId { &self.provider_id } /// Get the key name pub fn key_name(&self) -> &str { &self.key_name } /// Get the app name pub fn app_name(&self) -> &ApplicationName { &self.app_name } } #[allow(deprecated)] impl fmt::Display for KeyTriple { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "KeyTriple: app_name=\"{}\" provider_id={} key_name=\"{}\"", self.app_name, self.provider_id, self.key_name ) } } #[allow(deprecated)] impl TryFrom for KeyTriple { type Error = String; fn try_from(key_identity: KeyIdentity) -> ::std::result::Result { let provider_id = match key_identity.provider.uuid().as_str() { CoreProvider::PROVIDER_UUID => Ok(ProviderId::Core), #[cfg(feature = "cryptoauthlib-provider")] CryptoAuthLibProvider::PROVIDER_UUID => Ok(ProviderId::CryptoAuthLib), #[cfg(feature = "mbed-crypto-provider")] MbedCryptoProvider::PROVIDER_UUID => Ok(ProviderId::MbedCrypto), #[cfg(feature = "pkcs11-provider")] Pkcs11Provider::PROVIDER_UUID => Ok(ProviderId::Pkcs11), #[cfg(feature = "tpm-provider")] TpmProvider::PROVIDER_UUID => Ok(ProviderId::Tpm), #[cfg(feature = "trusted-service-provider")] TrustedServiceProvider::PROVIDER_UUID => Ok(ProviderId::TrustedService), _ => Err(format!( "Cannot convert from KeyIdentity to KeyTriple. Provider \"{}\" is not recognised. Could be it does not exist, or Parsec was not compiled with the required provider feature flags.", key_identity.provider().uuid() )), }?; let app_name = ApplicationName::from_name(key_identity.application().name().clone()); Ok(KeyTriple { provider_id, app_name, key_name: key_identity.key_name, }) } } #[allow(deprecated)] impl TryFrom<(KeyTriple, ProviderIdentity, AuthType)> for KeyIdentity { type Error = String; fn try_from( (key_triple, provider_identity, auth_type): (KeyTriple, ProviderIdentity, AuthType), ) -> ::std::result::Result { // Result types required by clippy as Err result has the possibility of not being compiled. let provider_uuid = match key_triple.provider_id { ProviderId::Core => Ok::( CoreProvider::PROVIDER_UUID.to_string(), ), #[cfg(feature = "cryptoauthlib-provider")] ProviderId::CryptoAuthLib => Ok::(CryptoAuthLibProvider::PROVIDER_UUID.to_string()), #[cfg(feature = "mbed-crypto-provider")] ProviderId::MbedCrypto => Ok::(MbedCryptoProvider::PROVIDER_UUID.to_string()), #[cfg(feature = "pkcs11-provider")] ProviderId::Pkcs11 => Ok::(Pkcs11Provider::PROVIDER_UUID.to_string()), #[cfg(feature = "tpm-provider")] ProviderId::Tpm => Ok::(TpmProvider::PROVIDER_UUID.to_string()), #[cfg(feature = "trusted-service-provider")] ProviderId::TrustedService => Ok::(TrustedServiceProvider::PROVIDER_UUID.to_string()), #[cfg(not(all( feature = "cryptoauthlib-provider", feature = "mbed-crypto-provider", feature = "pkcs11-provider", feature = "tpm-provider", feature = "trusted-service-provider", )))] _ => Err(format!("Cannot convert from KeyTriple to KeyIdentity.\nProvider \"{}\" is not recognised.\nCould be it does not exist, or Parsec was not compiled with the required provider feature flags.", key_triple.provider_id)), }?; Ok(KeyIdentity { provider: ProviderIdentity::new(provider_uuid, provider_identity.name().clone()), application: ApplicationIdentity::new(key_triple.app_name().to_string(), auth_type), key_name: key_triple.key_name.to_string(), }) } } /// A key info manager storing key triple to key info mapping on files on disk #[derive(Debug)] pub struct OnDiskKeyInfoManager { /// Internal mapping, used for non-modifying operations. #[allow(deprecated)] key_store: HashMap, /// Folder where all the key triple to key info mappings are saved. This folder will be created /// if it does already exist. mappings_dir_path: PathBuf, /// The AuthType currently being used by Parsec and hence used to namespace the OnDiskKeyInfoManager. auth_type: AuthType, } /// Encodes a KeyTriple's data into base64 strings that can be used as filenames. /// The ProviderId will not be converted as a base64 as it can always be represented as a String /// being a number from 0 and 255. #[allow(deprecated)] fn key_triple_to_base64_filenames(key_triple: &KeyTriple) -> (String, String, String) { ( base64::engine::general_purpose::URL_SAFE.encode(key_triple.app_name.as_bytes()), (key_triple.provider_id as u8).to_string(), base64::engine::general_purpose::URL_SAFE.encode(key_triple.key_name.as_bytes()), ) } /// Decodes base64 bytes to its original String value. /// /// # Errors /// /// Returns an error as a string if either the decoding or the bytes conversion to UTF-8 failed. fn base64_data_to_string(base64_bytes: &[u8]) -> Result { match base64::engine::general_purpose::URL_SAFE.decode(base64_bytes) { Ok(decode_bytes) => match String::from_utf8(decode_bytes) { Ok(string) => Ok(string), Err(error) => Err(error.to_string()), }, Err(error) => Err(error.to_string()), } } /// Decodes key triple's data to the original path. /// The Provider ID data is not converted as base64. /// /// # Errors /// /// Returns an error as a string if either the decoding or the bytes conversion to UTF-8 failed. #[allow(deprecated)] fn base64_data_triple_to_key_triple( app_name: &[u8], provider_id: ProviderId, key_name: &[u8], ) -> Result { let app_name = ApplicationName::from_name(base64_data_to_string(app_name)?); let key_name = base64_data_to_string(key_name)?; Ok(KeyTriple { app_name, provider_id, key_name, }) } /// Converts an OsStr reference to a byte array. /// /// # Errors /// /// Returns a custom std::io error if the conversion failed. fn os_str_to_u8_ref(os_str: &OsStr) -> std::io::Result<&[u8]> { match os_str.to_str() { Some(str) => Ok(str.as_bytes()), None => Err(Error::new( ErrorKind::Other, "Conversion from PathBuf to String failed.", )), } } /// Converts an OsStr reference to a ProviderId value. /// /// # Errors /// /// Returns a custom std::io error if the conversion failed. fn os_str_to_provider_id(os_str: &OsStr) -> std::io::Result { match os_str.to_str() { Some(str) => match str.parse::() { Ok(provider_id_u8) => match ProviderId::try_from(provider_id_u8) { Ok(provider_id) => Ok(provider_id), Err(response_status) => { Err(Error::new(ErrorKind::Other, response_status.to_string())) } }, Err(_) => Err(Error::new( ErrorKind::Other, "Failed to convert Provider directory name to an u8 number.", )), }, None => Err(Error::new( ErrorKind::Other, "Conversion from PathBuf to String failed.", )), } } /// Lists all the directory paths in the given directory path. fn list_dirs(path: &Path) -> std::io::Result> { // read_dir returning an iterator over Result, there is first a conversion to a path // and then a check if the path is a directory or not. let dir_entries: std::io::Result> = path.read_dir()?.collect(); Ok(dir_entries? .iter() .map(|dir_entry| dir_entry.path()) .filter(|dir_path| dir_path.is_dir()) .collect()) } /// Lists all the file paths in the given directory path. fn list_files(path: &Path) -> std::io::Result> { let dir_entries: std::io::Result> = path.read_dir()?.collect(); Ok(dir_entries? .iter() .map(|dir_entry| dir_entry.path()) .filter(|dir_path| dir_path.is_file()) .collect()) } /// Filesystem-based `KeyInfoManager` /// /// The `OnDiskKeyInfoManager` relies on access control mechanisms provided by the OS for /// the filesystem to ensure security of the mappings. #[allow(deprecated)] impl OnDiskKeyInfoManager { /// Creates an instance of the on-disk manager from the mapping files. This function will /// create the mappings directory if it does not already exist. /// The mappings folder is composed of three levels: two levels of directory and one level /// of files. The key triple to key info mappings are represented on disk as the following: /// /// mappings_dir_path/ /// |---app1/ /// | |---provider1/ /// | | |---key1 /// | | |---key2 /// | | | ... /// | | |---keyP /// | |---provider2/ /// | | ... /// | |---providerM/ /// |---app2/ /// | ... /// |---appN/ /// /// where the path of a key name from the mappings directory is the key triple (application, /// provider, key) and the data inside the key name file is the key info serialised in binary /// format. /// Each mapping is contained in its own file to prevent the modification of one mapping /// impacting the other ones. /// /// # Errors /// /// Returns an std::io error if the function failed reading the mapping files. fn new(mappings_dir_path: PathBuf, auth_type: AuthType) -> Result { let mut key_store = HashMap::new(); // Will ignore if the mappings directory already exists. fs::create_dir_all(&mappings_dir_path).with_context(|| { format!( "Failed to create Key Info Mappings directory at {:?}", mappings_dir_path ) })?; for app_name_dir_path in list_dirs(&mappings_dir_path)?.iter() { for provider_dir_path in list_dirs(app_name_dir_path)?.iter() { for key_name_file_path in list_files(provider_dir_path)?.iter() { let mut key_info = Vec::new(); let mut key_info_file = File::open(key_name_file_path).with_context(|| { format!( "Failed to open Key Info Mappings file at {:?}", key_name_file_path ) })?; let _ = key_info_file.read_to_end(&mut key_info)?; let key_info = bincode::deserialize(&key_info[..]).map_err(|e| { format_error!("Error deserializing key info", e); Error::new(ErrorKind::Other, "error deserializing key info") })?; match base64_data_triple_to_key_triple( os_str_to_u8_ref(app_name_dir_path.file_name().expect( "The application name directory path should contain a final component.", ))?, os_str_to_provider_id(provider_dir_path.file_name().expect( "The provider directory path should contain a final component.", ))?, os_str_to_u8_ref(key_name_file_path.file_name().expect( "The key name directory path should contain a final component.", ))?, ) { Ok(key_triple) => { if crate::utils::GlobalConfig::log_error_details() { warn!( "Inserting Key Triple ({}) mapping read from disk.", key_triple.clone() ); } let _ = key_store.insert(key_triple, key_info); } Err(string) => { format_error!( "Failed to convert the mapping path found to an UTF-8 string", string ); return Err( Error::new(ErrorKind::Other, "error parsing mapping path").into() ); } } } } } if !crate::utils::GlobalConfig::log_error_details() { info!("Found {} mapping files", key_store.len()); } let permissions = Permissions::from_mode(DIR_PERMISSION); fs::set_permissions(&mappings_dir_path, permissions)?; Ok(OnDiskKeyInfoManager { key_store, mappings_dir_path, auth_type, }) } /// Saves the key triple to key info mapping in its own file. /// The filename will be `mappings/[APP_NAME]/[PROVIDER_NAME]/[KEY_NAME]` under the same path as the /// on-disk manager. It will contain the Key info data. fn save_mapping(&self, key_triple: &KeyTriple, key_info: &KeyInfo) -> std::io::Result<()> { if crate::utils::GlobalConfig::log_error_details() { warn!( "Saving Key Triple ({}) mapping to disk.", key_triple.clone() ); } // Create the directories with base64 names. let (app_name, prov, key_name) = key_triple_to_base64_filenames(key_triple); let app_dir_path = self.mappings_dir_path.join(app_name); let provider_dir_path = app_dir_path.join(prov); let key_name_file_path = provider_dir_path.join(key_name); // Will ignore if they already exist. fs::create_dir_all(&provider_dir_path).map_err(|e| { format_error!( format!( "Failed to create provider directory as {:?}", &provider_dir_path ), e ); e })?; let dir_permissions = Permissions::from_mode(DIR_PERMISSION); fs::set_permissions(&app_dir_path, dir_permissions.clone())?; fs::set_permissions(&provider_dir_path, dir_permissions)?; if key_name_file_path.exists() { fs::remove_file(&key_name_file_path)?; } let mut mapping_file = fs::File::create(&key_name_file_path).map_err(|e| { error!( "Failed to create Key Info Mapping file at {:?}", key_name_file_path ); e })?; let file_permissions = Permissions::from_mode(FILE_PERMISSION); fs::set_permissions(&key_name_file_path, file_permissions)?; mapping_file.write_all(&bincode::serialize(key_info).map_err(|e| { format_error!("Error serializing key info", e); Error::new(ErrorKind::Other, "error serializing key info") })?) } /// Removes the mapping file. /// Will do nothing if the mapping file does not exist. fn delete_mapping(&self, key_triple: &KeyTriple) -> std::io::Result<()> { let (app_name, prov, key_name) = key_triple_to_base64_filenames(key_triple); let key_name_file_path = self .mappings_dir_path .join(app_name) .join(prov) .join(key_name); if key_name_file_path.exists() { fs::remove_file(key_name_file_path) } else { Ok(()) } } } #[allow(deprecated)] impl ManageKeyInfo for OnDiskKeyInfoManager { fn key_info_manager_type(&self) -> KeyInfoManagerType { KeyInfoManagerType::OnDisk } fn get(&self, key_identity: &KeyIdentity) -> Result, String> { let key_triple = KeyTriple::try_from(key_identity.clone())?; // An Option<&Vec> can not automatically coerce to an Option<&[u8]>, it needs to be // done by hand. if let Some(key_info) = self.key_store.get(&key_triple) { Ok(Some(key_info)) } else { Ok(None) } } fn get_all(&self, provider_identity: ProviderIdentity) -> Result, String> { let provider_id = ProviderId::try_from(provider_identity.clone())?; let key_triples = self .key_store .keys() .filter(|key_triple| key_triple.belongs_to_provider(provider_id)); let mut key_identites = Vec::new(); for key_triple in key_triples { let key_identity = KeyIdentity::try_from(( key_triple.clone(), provider_identity.clone(), self.auth_type, ))?; key_identites.push(key_identity) } Ok(key_identites) } fn insert( &mut self, key_identity: KeyIdentity, key_info: KeyInfo, ) -> Result, String> { let key_triple = KeyTriple::try_from(key_identity)?; if let Err(err) = self.save_mapping(&key_triple, &key_info) { Err(err.to_string()) } else { Ok(self.key_store.insert(key_triple, key_info)) } } fn remove(&mut self, key_identity: &KeyIdentity) -> Result, String> { let key_triple = KeyTriple::try_from(key_identity.clone())?; if let Err(err) = self.delete_mapping(&key_triple) { Err(err.to_string()) } else if let Some(key_info) = self.key_store.remove(&key_triple) { Ok(Some(key_info)) } else { Ok(None) } } fn exists(&self, key_identity: &KeyIdentity) -> Result { let key_triple = KeyTriple::try_from(key_identity.clone())?; Ok(self.key_store.contains_key(&key_triple)) } } /// OnDiskKeyInfoManager builder #[derive(Debug, Default)] pub struct OnDiskKeyInfoManagerBuilder { mappings_dir_path: Option, auth_type: Option, } impl OnDiskKeyInfoManagerBuilder { /// Create a new OnDiskKeyInfoManagerBuilder pub fn new() -> OnDiskKeyInfoManagerBuilder { OnDiskKeyInfoManagerBuilder { mappings_dir_path: None, auth_type: None, } } /// Add a mappings directory path to the builder pub fn with_mappings_dir_path(mut self, path: PathBuf) -> OnDiskKeyInfoManagerBuilder { self.mappings_dir_path = Some(path); self } /// Add an authentication type to the builder pub fn with_auth_type(mut self, default_auth_type: AuthType) -> OnDiskKeyInfoManagerBuilder { self.auth_type = Some(default_auth_type); self } /// Build into a OnDiskKeyInfoManager pub fn build(self) -> Result { OnDiskKeyInfoManager::new( self.mappings_dir_path .unwrap_or_else(|| PathBuf::from(DEFAULT_MAPPINGS_PATH)), self.auth_type.ok_or_else(|| { Error::new( ErrorKind::InvalidData, "AuthType must be supplied to OnDiskKeyInfoManager", ) })?, ) } } #[cfg(test)] mod test { use super::super::{KeyIdentity, KeyInfo, ManageKeyInfo}; use super::OnDiskKeyInfoManager; use crate::key_info_managers::{ApplicationIdentity, ProviderIdentity}; use crate::providers::core::Provider as CoreProvider; #[cfg(feature = "mbed-crypto-provider")] use crate::providers::mbed_crypto::Provider as MbedCryptoProvider; use parsec_interface::operations::psa_algorithm::{ Algorithm, AsymmetricSignature, Hash, SignHash, }; use parsec_interface::operations::psa_key_attributes::{ Attributes, Lifetime, Policy, Type, UsageFlags, }; use parsec_interface::requests::AuthType; use std::fs; use std::path::PathBuf; fn test_key_attributes() -> Attributes { Attributes { lifetime: Lifetime::Persistent, key_type: Type::Derive, bits: 1024, policy: Policy { usage_flags: { let mut usage_flags = UsageFlags::default(); let _ = usage_flags.set_sign_hash(); usage_flags }, permitted_algorithms: Algorithm::AsymmetricSignature( AsymmetricSignature::RsaPkcs1v15Sign { hash_alg: SignHash::Specific(Hash::Sha256), }, ), }, } } fn test_key_info() -> KeyInfo { KeyInfo { id: vec![0x11, 0x22, 0x33], attributes: test_key_attributes(), } } #[test] fn insert_get_key_info() { let path = PathBuf::from(env!("OUT_DIR").to_owned() + "/insert_get_key_info_mappings"); let mut manager = OnDiskKeyInfoManager::new(path.clone(), AuthType::NoAuth).unwrap(); let key_identity = new_key_identity("insert_get_key_info".to_string()); let key_info = test_key_info(); assert!(manager.get(&key_identity).unwrap().is_none()); assert!(manager .insert(key_identity.clone(), key_info.clone()) .unwrap() .is_none()); let stored_key_info = manager .get(&key_identity) .unwrap() .expect("Failed to get key info") .clone(); assert_eq!(stored_key_info, key_info); assert!(manager.remove(&key_identity).unwrap().is_some()); fs::remove_dir_all(path).unwrap(); } #[test] fn insert_remove_key() { let path = PathBuf::from(env!("OUT_DIR").to_owned() + "/insert_remove_key_mappings"); let mut manager = OnDiskKeyInfoManager::new(path.clone(), AuthType::NoAuth).unwrap(); let key_identity = new_key_identity("insert_remove_key".to_string()); let key_info = test_key_info(); let _ = manager.insert(key_identity.clone(), key_info).unwrap(); assert!(manager.remove(&key_identity).unwrap().is_some()); fs::remove_dir_all(path).unwrap(); } #[test] fn remove_unexisting_key() { let path = PathBuf::from(env!("OUT_DIR").to_owned() + "/remove_unexisting_key_mappings"); let mut manager = OnDiskKeyInfoManager::new(path.clone(), AuthType::NoAuth).unwrap(); let key_identity = new_key_identity("remove_unexisting_key".to_string()); assert_eq!(manager.remove(&key_identity).unwrap(), None); fs::remove_dir_all(path).unwrap(); } #[test] fn exists() { let path = PathBuf::from(env!("OUT_DIR").to_owned() + "/exists_mappings"); let mut manager = OnDiskKeyInfoManager::new(path.clone(), AuthType::NoAuth).unwrap(); let key_identity = new_key_identity("exists".to_string()); let key_info = test_key_info(); assert!(!manager.exists(&key_identity).unwrap()); let _ = manager.insert(key_identity.clone(), key_info).unwrap(); assert!(manager.exists(&key_identity).unwrap()); let _ = manager.remove(&key_identity).unwrap(); assert!(!manager.exists(&key_identity).unwrap()); fs::remove_dir_all(path).unwrap(); } #[test] fn insert_overwrites() { let path = PathBuf::from(env!("OUT_DIR").to_owned() + "/insert_overwrites_mappings"); let mut manager = OnDiskKeyInfoManager::new(path.clone(), AuthType::NoAuth).unwrap(); let key_identity = new_key_identity("insert_overwrites".to_string()); let key_info_1 = test_key_info(); let key_info_2 = KeyInfo { id: vec![0xaa, 0xbb, 0xcc], attributes: test_key_attributes(), }; let _ = manager.insert(key_identity.clone(), key_info_1).unwrap(); let _ = manager .insert(key_identity.clone(), key_info_2.clone()) .unwrap(); let stored_key_info = manager .get(&key_identity) .unwrap() .expect("Failed to get key info") .clone(); assert_eq!(stored_key_info, key_info_2); assert!(manager.remove(&key_identity).unwrap().is_some()); fs::remove_dir_all(path).unwrap(); } #[test] fn big_names_ascii() { let path = PathBuf::from(env!("OUT_DIR").to_owned() + "/big_names_ascii_mappings"); let mut manager = OnDiskKeyInfoManager::new(path.clone(), AuthType::NoAuth).unwrap(); let big_app_name_ascii = " Lorem ipsum dolor sit amet, ei suas viris sea, deleniti repudiare te qui. Natum paulo decore ut nec, ne propriae offendit adipisci has. Eius clita legere mel at, ei vis minimum tincidunt.".to_string(); let big_key_name_ascii = " Lorem ipsum dolor sit amet, ei suas viris sea, deleniti repudiare te qui. Natum paulo decore ut nec, ne propriae offendit adipisci has. Eius clita legere mel at, ei vis minimum tincidunt.".to_string(); let key_identity = KeyIdentity::new( ApplicationIdentity::new(big_app_name_ascii, AuthType::NoAuth), ProviderIdentity::new( CoreProvider::PROVIDER_UUID.to_string(), CoreProvider::DEFAULT_PROVIDER_NAME.to_string(), ), big_key_name_ascii, ); let key_info = test_key_info(); let _ = manager .insert(key_identity.clone(), key_info.clone()) .unwrap(); assert_eq!(manager.remove(&key_identity).unwrap().unwrap(), key_info); fs::remove_dir_all(path).unwrap(); } #[test] fn big_names_emoticons() { let path = PathBuf::from(env!("OUT_DIR").to_owned() + "/big_names_emoticons_mappings"); let mut manager = OnDiskKeyInfoManager::new(path.clone(), AuthType::NoAuth).unwrap(); let big_app_name_emoticons = "😀😁😂😃😄😅😆😇😈😉😊😋😌😍😎😏😐😑😒😓😔😕😖😗😘😙😚😛😜😝😞😟😠😡😢😣😤😥😦😧😨😩😪😫😬😭😮".to_string(); let big_key_name_emoticons = "😀😁😂😃😄😅😆😇😈😉😊😋😌😍😎😏😐😑😒😓😔😕😖😗😘😙😚😛😜😝😞😟😠😡😢😣😤😥😦😧😨😩😪😫😬😭😮".to_string(); let key_identity = KeyIdentity::new( ApplicationIdentity::new(big_app_name_emoticons, AuthType::NoAuth), ProviderIdentity::new( CoreProvider::PROVIDER_UUID.to_string(), CoreProvider::DEFAULT_PROVIDER_NAME.to_string(), ), big_key_name_emoticons, ); let key_info = test_key_info(); let _ = manager .insert(key_identity.clone(), key_info.clone()) .unwrap(); assert_eq!(manager.remove(&key_identity).unwrap().unwrap(), key_info); fs::remove_dir_all(path).unwrap(); } #[cfg(feature = "mbed-crypto-provider")] #[test] fn create_and_load() { let path = PathBuf::from(env!("OUT_DIR").to_owned() + "/create_and_load_mappings"); let app_name1 = "😀 Application One 😀".to_string(); let key_name1 = "😀 Key One 😀".to_string(); let key_identity_1 = KeyIdentity::new( ApplicationIdentity::new(app_name1, AuthType::NoAuth), ProviderIdentity::new( CoreProvider::PROVIDER_UUID.to_string(), CoreProvider::DEFAULT_PROVIDER_NAME.to_string(), ), key_name1, ); let key_info1 = test_key_info(); let app_name2 = "😇 Application Two 😇".to_string(); let key_name2 = "😇 Key Two 😇".to_string(); let key_identity_2 = KeyIdentity::new( ApplicationIdentity::new(app_name2, AuthType::NoAuth), ProviderIdentity::new( MbedCryptoProvider::PROVIDER_UUID.to_string(), MbedCryptoProvider::DEFAULT_PROVIDER_NAME.to_string(), ), key_name2, ); let key_info2 = KeyInfo { id: vec![0x12, 0x22, 0x32], attributes: test_key_attributes(), }; let app_name3 = "😈 Application Three 😈".to_string(); let key_name3 = "😈 Key Three 😈".to_string(); let key_identity_3 = KeyIdentity::new( ApplicationIdentity::new(app_name3, AuthType::NoAuth), ProviderIdentity::new( CoreProvider::PROVIDER_UUID.to_string(), CoreProvider::DEFAULT_PROVIDER_NAME.to_string(), ), key_name3, ); let key_info3 = KeyInfo { id: vec![0x13, 0x23, 0x33], attributes: test_key_attributes(), }; { let mut manager = OnDiskKeyInfoManager::new(path.clone(), AuthType::NoAuth).unwrap(); let _ = manager .insert(key_identity_1.clone(), key_info1.clone()) .unwrap(); let _ = manager .insert(key_identity_2.clone(), key_info2.clone()) .unwrap(); let _ = manager .insert(key_identity_3.clone(), key_info3.clone()) .unwrap(); } // The local hashmap is dropped when leaving the inner scope. { let mut manager = OnDiskKeyInfoManager::new(path.clone(), AuthType::NoAuth).unwrap(); assert_eq!(manager.remove(&key_identity_1).unwrap().unwrap(), key_info1); assert_eq!(manager.remove(&key_identity_2).unwrap().unwrap(), key_info2); assert_eq!(manager.remove(&key_identity_3).unwrap().unwrap(), key_info3); } fs::remove_dir_all(path).unwrap(); } fn new_key_identity(key_name: String) -> KeyIdentity { KeyIdentity::new( ApplicationIdentity::new("Testing Application 😎".to_string(), AuthType::NoAuth), ProviderIdentity::new( CoreProvider::PROVIDER_UUID.to_string(), CoreProvider::DEFAULT_PROVIDER_NAME.to_string(), ), key_name, ) } #[cfg(feature = "mbed-crypto-provider")] mod permissions_test { use super::*; use crate::key_info_managers::on_disk_manager::DIR_PERMISSION; use crate::key_info_managers::on_disk_manager::FILE_PERMISSION; use std::fs::Permissions; use std::io; use std::os::unix::fs::PermissionsExt; use std::path::Path; // loop through every directory and file and check permissions fn check_permissions(dir: &Path) -> io::Result<()> { let file_permissions = Permissions::from_mode(FILE_PERMISSION); let dir_permissions = Permissions::from_mode(DIR_PERMISSION); if dir.is_dir() { for entry in fs::read_dir(dir)? { let path = entry?.path(); if path.is_dir() { assert_eq!( fs::metadata(&path)?.permissions().mode() & dir_permissions.mode(), dir_permissions.mode() ); check_permissions(&path)?; } else { assert_eq!( fs::metadata(&path)?.permissions().mode() & file_permissions.mode(), file_permissions.mode() ); } } } Ok(()) } #[test] fn check_kim_permissions() { let dir_permissions = Permissions::from_mode(DIR_PERMISSION); let path = PathBuf::from(env!("OUT_DIR").to_owned() + "/check_permissions"); let app_name1 = "App1".to_string(); let key_name1 = "Key1".to_string(); let key_identity_1 = KeyIdentity::new( ApplicationIdentity::new(app_name1, AuthType::NoAuth), ProviderIdentity::new( CoreProvider::PROVIDER_UUID.to_string(), CoreProvider::DEFAULT_PROVIDER_NAME.to_string(), ), key_name1, ); let key_info1 = test_key_info(); let app_name2 = "App2".to_string(); let key_name2 = "Key2".to_string(); let key_identity_2 = KeyIdentity::new( ApplicationIdentity::new(app_name2, AuthType::NoAuth), ProviderIdentity::new( MbedCryptoProvider::PROVIDER_UUID.to_string(), MbedCryptoProvider::DEFAULT_PROVIDER_NAME.to_string(), ), key_name2, ); let key_info2 = KeyInfo { id: vec![0x12, 0x22, 0x32], attributes: test_key_attributes(), }; let mut manager = OnDiskKeyInfoManager::new(path.clone(), AuthType::NoAuth).unwrap(); assert_eq!( fs::metadata(path.clone()).unwrap().permissions().mode() & dir_permissions.mode(), dir_permissions.mode() ); let _ = manager.insert(key_identity_1.clone(), key_info1).unwrap(); let _ = manager.insert(key_identity_2.clone(), key_info2).unwrap(); let _ = check_permissions(&path).is_ok(); let _ = manager.remove(&key_identity_1).unwrap().unwrap(); let _ = manager.remove(&key_identity_2).unwrap().unwrap(); fs::remove_dir_all(path).unwrap(); } } } parsec-service-1.3.0/src/key_info_managers/sqlite_manager/mod.rs000064400000000000000000001120371046102023000231330ustar 00000000000000// Copyright 2021 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! A key info manager storing key identity to key info mappings using a SQLite database. //! //! For security reasons, only the PARSEC service should have the ability to modify these files. use super::{KeyIdentity, KeyInfo, ManageKeyInfo}; use crate::authenticators::ApplicationIdentity; use crate::providers::ProviderIdentity; use crate::utils::config::KeyInfoManagerType; use anyhow::{Context, Result}; use log::{error, info}; use num_traits::FromPrimitive; use parsec_interface::operations::psa_key_attributes::Attributes; use parsec_interface::requests::AuthType; use rusqlite::types::Type::{Blob, Integer}; use rusqlite::{params, Connection, Error as RusqliteError}; use std::collections::HashMap; use std::fs; use std::fs::Permissions; use std::io::{Error, ErrorKind}; use std::os::unix::fs::PermissionsExt; use std::path::PathBuf; /// Default path where the database will be stored on disk pub const DEFAULT_DB_PATH: &str = "/var/lib/parsec/kim-mappings/sqlite/sqlite-key-info-manager.sqlite3"; ///File permissions for sqlite database ///Should only be visible to parsec user pub const FILE_PERMISSION: u32 = 0o600; /// The current serialization version of the Attributes object. pub const CURRENT_KEY_ATTRIBUTES_VERSION: u8 = 1; /// Placeholder global key_id_version until a new key id version for /// one of the providers is needed. pub const CURRENT_KEY_ID_VERSION: u8 = 1; /// The current database schema version of the SQLiteKeyInfoManager. pub const CURRENT_SCHEMA_VERSION: u8 = 1; /// A key info manager storing key identity to key info mapping on files on disk #[derive(Debug)] pub struct SQLiteKeyInfoManager { /// Internal mapping, used for non-modifying operations. key_store: HashMap, /// The file path where the SQLite database exists. This database holds /// key identity to key info mappings. database_path: PathBuf, } /// Converts a 64 bit integer to an AuthType fn i64_to_auth_type(auth_type: i64) -> Result { match FromPrimitive::from_i64(auth_type) { Some(auth_type) => Ok(auth_type), None => Err(format!( "Failed to get AuthType from authenticator_id.\nAuthenticator \"{}\" does not exist.", auth_type )), } } /// SQLite-based `KeyInfoManager` /// /// The `SQLiteKeyInfoManager` relies on access control mechanisms provided by the OS for /// the filesystem to ensure security of the database. impl SQLiteKeyInfoManager { /// Creates an instance of the sqlite key info manager. /// The SQLiteKeyInfoManager stores key info in the provided database_path file. /// Uses rusqlite. fn new(database_path: PathBuf) -> Result { // Create directory if it does not already exist let mut directory_path = database_path.clone(); let _ = directory_path.pop(); fs::create_dir_all(&directory_path) .with_context(|| format!("create directory {:?}", directory_path))?; // Connect to or create database at set path let conn = Connection::open(&database_path)?; let mut key_store = HashMap::new(); // Check if the tables we require exist let mut check_for_tables_stmt = conn.prepare( " SELECT * FROM sqlite_master WHERE type='table' AND ( name='key_mapping' OR name='kim_metadata' ) ", )?; let key_iter = check_for_tables_stmt.query_map([], |_row| Ok(()))?; let num_of_tables = key_iter.count(); match num_of_tables { // Create tables as they do not exist. 0 => { let _ = conn.execute( " CREATE TABLE kim_metadata ( id TEXT NOT NULL, int_value INTEGER NOT NULL, PRIMARY KEY (id) ) ", [], )?; let _ = conn.execute( " INSERT INTO kim_metadata (id, int_value) VALUES ('schema_version', ?1) ", params![CURRENT_SCHEMA_VERSION], )?; let _ = conn.execute( " CREATE TABLE IF NOT EXISTS key_mapping ( authenticator_id INTEGER NOT NULL, application_name TEXT NOT NULL, key_name TEXT NOT NULL, provider_uuid TEXT NOT NULL, provider_name TEXT NOT NULL, key_id BLOB NOT NULL, key_id_version INTEGER NOT NULL, key_attributes BLOB NOT NULL, key_attributes_version INTEGER NOT NULL, PRIMARY KEY (authenticator_id, application_name, key_name) ) ", [], )?; } // The correct number of tables are present, no-op 2 => {} // The KIM expects both the kim_metadata and key_mapping table to be present, throw an error _ => { let error_message = format!( "SQLiteKeyInfoManager database schema is not in a recognised format. There is an unrecognised number of tables in the database. Database found at {}", database_path .into_os_string() .into_string() .unwrap_or_else(|_| "DB_FILE_PATH_UNKNOWN".to_string()), ); error!("{}", error_message); return Err(Error::new(ErrorKind::Other, error_message).into()); } } // The tables we require exist, check schema version matches let mut schema_version_stmt = conn.prepare( " SELECT * FROM kim_metadata WHERE id = 'schema_version' ", )?; let mut rows = schema_version_stmt.query(params![])?; while let Some(row) = rows.next()? { let version_number: u8 = row.get("int_value")?; if version_number != CURRENT_SCHEMA_VERSION { let error_message = format!( " SQLiteKeyInfoManager database schema version is incompatible. Parsec Service is using version [{}]. Database at [{}] is using version [{}]. ", CURRENT_SCHEMA_VERSION, database_path .into_os_string() .into_string() .unwrap_or_else(|_| "DB_FILE_PATH_UNKNOWN".to_string()), version_number ); error!("{}", error_message); return Err(Error::new(ErrorKind::Other, error_message).into()); } } // The tables we require exist and the schema version is the correct. // Check that the key_info_version for every key is correct. let mut key_info_version_stmt = conn.prepare( " SELECT * FROM key_mapping WHERE key_id_version != ?1 OR key_attributes_version != ?2 ", )?; let mut rows = key_info_version_stmt.query(params![ CURRENT_KEY_ID_VERSION, CURRENT_KEY_ATTRIBUTES_VERSION ])?; // If a mapping exists with the wrong key_id_version or key_attributes_version, throw an error. if let Some(row) = rows.next()? { let key_id_version: u8 = row.get("key_id_version")?; let key_attributes_version: u8 = row.get("key_attributes_version")?; let error_message = format!( " Some records within the SQLiteKeyInfoManager are using an incompatible key_id_version or key_attributes_version. Parsec Service SQLiteKeyInfoManager is using [key_id_version={}, key_attributes_version={}]. Database at [{}] contains mapping(s) using [key_id_version={}, key_attributes_version={}]. ", CURRENT_KEY_ID_VERSION, CURRENT_KEY_ATTRIBUTES_VERSION, database_path .into_os_string() .into_string() .unwrap_or_else(|_| "DB_FILE_PATH".to_string()), key_id_version, key_attributes_version, ); error!("{}", error_message); return Err(Error::new(ErrorKind::Other, error_message).into()); } // All checks have passed, load key mappings let mut key_mapping_stmt = conn.prepare( " SELECT * FROM key_mapping ", )?; // Deserialize key mappings and store within local key_store HashMap. let mut rows = key_mapping_stmt.query(params![])?; while let Some(row) = rows.next()? { let key_identity = KeyIdentity::new( ApplicationIdentity::new( row.get("application_name")?, i64_to_auth_type(row.get("authenticator_id")?).map_err(|e| { format_error!("Failed to get AuthType from authenticator_id.", e); let error = Box::new(Error::new(ErrorKind::InvalidData, e)); RusqliteError::FromSqlConversionFailure(64, Integer, error) })?, ), ProviderIdentity::new(row.get("provider_uuid")?, row.get("provider_name")?), row.get("key_name")?, ); let key_id: Vec = row.get("key_id")?; let key_attributes_blob: Vec = row.get("key_attributes")?; let key_attributes: Attributes = bincode::deserialize(&key_attributes_blob[..]) .map_err(|e| { format_error!("Error deserializing key attributes", e); RusqliteError::FromSqlConversionFailure(key_attributes_blob.len(), Blob, e) })?; let key_info = KeyInfo { id: key_id, attributes: key_attributes, }; let _ = key_store.insert(key_identity, key_info); } if !crate::utils::GlobalConfig::log_error_details() { info!( "SQLiteKeyInfoManager - Found {} key info mapping records", key_store.len() ); } let permissions = Permissions::from_mode(FILE_PERMISSION); fs::set_permissions(database_path.clone(), permissions)?; Ok(SQLiteKeyInfoManager { key_store, database_path, }) } /// Saves the KeyIdentity and KeyInfo to the database. /// Inserts a new record to the database `key_mapping` table. fn save_mapping( &self, key_identity: &KeyIdentity, key_info: &KeyInfo, ) -> rusqlite::Result<(), RusqliteError> { let conn = Connection::open(&self.database_path)?; // The key_info.id should already be serialized using bincode at this stage by the // KIM client insert_key_info() function. let key_id_blob = key_info.id.clone(); // TODO: Change this to (protobuf?) version once format has been decided. // https://github.com/parallaxsecond/parsec/issues/424#issuecomment-883608164 let key_attributes_blob = bincode::serialize(&key_info.attributes).map_err(|e| { format_error!("Error serializing key info", e); RusqliteError::ToSqlConversionFailure(e) })?; // Insert the new key mapping, if a record does not exist. // If one does exist, replace the existing record. let _ = conn.execute( " REPLACE INTO `key_mapping` (`authenticator_id`, `application_name`, `provider_uuid`, `provider_name`, `key_name`, `key_id`, `key_id_version`, `key_attributes`, `key_attributes_version`) VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9); ", params![ *key_identity.application().authenticator_id() as u8, key_identity.application().name(), key_identity.provider().uuid(), key_identity.provider().name(), key_identity.key_name(), key_id_blob, // Key ID versioning will eventually need passing down from individual providers // if the serialization structure of one of them changes. CURRENT_KEY_ID_VERSION, key_attributes_blob, CURRENT_KEY_ATTRIBUTES_VERSION, ], )?; Ok(()) } /// Removes the mapping record. /// Will do nothing if the mapping record does not exist. fn delete_mapping(&self, key_identity: &KeyIdentity) -> rusqlite::Result<(), RusqliteError> { let conn = Connection::open(&self.database_path)?; let _ = conn.execute( " DELETE FROM `key_mapping` WHERE `authenticator_id` = ?1 AND `application_name` = ?2 AND `key_name` = ?3 ", params![ *key_identity.application().authenticator_id() as u8, key_identity.application().name(), key_identity.key_name(), ], )?; Ok(()) } } impl ManageKeyInfo for SQLiteKeyInfoManager { fn key_info_manager_type(&self) -> KeyInfoManagerType { KeyInfoManagerType::SQLite } fn get(&self, key_identity: &KeyIdentity) -> Result, String> { if let Some(key_info) = self.key_store.get(key_identity) { Ok(Some(key_info)) } else { Ok(None) } } fn get_all(&self, provider_identity: ProviderIdentity) -> Result, String> { Ok(self .key_store .keys() .filter(|key_identity| key_identity.belongs_to_provider(&provider_identity)) .cloned() .collect()) } fn insert( &mut self, key_identity: KeyIdentity, key_info: KeyInfo, ) -> Result, String> { if let Err(err) = self.save_mapping(&key_identity, &key_info) { Err(err.to_string()) } else { Ok(self.key_store.insert(key_identity, key_info)) } } fn remove(&mut self, key_identity: &KeyIdentity) -> Result, String> { if let Err(err) = self.delete_mapping(key_identity) { Err(err.to_string()) } else if let Some(key_info) = self.key_store.remove(key_identity) { Ok(Some(key_info)) } else { Ok(None) } } fn exists(&self, key_identity: &KeyIdentity) -> Result { Ok(self.key_store.contains_key(key_identity)) } } /// SQLiteKeyInfoManager builder #[derive(Debug, Default)] pub struct SQLiteKeyInfoManagerBuilder { database_path: Option, } impl SQLiteKeyInfoManagerBuilder { /// Create a new SQLiteKeyInfoManagerBuilder pub fn new() -> SQLiteKeyInfoManagerBuilder { SQLiteKeyInfoManagerBuilder { database_path: None, } } /// Add a mappings directory path to the builder pub fn with_db_path(mut self, path: PathBuf) -> SQLiteKeyInfoManagerBuilder { self.database_path = Some(path); self } /// Build into a SQLiteKeyInfoManager pub fn build(self) -> Result { SQLiteKeyInfoManager::new( self.database_path .unwrap_or_else(|| PathBuf::from(DEFAULT_DB_PATH)), ) } } #[cfg(test)] mod test { use super::super::{KeyIdentity, KeyInfo, ManageKeyInfo}; use super::SQLiteKeyInfoManager; use crate::key_info_managers::sqlite_manager::FILE_PERMISSION; use crate::key_info_managers::{ApplicationIdentity, ProviderIdentity}; use crate::providers::core::Provider as CoreProvider; #[cfg(feature = "mbed-crypto-provider")] use crate::providers::mbed_crypto::Provider as MbedCryptoProvider; use parsec_interface::operations::psa_algorithm::{ Algorithm, AsymmetricSignature, Hash, SignHash, }; use parsec_interface::operations::psa_key_attributes::{ Attributes, Lifetime, Policy, Type, UsageFlags, }; use parsec_interface::requests::AuthType; use rand::Rng; use std::fs; use std::fs::Permissions; use std::os::unix::fs::PermissionsExt; use std::path::PathBuf; fn test_key_attributes() -> Attributes { Attributes { lifetime: Lifetime::Persistent, key_type: Type::Derive, bits: 1024, policy: Policy { usage_flags: { let mut usage_flags = UsageFlags::default(); let _ = usage_flags.set_sign_hash(); usage_flags }, permitted_algorithms: Algorithm::AsymmetricSignature( AsymmetricSignature::RsaPkcs1v15Sign { hash_alg: SignHash::Specific(Hash::Sha256), }, ), }, } } fn test_key_info() -> KeyInfo { KeyInfo { id: vec![0x11, 0x22, 0x33], attributes: test_key_attributes(), } } fn test_key_info_with_random_id() -> KeyInfo { let mut rng = rand::thread_rng(); KeyInfo { id: vec![rng.gen(), rng.gen(), rng.gen()], attributes: test_key_attributes(), } } #[test] fn insert_get_key_info() { let path = PathBuf::from( env!("OUT_DIR").to_owned() + "/kim/sqlite/insert_get_key_info_mappings.sqlite3", ); fs::remove_file(&path).unwrap_or_default(); let mut manager = SQLiteKeyInfoManager::new(path.clone()).unwrap(); let key_identity = new_key_identity("insert_get_key_info".to_string()); let key_info = test_key_info(); assert!(manager.get(&key_identity).unwrap().is_none()); assert!(manager .insert(key_identity.clone(), key_info.clone()) .unwrap() .is_none()); let stored_key_info = manager .get(&key_identity) .unwrap() .expect("Failed to get key info") .clone(); assert_eq!(stored_key_info, key_info); assert!(manager.remove(&key_identity).unwrap().is_some()); fs::remove_file(&path).unwrap(); } #[test] fn insert_remove_key() { let path = PathBuf::from( env!("OUT_DIR").to_owned() + "/kim/sqlite/insert_remove_key_mappings.sqlite3", ); fs::remove_file(&path).unwrap_or_default(); let mut manager = SQLiteKeyInfoManager::new(path.clone()).unwrap(); let key_identity = new_key_identity("insert_remove_key".to_string()); let key_info = test_key_info(); let _ = manager.insert(key_identity.clone(), key_info).unwrap(); assert!(manager.remove(&key_identity).unwrap().is_some()); fs::remove_file(&path).unwrap(); } #[test] fn remove_unexisting_key() { let path = PathBuf::from( env!("OUT_DIR").to_owned() + "/kim/sqlite/remove_unexisting_key_mappings.sqlite3", ); fs::remove_file(&path).unwrap_or_default(); let mut manager = SQLiteKeyInfoManager::new(path.clone()).unwrap(); let key_identity = new_key_identity("remove_unexisting_key".to_string()); assert_eq!(manager.remove(&key_identity).unwrap(), None); fs::remove_file(&path).unwrap(); } #[test] fn exists() { let path = PathBuf::from(env!("OUT_DIR").to_owned() + "/kim/sqlite/exists_mappings.sqlite3"); fs::remove_file(&path).unwrap_or_default(); let mut manager = SQLiteKeyInfoManager::new(path.clone()).unwrap(); let key_identity = new_key_identity("exists".to_string()); let key_info = test_key_info(); assert!(!manager.exists(&key_identity).unwrap()); let _ = manager.insert(key_identity.clone(), key_info).unwrap(); assert!(manager.exists(&key_identity).unwrap()); let _ = manager.remove(&key_identity).unwrap(); assert!(!manager.exists(&key_identity).unwrap()); fs::remove_file(&path).unwrap(); } #[test] fn insert_overwrites() { let path = PathBuf::from( env!("OUT_DIR").to_owned() + "/kim/sqlite/insert_overwrites_mappings.sqlite3", ); fs::remove_file(&path).unwrap_or_default(); let mut manager = SQLiteKeyInfoManager::new(path.clone()).unwrap(); let key_identity = new_key_identity("insert_overwrites".to_string()); let key_info_1 = test_key_info(); let key_info_2 = KeyInfo { id: vec![0xaa, 0xbb, 0xcc], attributes: test_key_attributes(), }; let _ = manager.insert(key_identity.clone(), key_info_1).unwrap(); let _ = manager .insert(key_identity.clone(), key_info_2.clone()) .unwrap(); let stored_key_info = manager .get(&key_identity) .unwrap() .expect("Failed to get key info") .clone(); assert_eq!(stored_key_info, key_info_2); assert!(manager.remove(&key_identity).unwrap().is_some()); fs::remove_file(&path).unwrap(); } #[test] fn big_names_ascii() { let path = PathBuf::from( env!("OUT_DIR").to_owned() + "/kim/sqlite/big_names_ascii_mappings.sqlite3", ); fs::remove_file(&path).unwrap_or_default(); let mut manager = SQLiteKeyInfoManager::new(path.clone()).unwrap(); let big_app_name_ascii = " Lorem ipsum dolor sit amet, ei suas viris sea, deleniti repudiare te qui. Natum paulo decore ut nec, ne propriae offendit adipisci has. Eius clita legere mel at, ei vis minimum tincidunt.".to_string(); let big_key_name_ascii = " Lorem ipsum dolor sit amet, ei suas viris sea, deleniti repudiare te qui. Natum paulo decore ut nec, ne propriae offendit adipisci has. Eius clita legere mel at, ei vis minimum tincidunt.".to_string(); let key_identity = KeyIdentity::new( ApplicationIdentity::new(big_app_name_ascii, AuthType::NoAuth), ProviderIdentity::new( CoreProvider::PROVIDER_UUID.to_string(), CoreProvider::DEFAULT_PROVIDER_NAME.to_string(), ), big_key_name_ascii, ); let key_info = test_key_info(); let _ = manager .insert(key_identity.clone(), key_info.clone()) .unwrap(); assert_eq!(manager.remove(&key_identity).unwrap().unwrap(), key_info); fs::remove_file(&path).unwrap(); } #[test] fn big_names_emoticons() { let path = PathBuf::from( env!("OUT_DIR").to_owned() + "/kim/sqlite/big_names_emoticons_mappings.sqlite3", ); fs::remove_file(&path).unwrap_or_default(); let mut manager = SQLiteKeyInfoManager::new(path.clone()).unwrap(); let big_app_name_emoticons = "😀😁😂😃😄😅😆😇😈😉😊😋😌😍😎😏😐😑😒😓😔😕😖😗😘😙😚😛😜😝😞😟😠😡😢😣😤😥😦😧😨😩😪😫😬😭😮".to_string(); let big_key_name_emoticons = "😀😁😂😃😄😅😆😇😈😉😊😋😌😍😎😏😐😑😒😓😔😕😖😗😘😙😚😛😜😝😞😟😠😡😢😣😤😥😦😧😨😩😪😫😬😭😮".to_string(); let key_identity = KeyIdentity::new( ApplicationIdentity::new(big_app_name_emoticons, AuthType::NoAuth), ProviderIdentity::new( CoreProvider::PROVIDER_UUID.to_string(), CoreProvider::DEFAULT_PROVIDER_NAME.to_string(), ), big_key_name_emoticons, ); let key_info = test_key_info(); let _ = manager .insert(key_identity.clone(), key_info.clone()) .unwrap(); assert_eq!(manager.remove(&key_identity).unwrap().unwrap(), key_info); fs::remove_file(&path).unwrap(); } /// Test that keys with identical identities (aside from authenticator id) /// produce separate entries. #[test] fn namespace_authenticator_id() { let path = PathBuf::from( env!("OUT_DIR").to_owned() + "/kim/sqlite/namespace_authenticator_id.sqlite3", ); fs::remove_file(&path).unwrap_or_default(); let mut manager = SQLiteKeyInfoManager::new(path.clone()).unwrap(); let key_name = "key_name".to_string(); let app_name = "the_application".to_string(); let key_identity_1 = KeyIdentity::new( ApplicationIdentity::new(app_name.clone(), AuthType::NoAuth), ProviderIdentity::new( CoreProvider::PROVIDER_UUID.to_string(), CoreProvider::DEFAULT_PROVIDER_NAME.to_string(), ), key_name.clone(), ); let key_identity_2 = KeyIdentity::new( ApplicationIdentity::new(app_name, AuthType::Direct), ProviderIdentity::new( CoreProvider::PROVIDER_UUID.to_string(), CoreProvider::DEFAULT_PROVIDER_NAME.to_string(), ), key_name, ); let key_info_1 = test_key_info_with_random_id(); let key_info_2 = test_key_info_with_random_id(); let _ = manager .insert(key_identity_1.clone(), key_info_1.clone()) .unwrap(); let _ = manager .insert(key_identity_2.clone(), key_info_2.clone()) .unwrap(); assert_eq!( manager.remove(&key_identity_1).unwrap().unwrap(), key_info_1 ); assert_eq!( manager.remove(&key_identity_2).unwrap().unwrap(), key_info_2 ); fs::remove_file(&path).unwrap(); } /// Test that keys with identical identities (aside from application name) /// produce separate entries. #[test] fn namespace_application_name() { let path = PathBuf::from( env!("OUT_DIR").to_owned() + "/kim/sqlite/namespace_application_name.sqlite3", ); fs::remove_file(&path).unwrap_or_default(); let mut manager = SQLiteKeyInfoManager::new(path.clone()).unwrap(); let key_name = "key_name".to_string(); let app_name_1 = "application_1".to_string(); let app_name_2 = "application_2".to_string(); let key_identity_1 = KeyIdentity::new( ApplicationIdentity::new(app_name_1, AuthType::NoAuth), ProviderIdentity::new( CoreProvider::PROVIDER_UUID.to_string(), CoreProvider::DEFAULT_PROVIDER_NAME.to_string(), ), key_name.clone(), ); let key_identity_2 = KeyIdentity::new( ApplicationIdentity::new(app_name_2, AuthType::NoAuth), ProviderIdentity::new( CoreProvider::PROVIDER_UUID.to_string(), CoreProvider::DEFAULT_PROVIDER_NAME.to_string(), ), key_name, ); let key_info_1 = test_key_info_with_random_id(); let key_info_2 = test_key_info_with_random_id(); let _ = manager .insert(key_identity_1.clone(), key_info_1.clone()) .unwrap(); let _ = manager .insert(key_identity_2.clone(), key_info_2.clone()) .unwrap(); assert_eq!( manager.remove(&key_identity_1).unwrap().unwrap(), key_info_1 ); assert_eq!( manager.remove(&key_identity_2).unwrap().unwrap(), key_info_2 ); fs::remove_file(&path).unwrap(); } /// Test that keys with identical identities (aside from key name) /// produce separate entries. #[test] fn namespace_key_name() { let path = PathBuf::from(env!("OUT_DIR").to_owned() + "/kim/sqlite/namespace_key_name.sqlite3"); fs::remove_file(&path).unwrap_or_default(); let mut manager = SQLiteKeyInfoManager::new(path.clone()).unwrap(); let key_name_1 = "key_1".to_string(); let key_name_2 = "key_2".to_string(); let app_name = "the_application".to_string(); let key_identity_1 = KeyIdentity::new( ApplicationIdentity::new(app_name.clone(), AuthType::NoAuth), ProviderIdentity::new( CoreProvider::PROVIDER_UUID.to_string(), CoreProvider::DEFAULT_PROVIDER_NAME.to_string(), ), key_name_1, ); let key_identity_2 = KeyIdentity::new( ApplicationIdentity::new(app_name, AuthType::NoAuth), ProviderIdentity::new( CoreProvider::PROVIDER_UUID.to_string(), CoreProvider::DEFAULT_PROVIDER_NAME.to_string(), ), key_name_2, ); let key_info_1 = test_key_info_with_random_id(); let key_info_2 = test_key_info_with_random_id(); let _ = manager .insert(key_identity_1.clone(), key_info_1.clone()) .unwrap(); let _ = manager .insert(key_identity_2.clone(), key_info_2.clone()) .unwrap(); assert_eq!( manager.remove(&key_identity_1).unwrap().unwrap(), key_info_1 ); assert_eq!( manager.remove(&key_identity_2).unwrap().unwrap(), key_info_2 ); fs::remove_file(&path).unwrap(); } /// Test that keys with identical identities (aside from provider name) /// produce the same entry. #[test] fn namespace_provider_name() { let path = PathBuf::from( env!("OUT_DIR").to_owned() + "/kim/sqlite/namespace_provider_name.sqlite3", ); fs::remove_file(&path).unwrap_or_default(); let mut manager = SQLiteKeyInfoManager::new(path.clone()).unwrap(); let key_name = "key_name".to_string(); let app_name = "the_application".to_string(); let key_identity_1 = KeyIdentity::new( ApplicationIdentity::new(app_name.clone(), AuthType::NoAuth), ProviderIdentity::new( CoreProvider::PROVIDER_UUID.to_string(), "One provider name".to_string(), ), key_name.clone(), ); let key_identity_2 = KeyIdentity::new( ApplicationIdentity::new(app_name, AuthType::NoAuth), ProviderIdentity::new( CoreProvider::PROVIDER_UUID.to_string(), "Another provider name".to_string(), ), key_name, ); let key_info = test_key_info_with_random_id(); let _ = manager.insert(key_identity_1, key_info.clone()).unwrap(); assert_eq!(manager.remove(&key_identity_2).unwrap().unwrap(), key_info); fs::remove_file(&path).unwrap(); } /// Test that keys with identical identities (aside from provider name) /// produce the same entry. #[test] fn namespace_provider_uuid() { let path = PathBuf::from( env!("OUT_DIR").to_owned() + "/kim/sqlite/namespace_provider_uuid.sqlite3", ); fs::remove_file(&path).unwrap_or_default(); let mut manager = SQLiteKeyInfoManager::new(path.clone()).unwrap(); let key_name = "key_name".to_string(); let app_name = "the_application".to_string(); let key_identity_1 = KeyIdentity::new( ApplicationIdentity::new(app_name.clone(), AuthType::NoAuth), ProviderIdentity::new( "some-random-uuid".to_string(), CoreProvider::DEFAULT_PROVIDER_NAME.to_string(), ), key_name.clone(), ); let key_identity_2 = KeyIdentity::new( ApplicationIdentity::new(app_name, AuthType::NoAuth), ProviderIdentity::new( "some-random-uuid-that-isn't-the-same".to_string(), CoreProvider::DEFAULT_PROVIDER_NAME.to_string(), ), key_name, ); let key_info = test_key_info_with_random_id(); let _ = manager.insert(key_identity_1, key_info.clone()).unwrap(); assert_eq!(manager.remove(&key_identity_2).unwrap().unwrap(), key_info); fs::remove_file(&path).unwrap(); } #[cfg(feature = "mbed-crypto-provider")] #[test] fn create_and_load() { let path = PathBuf::from( env!("OUT_DIR").to_owned() + "/kim/sqlite/create_and_load_mappings.sqlite3", ); fs::remove_file(&path).unwrap_or_default(); let app_name1 = "😀 Application One 😀".to_string(); let key_name1 = "😀 Key One 😀".to_string(); let key_identity_1 = KeyIdentity::new( ApplicationIdentity::new(app_name1, AuthType::NoAuth), ProviderIdentity::new( CoreProvider::PROVIDER_UUID.to_string(), CoreProvider::DEFAULT_PROVIDER_NAME.to_string(), ), key_name1, ); let key_info1 = test_key_info(); let app_name2 = "😇 Application Two 😇".to_string(); let key_name2 = "😇 Key Two 😇".to_string(); let key_identity_2 = KeyIdentity::new( ApplicationIdentity::new(app_name2, AuthType::NoAuth), ProviderIdentity::new( MbedCryptoProvider::PROVIDER_UUID.to_string(), MbedCryptoProvider::DEFAULT_PROVIDER_NAME.to_string(), ), key_name2, ); let key_info2 = KeyInfo { id: vec![0x12, 0x22, 0x32], attributes: test_key_attributes(), }; let app_name3 = "😈 Application Three 😈".to_string(); let key_name3 = "😈 Key Three 😈".to_string(); let key_identity_3 = KeyIdentity::new( ApplicationIdentity::new(app_name3, AuthType::NoAuth), ProviderIdentity::new( CoreProvider::PROVIDER_UUID.to_string(), CoreProvider::DEFAULT_PROVIDER_NAME.to_string(), ), key_name3, ); let key_info3 = KeyInfo { id: vec![0x13, 0x23, 0x33], attributes: test_key_attributes(), }; { let mut manager = SQLiteKeyInfoManager::new(path.clone()).unwrap(); let _ = manager .insert(key_identity_1.clone(), key_info1.clone()) .unwrap(); let _ = manager .insert(key_identity_2.clone(), key_info2.clone()) .unwrap(); let _ = manager .insert(key_identity_3.clone(), key_info3.clone()) .unwrap(); } // The local hashmap is dropped when leaving the inner scope. { let mut manager = SQLiteKeyInfoManager::new(path.clone()).unwrap(); assert_eq!(manager.remove(&key_identity_1).unwrap().unwrap(), key_info1); assert_eq!(manager.remove(&key_identity_2).unwrap().unwrap(), key_info2); assert_eq!(manager.remove(&key_identity_3).unwrap().unwrap(), key_info3); } fs::remove_file(&path).unwrap(); } fn new_key_identity(key_name: String) -> KeyIdentity { KeyIdentity::new( ApplicationIdentity::new("Testing Application 😎".to_string(), AuthType::NoAuth), ProviderIdentity::new( CoreProvider::PROVIDER_UUID.to_string(), CoreProvider::DEFAULT_PROVIDER_NAME.to_string(), ), key_name, ) } #[test] fn check_permissions() { let path = PathBuf::from(env!("OUT_DIR").to_owned() + "/kim/sqlite/check_permissions.sqlite3"); fs::remove_file(&path).unwrap_or_default(); let app_name1 = "App1".to_string(); let key_name1 = "Key1".to_string(); let key_identity_1 = KeyIdentity::new( ApplicationIdentity::new(app_name1, AuthType::NoAuth), ProviderIdentity::new( CoreProvider::PROVIDER_UUID.to_string(), CoreProvider::DEFAULT_PROVIDER_NAME.to_string(), ), key_name1, ); let key_info1 = test_key_info(); let mut manager = SQLiteKeyInfoManager::new(path.clone()).unwrap(); let _ = manager.insert(key_identity_1.clone(), key_info1).unwrap(); let permissions = Permissions::from_mode(FILE_PERMISSION); assert_eq!( fs::metadata(&path).unwrap().permissions().mode() & permissions.mode(), permissions.mode() ); let _ = manager.remove(&key_identity_1).unwrap().unwrap(); fs::remove_file(&path).unwrap(); } } parsec-service-1.3.0/src/lib.rs000064400000000000000000000042721046102023000144500ustar 00000000000000// Copyright 2019 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Parsec service documentation //! //! This is the source code documentation for Parsec (Platform AbstRaction for //! SECurity) service. For a more in-depth guide of the system architecture, //! supported operations and other Parsec-related topics, see our //! [Parsec Book](https://parallaxsecond.github.io/parsec-book/index.html). #![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)] #[allow(unused)] macro_rules! format_error { ($message:expr, $error:expr) => { if crate::utils::GlobalConfig::log_error_details() { log::error!("{}; Error: {}", $message, $error) } else { log::error!("{};", $message) } }; } #[allow(unused)] macro_rules! deprecation_check { ($operation:ident, $warning:expr, $return_flag:expr) => { if let Err(ResponseStatus::DeprecatedPrimitive) = $operation.check_deprecated() { log::warn!("{}", $warning); if $return_flag && !crate::utils::GlobalConfig::allow_deprecated() { return Err(ResponseStatus::DeprecatedPrimitive); } } }; } #[allow(unused)] macro_rules! warn_on_deprecated { ($operation:ident, $warning:expr) => { deprecation_check!($operation, $warning, false); }; } #[allow(unused)] macro_rules! return_on_deprecated { ($operation:ident, $warning:expr) => { deprecation_check!($operation, $warning, true); }; } pub mod authenticators; pub mod back; pub mod front; pub mod key_info_managers; pub mod providers; pub mod utils; parsec-service-1.3.0/src/providers/core/mod.rs000064400000000000000000000270701046102023000174270ustar 00000000000000// Copyright 2019 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Core information source for the service //! //! The core provider acts as a source of information for the Parsec service, //! aiding clients in discovering the capabilities offered by their underlying //! platform. use super::Provide; use crate::authenticators::ApplicationIdentity; use derivative::Derivative; use log::{error, trace}; use parsec_interface::operations::list_providers::Uuid; use parsec_interface::operations::{ delete_client, list_authenticators, list_clients, list_keys, list_opcodes, list_providers, ping, psa_destroy_key, }; use parsec_interface::operations::{ list_authenticators::AuthenticatorInfo, list_keys::KeyInfo, list_providers::ProviderInfo, }; use parsec_interface::requests::{Opcode, ProviderId, ResponseStatus, Result}; use std::collections::{HashMap, HashSet}; use std::io::{Error, ErrorKind}; use std::num::ParseIntError; use std::sync::Arc; const SUPPORTED_OPCODES: [Opcode; 5] = [ Opcode::ListProviders, Opcode::ListOpcodes, Opcode::Ping, Opcode::ListAuthenticators, Opcode::ListKeys, ]; /// Service information provider /// /// The core provider is a non-cryptographic provider tasked with offering /// structured information about the status of the service and the providers /// available. #[derive(Derivative)] #[derivative(Debug)] pub struct Provider { wire_protocol_version_min: u8, wire_protocol_version_maj: u8, provider_info: Vec, provider_opcodes: HashMap>, authenticator_info: Vec, #[derivative(Debug = "ignore")] prov_list: Vec>, } impl Provider { /// The default provider name for cryptoauthlib provider pub const DEFAULT_PROVIDER_NAME: &'static str = "core-provider"; /// The UUID for this provider pub const PROVIDER_UUID: &'static str = "47049873-2a43-4845-9d72-831eab668784"; } impl Provide for Provider { fn list_opcodes(&self, op: list_opcodes::Operation) -> Result { trace!("list_opcodes ingress"); Ok(list_opcodes::Result { opcodes: self .provider_opcodes .get(&op.provider_id) .ok_or(ResponseStatus::ProviderNotRegistered)? .clone(), }) } fn list_providers(&self, _op: list_providers::Operation) -> Result { trace!("list_providers ingress"); Ok(list_providers::Result { providers: self.provider_info.clone(), }) } fn list_authenticators( &self, _op: list_authenticators::Operation, ) -> Result { trace!("list_authenticators ingress"); Ok(list_authenticators::Result { authenticators: self.authenticator_info.clone(), }) } fn list_keys( &self, application_identity: &ApplicationIdentity, _op: list_keys::Operation, ) -> Result { trace!("list_keys ingress"); let mut keys: Vec = Vec::new(); for provider in &self.prov_list { let id = if let Ok((provider_info, _)) = provider.describe() { provider_info.id.to_string() } else { "unknown".to_string() }; let mut result = provider .list_keys(application_identity, _op) .unwrap_or_else(|e| { error!("list_keys failed on provider {} with {}", id, e); list_keys::Result { keys: Vec::new() } }); keys.append(&mut result.keys); } Ok(list_keys::Result { keys }) } fn list_clients(&self, _op: list_clients::Operation) -> Result { trace!("list_clients ingress"); let mut clients: Vec = Vec::new(); for provider in &self.prov_list { let mut result = provider.list_clients(_op).unwrap_or_else(|e| { let id = if let Ok((provider_info, _)) = provider.describe() { provider_info.id.to_string() } else { "unknown".to_string() }; error!("list_clients failed on provider {} with {}", id, e); list_clients::Result { clients: Vec::new(), } }); clients.append(&mut result.clients); } clients.sort(); clients.dedup(); Ok(list_clients::Result { clients }) } fn delete_client( &self, application_identity: &ApplicationIdentity, op: delete_client::Operation, ) -> Result { trace!("delete_client ingress"); let client = op.client; for provider in &self.prov_list { let id = if let Ok((provider_info, _)) = provider.describe() { provider_info.id.to_string() } else { "unknown".to_string() }; // Currently Parsec only stores keys, we delete all of them. let keys = provider .list_keys( &ApplicationIdentity::new( client.clone(), *application_identity.authenticator_id(), ), list_keys::Operation {}, ) .unwrap_or_else(|e| { error!("list_keys failed on provider {} with {}", id, e); list_keys::Result { keys: Vec::new() } }) .keys; for key in keys { let key_name = key.name; let _ = provider .psa_destroy_key( &ApplicationIdentity::new( client.clone(), *application_identity.authenticator_id(), ), psa_destroy_key::Operation { key_name }, ) .unwrap_or_else(|e| { error!("psa_destroy_key failed on provider {} with {}", id, e); psa_destroy_key::Result {} }); } } Ok(delete_client::Result {}) } fn ping(&self, _op: ping::Operation) -> Result { trace!("ping ingress"); let result = ping::Result { wire_protocol_version_maj: self.wire_protocol_version_maj, wire_protocol_version_min: self.wire_protocol_version_min, }; Ok(result) } fn describe(&self) -> Result<(ProviderInfo, HashSet)> { trace!("describe ingress"); unreachable!() } } /// Builder for CoreProvider #[derive(Derivative, Default)] #[derivative(Debug)] pub struct ProviderBuilder { version_maj: Option, version_min: Option, #[derivative(Debug = "ignore")] prov_list: Vec>, #[derivative(Debug = "ignore")] authenticator_info: Vec, } impl ProviderBuilder { /// Create a new CoreProvider builder pub fn new() -> Self { ProviderBuilder { version_maj: None, version_min: None, prov_list: Vec::new(), authenticator_info: Vec::new(), } } /// Add the wire protocol version used by the service pub fn with_wire_protocol_version(mut self, version_min: u8, version_maj: u8) -> Self { self.version_maj = Some(version_maj); self.version_min = Some(version_min); self } /// Add a provider used pub fn with_provider(mut self, provider: Arc) -> Self { self.prov_list.push(provider); self } /// Add the authenticator information pub fn with_authenticator_info(mut self, authenticator_info: AuthenticatorInfo) -> Self { self.authenticator_info.push(authenticator_info); self } /// Build into a CoreProvider pub fn build(self) -> std::io::Result { let mut provider_opcodes = HashMap::new(); let _ = provider_opcodes.insert( ProviderId::Core, SUPPORTED_OPCODES.iter().copied().collect(), ); let mut provider_info_vec = Vec::new(); for provider in &self.prov_list { let (provider_info, opcodes) = provider .describe() .map_err(|_| Error::new(ErrorKind::Other, "Failed to describe provider"))?; let _ = provider_opcodes.insert(provider_info.id, opcodes); provider_info_vec.push(provider_info); } let crate_version: std::result::Result, ParseIntError> = env!("CARGO_PKG_VERSION") .split('.') .map(|v| v.parse()) .collect(); let crate_version = crate_version.map_err(|e| { format_error!("Invalid CARGO_PKG_VERSION format", e); Error::new( ErrorKind::InvalidData, "crate version number has invalid format", ) })?; if crate_version.len() != 3 { return Err(Error::new( ErrorKind::InvalidData, format!( "Invalid CARGO_PKG_VERSION format: expected 3 components, got {}.", crate_version.len() ), )); } provider_info_vec.push(ProviderInfo { // Assigned UUID for this provider: 47049873-2a43-4845-9d72-831eab668784 uuid: Uuid::parse_str("47049873-2a43-4845-9d72-831eab668784").map_err(|_| Error::new( ErrorKind::InvalidData, "provider UUID is invalid", ))?, description: String::from("Software provider that implements only administrative (i.e. no cryptographic) operations"), vendor: String::new(), version_maj: crate_version[0], version_min: crate_version[1], version_rev: crate_version[2], id: ProviderId::Core, }); let core_provider = Provider { wire_protocol_version_maj: self .version_maj .ok_or_else(|| Error::new(ErrorKind::InvalidData, "version maj is missing"))?, wire_protocol_version_min: self .version_min .ok_or_else(|| Error::new(ErrorKind::InvalidData, "version min is missing"))?, provider_opcodes, provider_info: provider_info_vec, authenticator_info: self.authenticator_info, prov_list: self.prov_list, }; Ok(core_provider) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_ping() { let provider = Provider { wire_protocol_version_min: 8, wire_protocol_version_maj: 10, provider_info: Vec::new(), authenticator_info: Vec::new(), provider_opcodes: HashMap::new(), prov_list: Vec::new(), }; let op = ping::Operation {}; let result = provider.ping(op).unwrap(); assert_eq!( result.wire_protocol_version_maj, provider.wire_protocol_version_maj ); assert_eq!( result.wire_protocol_version_min, provider.wire_protocol_version_min ); } #[test] fn test_build() { let provider_builder = ProviderBuilder::new().with_wire_protocol_version(42, 12); assert!( provider_builder.build().is_ok(), "error building a CoreProvider" ) } } parsec-service-1.3.0/src/providers/crypto_capability.rs000064400000000000000000000115321046102023000214350ustar 00000000000000// Copyright 2021 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Crypto capabilities trait for Parsec providers //! //! The trait provides generic crypto compatibility checking methods //! https://parallaxsecond.github.io/parsec-book/parsec_client/operations/can_do_crypto.html //! https://parallaxsecond.github.io/parsec-book/parsec_client/operations/service_api_coverage.html use crate::authenticators::ApplicationIdentity; use log::{info, trace}; use parsec_interface::operations::can_do_crypto; use parsec_interface::operations::can_do_crypto::{CheckType, Operation}; use parsec_interface::operations::psa_algorithm::Algorithm; use parsec_interface::operations::psa_key_attributes::Attributes; use parsec_interface::requests::ResponseStatus::PsaErrorNotSupported; use parsec_interface::requests::Result; /// Provider interface for checking crypto capabilities /// /// Definition of the interface that a provider must expand to /// be correctly checked for crypto compatibility. pub trait CanDoCrypto { /// Check if the crypto operation is supported by provider. /// This method is called by Provide trait and doesn't need to be changed. fn can_do_crypto_main( &self, application_identity: &ApplicationIdentity, op: Operation, ) -> Result { trace!("can_do_crypto_main in CanDoCrypto trait"); let _ = self.can_do_crypto_internal(application_identity, op)?; match op.check_type { CheckType::Generate => self.generate_check(op.attributes), CheckType::Import => self.import_check(op.attributes), CheckType::Use => self.use_check(op.attributes), CheckType::Derive => self.derive_check(op.attributes), } } /// Common checks if an existing key of the key type that defined in the attributes /// and the same length can be used to perform the algorithm in policy.key_algorithm fn use_check(&self, attributes: Attributes) -> Result { trace!("Use check in CanDoCrypto trait"); if attributes.policy.permitted_algorithms == Algorithm::None { info!("No algorithm defined for the operation"); return Err(PsaErrorNotSupported); } if !(attributes.policy.usage_flags.decrypt() || attributes.policy.usage_flags.encrypt() || attributes.policy.usage_flags.sign_hash() || attributes.policy.usage_flags.sign_message() || attributes.policy.usage_flags.verify_hash() || attributes.policy.usage_flags.verify_message()) { info!("No usage flags defined for the operation"); return Err(PsaErrorNotSupported); } attributes .compatible_with_alg(attributes.policy.permitted_algorithms) .map_err(|_| PsaErrorNotSupported)?; self.use_check_internal(attributes) } /// Common checks if a key with the attributes can be generated fn generate_check(&self, attributes: Attributes) -> Result { trace!("Generate check in CanDoCrypto trait"); let _ = self.generate_check_internal(attributes)?; if attributes.policy.permitted_algorithms != Algorithm::None { return self.use_check(attributes); } Ok(can_do_crypto::Result) } /// Common checks if a key with the attributes can be imported. fn import_check(&self, attributes: Attributes) -> Result { trace!("Import check in CanDoCrypto trait"); let _ = self.import_check_internal(attributes)?; if attributes.policy.permitted_algorithms != Algorithm::None { return self.use_check(attributes); } Ok(can_do_crypto::Result) } /// Checks if a key with the attributes can be derived. fn derive_check(&self, _attributes: Attributes) -> Result { info!("Derive check type is not supported"); Err(PsaErrorNotSupported) } /// Provider specific heck if the crypto operation is supported by provider. /// This method should be re-implemented by providers. fn can_do_crypto_internal( &self, _application_identity: &ApplicationIdentity, _op: Operation, ) -> Result; /// Provider specific Use check. /// This method should be re-implemented by providers. fn use_check_internal(&self, _attributes: Attributes) -> Result; /// Provider specific Generate check. /// This method should be re-implemented by providers. fn generate_check_internal(&self, attributes: Attributes) -> Result; /// Provider specific Import check. /// This method should be re-implemented by providers. fn import_check_internal(&self, attributes: Attributes) -> Result; } parsec-service-1.3.0/src/providers/cryptoauthlib/access_keys.rs000064400000000000000000000046311046102023000231030ustar 00000000000000// Copyright 2021 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::Provider; use log::{error, warn}; use serde::Deserialize; use std::fs::read_to_string; use std::path::Path; #[derive(Debug, Deserialize)] struct AccessKeyContainer { access_keys: Vec, } #[derive(Debug, Deserialize)] struct AccessKey { slot: u8, key: [u8; 32], } impl Provider { /// Read access keys from a configuration file and setup the CALib to use them. pub fn set_access_keys( &self, access_keys_file_name: Option, ) -> Option { let access_keys_string = match access_keys_file_name.clone() { None => { warn!("Missing 'access_key_file_name' entry in configuration toml file"); return None; } Some(file_name) => match read_to_string(Path::new(&file_name)) { Err(err) => { warn!("Cannot read from {} file because: {}.", file_name, err); return None; } Ok(config_string) => config_string, }, }; let access_keys_container: AccessKeyContainer = match toml::from_str(&access_keys_string) { Ok(keys) => keys, Err(err) => { error!( "Error parsing access key config file {}. {}", access_keys_file_name.unwrap(), err ); return None; } }; for access_key in access_keys_container.access_keys.iter() { if rust_cryptoauthlib::ATCA_ATECC_SLOTS_COUNT > access_key.slot { let err = self.device.add_access_key(access_key.slot, &access_key.key); match err { rust_cryptoauthlib::AtcaStatus::AtcaSuccess => (), _ => error!( "add_access_key() for slot {} failed, because {}", access_key.slot, err ), } } else { error!( "add_access_key() for slot {} failed, because it exceeded total slots count {}", access_key.slot, rust_cryptoauthlib::ATCA_ATECC_SLOTS_COUNT ) } } Some(rust_cryptoauthlib::AtcaStatus::AtcaSuccess) } } parsec-service-1.3.0/src/providers/cryptoauthlib/aead.rs000064400000000000000000000160271046102023000215030ustar 00000000000000// Copyright 2021 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 // CAL supports CCM with: // - tag lenght must be <4,16> and must be even number // - nonce lenght must be <7,13> // CAL supports GCM with: // - tag lenght must be <12,16> // - nonce lenght must be <7,13> use super::Provider; use crate::authenticators::ApplicationIdentity; use crate::key_info_managers::KeyIdentity; use log::error; use parsec_interface::operations::psa_algorithm::{Aead, AeadWithDefaultLengthTag}; use parsec_interface::operations::{psa_aead_decrypt, psa_aead_encrypt}; use parsec_interface::requests::{ResponseStatus, Result}; const DEFAULT_TAG_LENGTH: usize = 16; pub fn get_tag_length(alg: &Aead) -> Option { match alg { Aead::AeadWithDefaultLengthTag(AeadWithDefaultLengthTag::Ccm) => Some(DEFAULT_TAG_LENGTH), Aead::AeadWithDefaultLengthTag(AeadWithDefaultLengthTag::Gcm) => Some(DEFAULT_TAG_LENGTH), Aead::AeadWithShortenedTag { aead_alg: AeadWithDefaultLengthTag::Ccm, tag_length, } => Some(*tag_length), Aead::AeadWithShortenedTag { aead_alg: AeadWithDefaultLengthTag::Gcm, tag_length, } => Some(*tag_length), _ => None, } } pub fn is_ccm_selected(alg: &Aead) -> bool { matches!( alg, Aead::AeadWithDefaultLengthTag(AeadWithDefaultLengthTag::Ccm) | Aead::AeadWithShortenedTag { aead_alg: AeadWithDefaultLengthTag::Ccm, .. } ) } pub fn get_aead_algorithm( aead_params: rust_cryptoauthlib::AeadParam, alg: &Aead, ) -> rust_cryptoauthlib::AeadAlgorithm { if is_ccm_selected(alg) { rust_cryptoauthlib::AeadAlgorithm::Ccm(aead_params) } else { rust_cryptoauthlib::AeadAlgorithm::Gcm(aead_params) } } impl Provider { pub(super) fn psa_aead_encrypt_internal( &self, application_identity: &ApplicationIdentity, op: psa_aead_encrypt::Operation, ) -> Result { match get_tag_length(&op.alg) { Some(tag_length) => { let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), op.key_name.clone(), ); let key_id = self.key_info_store.get_key_id::(&key_identity)?; let key_attributes = self.key_info_store.get_key_attributes(&key_identity)?; op.validate(key_attributes)?; let aead_param = rust_cryptoauthlib::AeadParam { nonce: op.nonce.to_vec(), tag_length: Some(tag_length as u8), additional_data: Some(op.additional_data.to_vec()), ..Default::default() }; let mut plaintext = op.plaintext.to_vec(); match self.device.aead_encrypt( get_aead_algorithm(aead_param, &op.alg), key_id, &mut plaintext, ) { Ok(tag) => { plaintext.extend(tag); Ok(psa_aead_encrypt::Result { ciphertext: plaintext.into(), }) } Err(error) => { error!("aead_encrypt failed CAL error {}.", error); match error { rust_cryptoauthlib::AtcaStatus::AtcaInvalidSize | rust_cryptoauthlib::AtcaStatus::AtcaInvalidId | rust_cryptoauthlib::AtcaStatus::AtcaBadParam => { Err(ResponseStatus::PsaErrorInvalidArgument) } _ => Err(ResponseStatus::PsaErrorGenericError), } } } } None => { error!("aead_encrypt failed, algorithm not supported"); Err(ResponseStatus::PsaErrorNotSupported) } } } pub(super) fn psa_aead_decrypt_internal( &self, application_identity: &ApplicationIdentity, op: psa_aead_decrypt::Operation, ) -> Result { match get_tag_length(&op.alg) { Some(tag_length) => { let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), op.key_name.clone(), ); let key_id = self.key_info_store.get_key_id::(&key_identity)?; let key_attributes = self.key_info_store.get_key_attributes(&key_identity)?; op.validate(key_attributes)?; if tag_length < op.ciphertext.len() { let mut ciphertext = op.ciphertext.to_vec(); let tag = ciphertext.split_off(ciphertext.len() - tag_length); let aead_param = rust_cryptoauthlib::AeadParam { nonce: op.nonce.to_vec(), tag: Some(tag), additional_data: Some(op.additional_data.to_vec()), ..Default::default() }; match self.device.aead_decrypt( get_aead_algorithm(aead_param, &op.alg), key_id, &mut ciphertext, ) { Ok(true) => Ok(psa_aead_decrypt::Result { plaintext: ciphertext.into(), }), Ok(false) => { error!("aead_decrypt authentication failed"); Err(ResponseStatus::PsaErrorInvalidSignature) } Err(error) => { error!("aead_decrypt error {}", error); match error { rust_cryptoauthlib::AtcaStatus::AtcaInvalidSize | rust_cryptoauthlib::AtcaStatus::AtcaInvalidId | rust_cryptoauthlib::AtcaStatus::AtcaBadParam => { Err(ResponseStatus::PsaErrorInvalidArgument) } _ => Err(ResponseStatus::PsaErrorGenericError), } } } } else { error!( "aead_decrypt failed, tag lenght {} longer then ciphertext {}", tag_length, op.ciphertext.len() ); Err(ResponseStatus::PsaErrorInvalidArgument) } } None => { error!("aead_decrypt failed, algorithm not supported"); Err(ResponseStatus::PsaErrorNotSupported) } } } } parsec-service-1.3.0/src/providers/cryptoauthlib/asym_sign.rs000064400000000000000000000164421046102023000226030ustar 00000000000000// Copyright 2021 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::Provider; use crate::authenticators::ApplicationIdentity; use crate::key_info_managers::KeyIdentity; use log::error; use parsec_interface::operations::psa_algorithm::{AsymmetricSignature, Hash, SignHash}; use parsec_interface::operations::psa_key_attributes::{EccFamily, Type}; use parsec_interface::operations::{ psa_sign_hash, psa_sign_message, psa_verify_hash, psa_verify_message, }; use parsec_interface::requests::{ResponseStatus, Result}; use rust_cryptoauthlib::AtcaStatus; impl Provider { fn ecdsa_hash_sign(&self, key_id: u8, hash: &[u8]) -> Result { let sign_mode = rust_cryptoauthlib::SignMode::External(hash.to_vec()); let mut signature = vec![0u8; rust_cryptoauthlib::ATCA_SIG_SIZE]; let result = self.device.sign_hash(sign_mode, key_id, &mut signature); match result { AtcaStatus::AtcaSuccess => Ok(psa_sign_hash::Result { signature: signature.into(), }), _ => { error!("Sign hash failed, hardware reported: {}", result); Err(ResponseStatus::PsaErrorHardwareFailure) } } } // Get the public key for hash verification. // Either the public key is stored in slot (internal mode) // or it must be calculated from a private key in slot (external mode) fn ecdsa_verify_mode_get( &self, key_id: u8, key_type: Type, ) -> Result { match key_type { Type::EccKeyPair { curve_family: EccFamily::SecpR1, } => { let mut raw_public_key: Vec = Vec::new(); match self.device.get_public_key(key_id, &mut raw_public_key) { AtcaStatus::AtcaSuccess => { Ok(rust_cryptoauthlib::VerifyMode::External(raw_public_key)) } _ => Err(ResponseStatus::PsaErrorHardwareFailure), } } Type::EccPublicKey { curve_family: EccFamily::SecpR1, } => Ok(rust_cryptoauthlib::VerifyMode::Internal(key_id)), _ => Err(ResponseStatus::PsaErrorNotSupported), } } fn ecdsa_hash_verify( &self, verify_mode: rust_cryptoauthlib::VerifyMode, hash: zeroize::Zeroizing>, signature: zeroize::Zeroizing>, ) -> Result { match self.device.verify_hash(verify_mode, &hash, &signature) { Ok(true) => Ok(psa_verify_hash::Result {}), Ok(false) => Err(ResponseStatus::PsaErrorInvalidSignature), Err(status) => { error!("Verify hash failed: {}.", status); match status { AtcaStatus::AtcaInvalidSize => Err(ResponseStatus::PsaErrorInvalidSignature), _ => Err(ResponseStatus::PsaErrorHardwareFailure), } } } } pub(super) fn psa_sign_hash_internal( &self, application_identity: &ApplicationIdentity, op: psa_sign_hash::Operation, ) -> Result { let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), op.key_name.clone(), ); let key_attributes = self.key_info_store.get_key_attributes(&key_identity)?; op.validate(key_attributes)?; if op.hash.len() != Hash::Sha256.hash_length() { return Err(ResponseStatus::PsaErrorInvalidArgument); } match op.alg { AsymmetricSignature::Ecdsa { hash_alg: SignHash::Specific(Hash::Sha256), } => { let key_id = self.key_info_store.get_key_id::(&key_identity)?; self.ecdsa_hash_sign(key_id, &op.hash) } _ => Err(ResponseStatus::PsaErrorNotSupported), } } pub(super) fn psa_verify_hash_internal( &self, application_identity: &ApplicationIdentity, op: psa_verify_hash::Operation, ) -> Result { let key_identity = self .key_info_store .get_key_identity(application_identity.clone(), op.key_name.clone()); let key_attributes = self.key_info_store.get_key_attributes(&key_identity)?; op.validate(key_attributes)?; if op.hash.len() != Hash::Sha256.hash_length() { return Err(ResponseStatus::PsaErrorInvalidArgument); } match op.alg { AsymmetricSignature::Ecdsa { hash_alg: SignHash::Specific(Hash::Sha256), } => { let key_id = self.key_info_store.get_key_id::(&key_identity)?; let verify_mode = self.ecdsa_verify_mode_get(key_id, key_attributes.key_type)?; self.ecdsa_hash_verify(verify_mode, op.hash, op.signature) } _ => Err(ResponseStatus::PsaErrorNotSupported), } } pub(super) fn psa_sign_message_internal( &self, application_identity: &ApplicationIdentity, op: psa_sign_message::Operation, ) -> Result { let key_identity = self .key_info_store .get_key_identity(application_identity.clone(), op.key_name.clone()); let key_attributes = self.key_info_store.get_key_attributes(&key_identity)?; op.validate(key_attributes)?; match op.alg { AsymmetricSignature::Ecdsa { hash_alg: SignHash::Specific(Hash::Sha256), } => { let key_id = self.key_info_store.get_key_id::(&key_identity)?; // Compute a hash let hash = self.sha256(&op.message)?.hash; // Sign computed hash let result = self.ecdsa_hash_sign(key_id, &hash)?.signature; Ok(psa_sign_message::Result { signature: result }) } _ => Err(ResponseStatus::PsaErrorNotSupported), } } pub(super) fn psa_verify_message_internal( &self, application_identity: &ApplicationIdentity, op: psa_verify_message::Operation, ) -> Result { let key_identity = self .key_info_store .get_key_identity(application_identity.clone(), op.key_name.clone()); let key_attributes = self.key_info_store.get_key_attributes(&key_identity)?; op.validate(key_attributes)?; match op.alg { AsymmetricSignature::Ecdsa { hash_alg: SignHash::Specific(Hash::Sha256), } => { let key_id = self.key_info_store.get_key_id::(&key_identity)?; // Calculate a hash of a message let hash = self.sha256(&op.message)?.hash; // Determine verify mode let verify_mode = self.ecdsa_verify_mode_get(key_id, key_attributes.key_type)?; // Verify the hash using public key let _ = self.ecdsa_hash_verify(verify_mode, hash, op.signature)?; Ok(psa_verify_message::Result {}) } _ => Err(ResponseStatus::PsaErrorNotSupported), } } } parsec-service-1.3.0/src/providers/cryptoauthlib/cipher.rs000064400000000000000000000137741046102023000220710ustar 00000000000000// Copyright 2021 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::Provider; use crate::authenticators::ApplicationIdentity; use crate::key_info_managers::KeyIdentity; use log::error; use parsec_interface::operations::psa_algorithm::Cipher; use parsec_interface::operations::{psa_cipher_decrypt, psa_cipher_encrypt, psa_generate_random}; use parsec_interface::requests::{ResponseStatus, Result}; use std::convert::TryInto; const CIPHER_IV_SIZE: usize = 16; const CIPHER_CTR_SIZE: u8 = 4; impl Provider { /// Check if given Cipher Algorithm need generated initialization vector to run pub(super) fn algorithm_need_iv(&self, alg: &Cipher) -> bool { !matches!(alg, Cipher::EcbNoPadding) } /// Convert Cipher Algorithm type from parsec to rust_cryptoauthlib type pub(super) fn get_cipher_algorithm( &self, mut cipher_params: rust_cryptoauthlib::CipherParam, alg: &Cipher, ) -> Result { match alg { Cipher::Cfb => Ok(rust_cryptoauthlib::CipherAlgorithm::Cfb(cipher_params)), Cipher::Ctr => { cipher_params.counter_size = Some(CIPHER_CTR_SIZE); Ok(rust_cryptoauthlib::CipherAlgorithm::Ctr(cipher_params)) } Cipher::Ofb => Ok(rust_cryptoauthlib::CipherAlgorithm::Ofb(cipher_params)), Cipher::EcbNoPadding => Ok(rust_cryptoauthlib::CipherAlgorithm::Ecb(cipher_params)), Cipher::CbcNoPadding => Ok(rust_cryptoauthlib::CipherAlgorithm::Cbc(cipher_params)), Cipher::CbcPkcs7 => Ok(rust_cryptoauthlib::CipherAlgorithm::CbcPkcs7(cipher_params)), _ => { error!("Cipher encryption failed: given algorithm is not supported."); Err(ResponseStatus::PsaErrorNotSupported) } } } /// Generate random non-zero initialization vector pub fn generate_iv(&self) -> Result> { let random_op = psa_generate_random::Operation { size: CIPHER_IV_SIZE, }; let random_bytes = self .psa_generate_random_internal(random_op)? .random_bytes .to_vec(); Ok(random_bytes) } pub(super) fn psa_cipher_encrypt_internal( &self, application_identity: &ApplicationIdentity, op: psa_cipher_encrypt::Operation, ) -> Result { let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), op.key_name.clone(), ); let key_attributes = self.key_info_store.get_key_attributes(&key_identity)?; op.validate(key_attributes)?; let mut cipher_param = rust_cryptoauthlib::CipherParam { ..Default::default() }; let mut generated_iv = vec![0u8; 0]; if self.algorithm_need_iv(&op.alg) { generated_iv = self.generate_iv()?; cipher_param.iv = Some(generated_iv[..].try_into()?); } let mut plaintext = op.plaintext.to_vec(); let key_id = self.key_info_store.get_key_id::(&key_identity)?; let result = self.device.cipher_encrypt( self.get_cipher_algorithm(cipher_param, &op.alg)?, key_id, &mut plaintext, ); match result { rust_cryptoauthlib::AtcaStatus::AtcaSuccess => { generated_iv.append(&mut plaintext); let ciphertext = zeroize::Zeroizing::new(generated_iv); Ok(psa_cipher_encrypt::Result { ciphertext }) } rust_cryptoauthlib::AtcaStatus::AtcaInvalidSize | rust_cryptoauthlib::AtcaStatus::AtcaInvalidId | rust_cryptoauthlib::AtcaStatus::AtcaBadParam => { error!("Cipher encryption failed: given plaintext is invalid."); Err(ResponseStatus::PsaErrorInvalidArgument) } _ => Err(ResponseStatus::PsaErrorGenericError), } } pub(super) fn psa_cipher_decrypt_internal( &self, application_identity: &ApplicationIdentity, op: psa_cipher_decrypt::Operation, ) -> Result { let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), op.key_name.clone(), ); let key_attributes = self.key_info_store.get_key_attributes(&key_identity)?; op.validate(key_attributes)?; let mut cipher_param = rust_cryptoauthlib::CipherParam { ..Default::default() }; let mut ciphertext = op.ciphertext.to_vec(); if self.algorithm_need_iv(&op.alg) { if ciphertext.len() < CIPHER_IV_SIZE { error!( "Cipher decryption failed: given ciphertext is too short to contain initialization vector." ); return Err(ResponseStatus::PsaErrorInvalidArgument); } let mut iv = ciphertext; ciphertext = iv.split_off(CIPHER_IV_SIZE); cipher_param.iv = Some(iv[..].try_into()?); } let key_id = self.key_info_store.get_key_id::(&key_identity)?; let result = self.device.cipher_decrypt( self.get_cipher_algorithm(cipher_param, &op.alg)?, key_id, &mut ciphertext, ); match result { rust_cryptoauthlib::AtcaStatus::AtcaSuccess => { let plaintext = zeroize::Zeroizing::new(ciphertext); Ok(psa_cipher_decrypt::Result { plaintext }) } rust_cryptoauthlib::AtcaStatus::AtcaInvalidSize | rust_cryptoauthlib::AtcaStatus::AtcaInvalidId | rust_cryptoauthlib::AtcaStatus::AtcaBadParam => { error!("Cipher decryption failed: given ciphertext is invalid."); Err(ResponseStatus::PsaErrorInvalidArgument) } _ => Err(ResponseStatus::PsaErrorGenericError), } } } parsec-service-1.3.0/src/providers/cryptoauthlib/generate_random.rs000064400000000000000000000044771046102023000237510ustar 00000000000000// Copyright 2021 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::Provider; use parsec_interface::operations::psa_generate_random; use parsec_interface::requests::{ResponseStatus, Result}; impl Provider { pub(super) fn psa_generate_random_internal( &self, op: psa_generate_random::Operation, ) -> Result { let mut random_bytes = vec![0u8; 0]; let zero_vector = vec![0u8, op.size as u8]; let mut loop_count = 0u8; // external loop to retry generation if vector is not secure loop { // calculate internal loop count let call_count = (op.size + rust_cryptoauthlib::ATCA_RANDOM_BUFFER_SIZE - 1) / rust_cryptoauthlib::ATCA_RANDOM_BUFFER_SIZE; // internal loop for vector size greater than buffer size for _i in 0..call_count { let mut buffer = Vec::with_capacity(rust_cryptoauthlib::ATCA_RANDOM_BUFFER_SIZE); let err = self.device.random(&mut buffer); match err { rust_cryptoauthlib::AtcaStatus::AtcaSuccess => { // append buffer vector to result vector random_bytes.append(&mut buffer); } _ => { let error = ResponseStatus::PsaErrorGenericError; format_error!("Bytes generation failed ", err); return Err(error); } } } // end internal loop random_bytes.truncate(op.size); // cut vector to desired size match random_bytes != zero_vector { true => { break Ok(psa_generate_random::Result { random_bytes: random_bytes.into(), }) } false => { loop_count += 1; if loop_count < 3 { continue; } else { let err = ResponseStatus::PsaErrorInsufficientEntropy; format_error!("Bytes generation failed ", err); return Err(err); } } } } // end external loop } } parsec-service-1.3.0/src/providers/cryptoauthlib/hash.rs000064400000000000000000000042721046102023000215330ustar 00000000000000// Copyright 2021 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::Provider; use log::error; use parsec_interface::operations::psa_algorithm::Hash; use parsec_interface::operations::psa_hash_compare; use parsec_interface::operations::psa_hash_compute; use parsec_interface::requests::{ResponseStatus, Result}; impl Provider { /// Calculate SHA2-256 digest for a given message using CALib. /// Ensure proper return value type. pub fn sha256(&self, msg: &[u8]) -> Result { let mut hash = vec![0u8; rust_cryptoauthlib::ATCA_SHA2_256_DIGEST_SIZE]; let result = self.device.sha(msg.to_vec(), &mut hash); match result { rust_cryptoauthlib::AtcaStatus::AtcaSuccess => { Ok(psa_hash_compute::Result { hash: hash.into() }) } _ => { error!("Hash compute failed, hardware reported: {}.", result); Err(ResponseStatus::PsaErrorHardwareFailure) } } } pub(super) fn psa_hash_compute_internal( &self, op: psa_hash_compute::Operation, ) -> Result { match op.alg { Hash::Sha256 => self.sha256(&op.input), _ => Err(ResponseStatus::PsaErrorNotSupported), } } pub(super) fn psa_hash_compare_internal( &self, op: psa_hash_compare::Operation, ) -> Result { // check hash length if op.hash.len() != Hash::Sha256.hash_length() { error!("Invalid input hash length."); return Err(ResponseStatus::PsaErrorInvalidArgument); } match op.alg { Hash::Sha256 => { // compute hash let hash = self.sha256(&op.input)?.hash; // compare input vs. computed hash if op.hash != hash { error!("Hash comparison failed."); Err(ResponseStatus::PsaErrorInvalidSignature) } else { Ok(psa_hash_compare::Result) } } _ => Err(ResponseStatus::PsaErrorNotSupported), } } } parsec-service-1.3.0/src/providers/cryptoauthlib/key_agreement.rs000064400000000000000000000042031046102023000234210ustar 00000000000000// Copyright 2022 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::Provider; use crate::authenticators::ApplicationIdentity; use crate::key_info_managers::KeyIdentity; use parsec_interface::operations::psa_algorithm::RawKeyAgreement; use parsec_interface::operations::psa_raw_key_agreement; use parsec_interface::requests::{ResponseStatus, Result}; use parsec_interface::secrecy::Secret; impl Provider { pub(super) fn psa_raw_key_agreement_internal( &self, application_identity: &ApplicationIdentity, op: psa_raw_key_agreement::Operation, ) -> Result { let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), op.private_key_name.clone(), ); let key_id = self.key_info_store.get_key_id::(&key_identity)?; let key_attributes = self.key_info_store.get_key_attributes(&key_identity)?; op.validate(key_attributes)?; match op.alg { RawKeyAgreement::Ecdh => { let parameters = rust_cryptoauthlib::EcdhParams { out_target: rust_cryptoauthlib::EcdhTarget::Output, slot_id: Some(key_id), ..Default::default() }; let mut key_data = op.peer_key.to_vec(); if key_data.len() == 65 { key_data = op.peer_key[1..].to_vec(); } match self.device.ecdh(parameters, &key_data) { Ok(result) => { let shared_secret = result.pms.unwrap().to_vec(); Ok(psa_raw_key_agreement::Result { shared_secret: Secret::new(shared_secret), }) } Err(status) => { format_error!("Raw key agreement status: ", status); Err(ResponseStatus::PsaErrorGenericError) } } } _ => Err(ResponseStatus::PsaErrorNotSupported), } } } parsec-service-1.3.0/src/providers/cryptoauthlib/key_management.rs000064400000000000000000000372321046102023000235760ustar 00000000000000// Copyright 2021 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::key_slot::KeySlotStatus; use super::Provider; use crate::authenticators::ApplicationIdentity; use log::{error, warn}; use parsec_interface::operations::psa_key_attributes::{Attributes, EccFamily, Type}; use parsec_interface::operations::utils_deprecated_primitives::CheckDeprecated; use parsec_interface::operations::{ psa_destroy_key, psa_export_key, psa_export_public_key, psa_generate_key, psa_import_key, }; use parsec_interface::requests::{Opcode, ResponseStatus, Result}; use parsec_interface::secrecy::{ExposeSecret, Secret}; use zeroize::Zeroizing; impl Provider { pub(super) fn psa_generate_key_internal( &self, application_identity: &ApplicationIdentity, op: psa_generate_key::Operation, ) -> Result { return_on_deprecated!(op, "The key requested to generate is deprecated"); let key_name = op.key_name; let key_identity = self .key_info_store .get_key_identity(application_identity.clone(), key_name); self.key_info_store.does_not_exist(&key_identity)?; if op.attributes.key_type.is_public_key() { error!("A public key type can not be generated."); return Err(ResponseStatus::PsaErrorInvalidArgument); } let key_attributes = op.attributes; let key_type = get_calib_key_type(&key_attributes).map_err(|e| { error!("Failed to get type for key. {}", e); e })?; let slot_id = self .key_slots .find_suitable_slot(&key_attributes, Some(Opcode::PsaGenerateKey)) .map_err(|e| { warn!("Failed to find suitable storage slot for key. {}", e); e })?; // generate key match self.device.gen_key(key_type, slot_id) { rust_cryptoauthlib::AtcaStatus::AtcaSuccess => { match self .key_info_store .insert_key_info(key_identity, &slot_id, key_attributes) { Ok(()) => Ok(psa_generate_key::Result {}), Err(error) => { error!("Insert KeyIdentity to KeyInfoManager failed. {}", error); self.key_slots .set_slot_status(slot_id as usize, KeySlotStatus::Free) .ok() .ok_or(error)?; Err(error) } } } _ => { let error = ResponseStatus::PsaErrorInvalidArgument; error!( "Key generation failed. Trying to update slot status. {}", error ); self.key_slots .set_slot_status(slot_id as usize, KeySlotStatus::Free) .ok() .ok_or(error)?; Err(error) } } } pub(super) fn psa_destroy_key_internal( &self, application_identity: &ApplicationIdentity, op: psa_destroy_key::Operation, ) -> Result { let key_name = op.key_name; let key_identity = self .key_info_store .get_key_identity(application_identity.clone(), key_name); let key_id = self.key_info_store.get_key_id::(&key_identity)?; match self.key_info_store.remove_key_info(&key_identity) { Ok(_) => { match self .key_slots .set_slot_status(key_id as usize, KeySlotStatus::Free) { Ok(()) => (), Err(error) => { warn!("Could not set slot {:?} as free because {}", key_id, error); } } Ok(psa_destroy_key::Result {}) } Err(error) => { warn!("Key {} removal reported : - {}", key_identity, error); Err(error) } } } pub(super) fn psa_import_key_internal( &self, application_identity: &ApplicationIdentity, op: psa_import_key::Operation, ) -> Result { warn_on_deprecated!(op, "The key requested to import is deprecated"); let key_name = op.key_name; let key_identity = self .key_info_store .get_key_identity(application_identity.clone(), key_name); self.key_info_store.does_not_exist(&key_identity)?; let key_attributes = op.attributes; let key_type = get_calib_key_type(&key_attributes).map_err(|e| { error!("Failed to get type for key. {}", e); e })?; let slot_id = self .key_slots .find_suitable_slot(&key_attributes, Some(Opcode::PsaImportKey))?; let key_data = raw_key_extract(key_attributes.key_type, &op.data)?; let atca_error_status = self .device .import_key(key_type, key_data.expose_secret(), slot_id); let psa_error_status: ResponseStatus = match atca_error_status { rust_cryptoauthlib::AtcaStatus::AtcaSuccess => { match self .key_info_store .insert_key_info(key_identity, &slot_id, key_attributes) { Ok(()) => return Ok(psa_import_key::Result {}), Err(error) => { // This is very bad. error!("Insert KeyIdentity to KeyInfoManager failed. {}", error); // Let the function mark the slot as free later on, // just in case things get better somehow. ResponseStatus::PsaErrorStorageFailure } } } rust_cryptoauthlib::AtcaStatus::AtcaInvalidSize | rust_cryptoauthlib::AtcaStatus::AtcaInvalidId | rust_cryptoauthlib::AtcaStatus::AtcaBadParam => { warn!("Key import failed. AtcaStatus: {}", atca_error_status); ResponseStatus::PsaErrorInvalidArgument } _ => { warn!("Key import failed. AtcaStatus: {}", atca_error_status); ResponseStatus::PsaErrorHardwareFailure } }; // Not Ok() match self .key_slots .set_slot_status(slot_id as usize, KeySlotStatus::Free) { Ok(()) => { // Import failed but at least slot was appropriately marked as Free } Err(error) => { // This is very bad error!("Unable to update storage slot status: {}", error); } }; Err(psa_error_status) } pub(super) fn psa_export_public_key_internal( &self, application_identity: &ApplicationIdentity, op: psa_export_public_key::Operation, ) -> Result { let key_identity = self .key_info_store .get_key_identity(application_identity.clone(), op.key_name); let key_attributes = self.key_info_store.get_key_attributes(&key_identity)?; match key_attributes.key_type { Type::EccPublicKey { curve_family: EccFamily::SecpR1, } | Type::EccKeyPair { curve_family: EccFamily::SecpR1, } => { let slot_number = self.key_info_store.get_key_id(&key_identity)?; let mut raw_public_key = Vec::new(); let result = self.device.get_public_key(slot_number, &mut raw_public_key); match result { rust_cryptoauthlib::AtcaStatus::AtcaSuccess => { let public_key = raw_key_wrap(key_attributes.key_type, &Secret::new(raw_public_key))?; Ok(psa_export_public_key::Result { data: Zeroizing::new(public_key.expose_secret().to_vec()), }) } _ => { error!("Export public key from cryptochip. {}", result); Err(ResponseStatus::PsaErrorHardwareFailure) } } } _ => Err(ResponseStatus::PsaErrorInvalidArgument), } } pub(super) fn psa_export_key_internal( &self, application_identity: &ApplicationIdentity, op: psa_export_key::Operation, ) -> Result { let key_identity = self .key_info_store .get_key_identity(application_identity.clone(), op.key_name); let key_attributes = self.key_info_store.get_key_attributes(&key_identity)?; if !key_attributes.is_exportable() { return Err(ResponseStatus::PsaErrorNotPermitted); } let key_type = get_calib_key_type(&key_attributes).map_err(|e| { error!("Failed to get type for key. {}", e); e })?; match key_attributes.key_type { Type::Aes | Type::RawData | Type::EccPublicKey { curve_family: EccFamily::SecpR1, } => { let slot_number = self.key_info_store.get_key_id::(&key_identity)?; let mut raw_key = Vec::new(); let result = self.device.export_key(key_type, &mut raw_key, slot_number); match result { rust_cryptoauthlib::AtcaStatus::AtcaSuccess => { let exported_key = raw_key_wrap(key_attributes.key_type, &Secret::new(raw_key))?; Ok(psa_export_key::Result { data: exported_key }) } _ => { error!("Export key failed, hardware reported: {}", result); Err(ResponseStatus::PsaErrorInvalidArgument) } } } _ => Err(ResponseStatus::PsaErrorInvalidArgument), } } } // Extract a raw key. // This is Parsec -> CALib conversion fn raw_key_extract(key_type: Type, secret: &Secret>) -> Result>> { let mut key = secret.expose_secret().to_vec(); match key_type { Type::Aes | Type::RawData | Type::EccKeyPair { curve_family: EccFamily::SecpR1, } => Ok(Secret::new(key)), Type::EccPublicKey { curve_family: EccFamily::SecpR1, } => match key.len() { // ECC public key length + 1 prefixing octet (0x04): // 512+8 bits == 64+1 octets 65 => { // Get rid of the prefix let raw_public_key: Vec<_> = key.drain(1..).collect(); Ok(Secret::new(raw_public_key)) } _ => Err(ResponseStatus::PsaErrorInvalidArgument), }, _ => Err(ResponseStatus::PsaErrorNotSupported), } } // Wrap the raw key with whatever Parsec wants // This is CALib -> Parsec conversion fn raw_key_wrap(key_type: Type, secret: &Secret>) -> Result>> { let key = secret.expose_secret().to_vec(); match key_type { Type::Aes | Type::RawData => Ok(Secret::new(key)), Type::EccPublicKey { curve_family: EccFamily::SecpR1, } | Type::EccKeyPair { curve_family: EccFamily::SecpR1, } => match key.len() { // No support for EccKeyPair, because cryptochip does not support exporting private key. // But public key can be calculated out of a private one, therefore this function // looks like it supports EccKeyPair. But only a public key. See below. // ECC public key length. 64 => { // Add the prefix let mut wrapped_public_key = vec![0x04]; wrapped_public_key.extend_from_slice(&key); Ok(Secret::new(wrapped_public_key)) } _ => Err(ResponseStatus::PsaErrorInvalidArgument), }, _ => Err(ResponseStatus::PsaErrorNotSupported), } } // Get CryptoAuthLib's key type based on PARSEC's KeyInfoManager type. fn get_calib_key_type(attributes: &Attributes) -> Result { match attributes.key_type { Type::RawData => Ok(rust_cryptoauthlib::KeyType::ShaOrText), Type::Aes => Ok(rust_cryptoauthlib::KeyType::Aes), Type::EccKeyPair { curve_family: EccFamily::SecpR1, } | Type::EccPublicKey { curve_family: EccFamily::SecpR1, } => { if attributes.bits == 256 || attributes.bits == 0 { Ok(rust_cryptoauthlib::KeyType::P256EccKey) } else { Err(ResponseStatus::PsaErrorInvalidArgument) } } _ => Err(ResponseStatus::PsaErrorNotSupported), } } #[test] fn test_extract_raw_ecc_public_key() { let public_ecc_key_array: Secret> = Secret::new( [ 0x04, 0x01, 0xf7, 0x69, 0xe2, 0x40, 0x3a, 0xeb, 0x0d, 0x64, 0x3e, 0x81, 0xb8, 0xda, 0x95, 0xb0, 0x1c, 0x25, 0x80, 0xfe, 0xa3, 0xd3, 0xd0, 0x5b, 0x2f, 0xef, 0x6a, 0x31, 0x9c, 0xa9, 0xca, 0x5d, 0xe5, 0x2b, 0x4b, 0x49, 0x2c, 0x24, 0x2c, 0xef, 0xf4, 0xf2, 0x3c, 0xef, 0xfa, 0x08, 0xa7, 0xb4, 0xc6, 0xe0, 0xce, 0x73, 0xac, 0xd0, 0x69, 0xd4, 0xcc, 0xa8, 0xd0, 0x55, 0xee, 0x6c, 0x65, 0xb5, 0x71, ] .to_vec(), ); let ecc_pub_key: [u8; 64] = [ // 0x04, 0x01, 0xf7, 0x69, 0xe2, 0x40, 0x3a, 0xeb, 0x0d, 0x64, 0x3e, 0x81, 0xb8, 0xda, 0x95, 0xb0, 0x1c, 0x25, 0x80, 0xfe, 0xa3, 0xd3, 0xd0, 0x5b, 0x2f, 0xef, 0x6a, 0x31, 0x9c, 0xa9, 0xca, 0x5d, 0xe5, 0x2b, 0x4b, 0x49, 0x2c, 0x24, 0x2c, 0xef, 0xf4, 0xf2, 0x3c, 0xef, 0xfa, 0x08, 0xa7, 0xb4, 0xc6, 0xe0, 0xce, 0x73, 0xac, 0xd0, 0x69, 0xd4, 0xcc, 0xa8, 0xd0, 0x55, 0xee, 0x6c, 0x65, 0xb5, 0x71, ]; let ecc_pub_key_ext = raw_key_extract( Type::EccPublicKey { curve_family: EccFamily::SecpR1, }, &public_ecc_key_array, ) .unwrap(); assert_eq!( ecc_pub_key.to_vec(), ecc_pub_key_ext.expose_secret().to_owned() ); } #[test] fn test_wrap_raw_ecc_public_key() { let raw_ecc_pub_key: Secret> = Secret::new( [ // 0x04, 0x01, 0xf7, 0x69, 0xe2, 0x40, 0x3a, 0xeb, 0x0d, 0x64, 0x3e, 0x81, 0xb8, 0xda, 0x95, 0xb0, 0x1c, 0x25, 0x80, 0xfe, 0xa3, 0xd3, 0xd0, 0x5b, 0x2f, 0xef, 0x6a, 0x31, 0x9c, 0xa9, 0xca, 0x5d, 0xe5, 0x2b, 0x4b, 0x49, 0x2c, 0x24, 0x2c, 0xef, 0xf4, 0xf2, 0x3c, 0xef, 0xfa, 0x08, 0xa7, 0xb4, 0xc6, 0xe0, 0xce, 0x73, 0xac, 0xd0, 0x69, 0xd4, 0xcc, 0xa8, 0xd0, 0x55, 0xee, 0x6c, 0x65, 0xb5, 0x71, ] .to_vec(), ); let wrapped_ecc_public_key: [u8; 65] = [ 0x04, 0x01, 0xf7, 0x69, 0xe2, 0x40, 0x3a, 0xeb, 0x0d, 0x64, 0x3e, 0x81, 0xb8, 0xda, 0x95, 0xb0, 0x1c, 0x25, 0x80, 0xfe, 0xa3, 0xd3, 0xd0, 0x5b, 0x2f, 0xef, 0x6a, 0x31, 0x9c, 0xa9, 0xca, 0x5d, 0xe5, 0x2b, 0x4b, 0x49, 0x2c, 0x24, 0x2c, 0xef, 0xf4, 0xf2, 0x3c, 0xef, 0xfa, 0x08, 0xa7, 0xb4, 0xc6, 0xe0, 0xce, 0x73, 0xac, 0xd0, 0x69, 0xd4, 0xcc, 0xa8, 0xd0, 0x55, 0xee, 0x6c, 0x65, 0xb5, 0x71, ]; let ecc_public_key = raw_key_wrap( Type::EccPublicKey { curve_family: EccFamily::SecpR1, }, &raw_ecc_pub_key, ) .unwrap(); assert_eq!( wrapped_ecc_public_key.to_vec(), ecc_public_key.expose_secret().to_owned() ); } parsec-service-1.3.0/src/providers/cryptoauthlib/key_slot.rs000064400000000000000000000545441046102023000224500ustar 00000000000000// Copyright 2021 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use parsec_interface::operations::psa_algorithm::{ Aead, AeadWithDefaultLengthTag, Algorithm, AsymmetricSignature, Cipher, FullLengthMac, Hash, KeyAgreement, Mac, RawKeyAgreement, SignHash, }; use parsec_interface::operations::psa_key_attributes::{Attributes, EccFamily, Type}; use parsec_interface::requests::{Opcode, ResponseStatus}; #[derive(Copy, Clone, Debug, PartialEq, Eq)] /// Software status of a ATECC slot pub enum KeySlotStatus { /// Slot is free Free, /// Slot is busy but can be released Busy, /// Slot is busy and cannot be released, because of hardware protection Locked, } #[derive(Copy, Clone, Debug)] /// Hardware slot information pub struct AteccKeySlot { /// Diagnostic field. Number of key identities pointing at this slot pub ref_count: u8, /// Slot status pub status: KeySlotStatus, /// Hardware configuration of a slot pub config: rust_cryptoauthlib::SlotConfig, } impl Default for AteccKeySlot { fn default() -> Self { AteccKeySlot { ref_count: 0u8, status: KeySlotStatus::Free, config: rust_cryptoauthlib::SlotConfig::default(), } } } impl AteccKeySlot { // Check if software key attributes are compatible with hardware slot configuration pub fn key_attr_vs_config( &self, slot: u8, key_attr: &Attributes, op: Option, ) -> Result<(), ResponseStatus> { // (1) Check attributes.key_type if !self.is_key_type_ok(slot, key_attr) { return Err(ResponseStatus::PsaErrorNotSupported); } // (2) Check attributes.policy.usage_flags and slot number if !self.is_usage_flags_ok(key_attr) { return Err(ResponseStatus::PsaErrorNotSupported); } // (3) Check attributes.policy.permitted_algorithms if !self.is_permitted_algorithms_ok(key_attr, op) { return Err(ResponseStatus::PsaErrorNotSupported); } Ok(()) } pub fn set_slot_status(&mut self, status: KeySlotStatus) -> Result<(), ResponseStatus> { if self.status == KeySlotStatus::Locked { return Err(ResponseStatus::PsaErrorNotPermitted); } self.status = match status { KeySlotStatus::Locked => return Err(ResponseStatus::PsaErrorNotPermitted), _ => status, }; Ok(()) } fn is_key_type_ok(&self, slot: u8, key_attr: &Attributes) -> bool { match key_attr.key_type { Type::RawData => self.config.key_type == rust_cryptoauthlib::KeyType::ShaOrText, Type::Hmac => !self.config.no_mac, Type::Aes => self.config.key_type == rust_cryptoauthlib::KeyType::Aes, Type::EccKeyPair { curve_family: EccFamily::SecpR1, } => { // P256 private key has 256 bits (32 bytes). 0 means - do not care. // Only private key is stored - public one can be computed when needed. // The private key can onlly be stored encrypted and the encryption key must be set, // see set_write_encryption_key() call in new(). (key_attr.bits == 0 || key_attr.bits == 256) && self.config.key_type == rust_cryptoauthlib::KeyType::P256EccKey && self.config.ecc_key_attr.is_private && self.config.is_secret } Type::EccPublicKey { curve_family: EccFamily::SecpR1, } => { // The uncompressed public key is 512 bits (64 bytes). // But this is a length of a private key. // First few (7) slots are too short for ECC public key. (key_attr.bits == 0 || key_attr.bits == 256) && self.config.key_type == rust_cryptoauthlib::KeyType::P256EccKey && slot >= rust_cryptoauthlib::ATCA_ATECC_MIN_SLOT_IDX_FOR_PUB_KEY } Type::Derive | Type::DhKeyPair { .. } | Type::DhPublicKey { .. } => { // This may change... false } _ => false, } } fn is_usage_flags_ok(&self, key_attr: &Attributes) -> bool { let mut result = true; if key_attr.policy.usage_flags.export() || key_attr.policy.usage_flags.copy() { result &= match key_attr.key_type { Type::EccKeyPair { .. } => { self.config.key_type == rust_cryptoauthlib::KeyType::P256EccKey && if self.config.ecc_key_attr.is_private { self.config.pub_info } else { true } } _ => true, } } if !result { return result; } if key_attr.policy.usage_flags.sign_hash() || key_attr.policy.usage_flags.sign_message() { result &= self.config.key_type == rust_cryptoauthlib::KeyType::P256EccKey; result &= self.config.ecc_key_attr.is_private; result &= self.config.ecc_key_attr.ext_sign; // The only supported mode result &= matches!(key_attr.key_type, Type::EccKeyPair { .. }); } if !result { return result; } if key_attr.policy.usage_flags.verify_hash() || key_attr.policy.usage_flags.verify_message() { result &= self.config.key_type == rust_cryptoauthlib::KeyType::P256EccKey; result &= match key_attr.key_type { Type::EccKeyPair { .. } => { // `pub_info == true` is relevant when `is_private == true` if self.config.ecc_key_attr.is_private { self.config.pub_info } else { true } } _ => true, }; } result } fn is_permitted_algorithms_ok(&self, key_attr: &Attributes, op: Option) -> bool { match key_attr.policy.permitted_algorithms { // Hash algorithm Algorithm::Hash(Hash::Sha256) => true, // Mac::Hmac algorithm Algorithm::Mac(Mac::Truncated { mac_alg: FullLengthMac::Hmac { hash_alg: Hash::Sha256, }, .. }) | Algorithm::Mac(Mac::FullLength(FullLengthMac::Hmac { hash_alg: Hash::Sha256, })) => { !self.config.no_mac && self.config.key_type != rust_cryptoauthlib::KeyType::P256EccKey && !self.config.ecc_key_attr.is_private } // Mac::CbcMac and Mac::Cmac algorithms Algorithm::Mac(Mac::Truncated { mac_alg: FullLengthMac::CbcMac, .. }) | Algorithm::Mac(Mac::FullLength(FullLengthMac::CbcMac)) | Algorithm::Mac(Mac::Truncated { mac_alg: FullLengthMac::Cmac, .. }) | Algorithm::Mac(Mac::FullLength(FullLengthMac::Cmac)) => { !self.config.no_mac && self.config.key_type == rust_cryptoauthlib::KeyType::Aes } // Cipher Algorithm::Cipher(Cipher::CbcPkcs7) | Algorithm::Cipher(Cipher::CbcNoPadding) | Algorithm::Cipher(Cipher::EcbNoPadding) | Algorithm::Cipher(Cipher::Ctr) | Algorithm::Cipher(Cipher::Cfb) | Algorithm::Cipher(Cipher::Ofb) => { self.config.key_type == rust_cryptoauthlib::KeyType::Aes } // Aead Algorithm::Aead(Aead::AeadWithDefaultLengthTag(AeadWithDefaultLengthTag::Ccm)) | Algorithm::Aead(Aead::AeadWithDefaultLengthTag(AeadWithDefaultLengthTag::Gcm)) | Algorithm::Aead(Aead::AeadWithShortenedTag { aead_alg: AeadWithDefaultLengthTag::Ccm, .. }) | Algorithm::Aead(Aead::AeadWithShortenedTag { aead_alg: AeadWithDefaultLengthTag::Gcm, .. }) => self.config.key_type == rust_cryptoauthlib::KeyType::Aes, // AsymmetricSignature Algorithm::AsymmetricSignature(AsymmetricSignature::Ecdsa { hash_alg: SignHash::Specific(Hash::Sha256), }) => { // Only ECC self.config.key_type == rust_cryptoauthlib::KeyType::P256EccKey && match key_attr.key_type { Type::EccKeyPair { curve_family: EccFamily::SecpR1, } => { // CryptoAuthLib supports using private key (here: pair of keys) for // both signing (directly) and verifying (indirectly). // Up to two WriteConfig values are allowed, depending on operation. self.config.ecc_key_attr.is_private && self.config.ecc_key_attr.ext_sign && match op { Some(opcode) => match opcode { Opcode::PsaImportKey => { self.config.write_config == rust_cryptoauthlib::WriteConfig::Encrypt } Opcode::PsaGenerateKey => { matches!( self.config.write_config, rust_cryptoauthlib::WriteConfig::Encrypt | rust_cryptoauthlib::WriteConfig::Never ) } _ => false, }, None => true, } } Type::EccPublicKey { curve_family: EccFamily::SecpR1, } => { // CryptoAuthLib supports using public key for verifying only. // Using Always is considred unsafe (the key can be read from chip), // but using PubInvalid is not supported by rust-cryptoauthlib 0.3.0 matches!( self.config.write_config, rust_cryptoauthlib::WriteConfig::Encrypt | rust_cryptoauthlib::WriteConfig::Never | rust_cryptoauthlib::WriteConfig::Always ) } _ => false, } } Algorithm::AsymmetricSignature(AsymmetricSignature::DeterministicEcdsa { hash_alg: SignHash::Specific(Hash::Sha256), }) => { // RFC 6979 false } // AsymmetricEncryption Algorithm::AsymmetricEncryption(..) => { // Why only RSA? it could work with ECC... // It could not - no support for ECC encryption in ATECC. false } // KeyAgreement Algorithm::KeyAgreement(KeyAgreement::Raw(RawKeyAgreement::Ecdh)) | Algorithm::KeyAgreement(KeyAgreement::WithKeyDerivation { ka_alg: RawKeyAgreement::Ecdh, .. }) => self.config.key_type == rust_cryptoauthlib::KeyType::P256EccKey, // Nothing else is known to be supported by Atecc _ => false, } } pub fn reference_check_and_set(&mut self) -> Result<(), ()> { if 0 < self.ref_count { Err(()) } else { self.ref_count = 1; Ok(()) } } pub fn is_free(&self) -> bool { matches!(self.status, KeySlotStatus::Free) } } #[cfg(test)] mod tests { use super::*; use parsec_interface::operations::psa_key_attributes::{ Attributes, Lifetime, Policy, Type, UsageFlags, }; use rust_cryptoauthlib::{EccKeyAttr, ReadKey, SlotConfig}; #[test] fn test_is_key_type_ok() { // SlotConfig init // let mut slot_config = rust_cryptoauthlib::SlotConfig::default(); // slot_config.key_type = rust_cryptoauthlib::KeyType::P256EccKey; let slot_config = SlotConfig { write_config: rust_cryptoauthlib::WriteConfig::Always, key_type: rust_cryptoauthlib::KeyType::P256EccKey, read_key: ReadKey { encrypt_read: false, slot_number: 0, }, ecc_key_attr: EccKeyAttr { is_private: false, ext_sign: false, int_sign: false, ecdh_operation: false, ecdh_secret_out: false, }, x509id: 0, auth_key: 0, write_key: 0, is_secret: false, limited_use: false, no_mac: true, persistent_disable: false, req_auth: false, req_random: false, lockable: false, pub_info: false, }; // AteccKeySlot init let mut key_slot = AteccKeySlot { ref_count: 1, status: KeySlotStatus::Busy, config: slot_config, }; // ECC Key Attributes let mut attributes = Attributes { lifetime: Lifetime::Persistent, key_type: Type::EccKeyPair { curve_family: EccFamily::SecpR1, }, bits: 256, policy: Policy { usage_flags: { let mut flags = UsageFlags::default(); let _ = flags.set_sign_hash().set_verify_hash().set_sign_message(); flags }, permitted_algorithms: AsymmetricSignature::DeterministicEcdsa { hash_alg: Hash::Sha256.into(), } .into(), }, }; // KeyType::P256EccKey // Type::EccKeyPair => NOK assert!(!key_slot.is_key_type_ok(0, &attributes)); // private key attrs => OK key_slot.config.key_type = rust_cryptoauthlib::KeyType::P256EccKey; key_slot.config.write_config = rust_cryptoauthlib::WriteConfig::Encrypt; key_slot.config.is_secret = true; key_slot.config.ecc_key_attr.is_private = true; assert!(key_slot.is_key_type_ok(0, &attributes)); // Type::Aes => NOK attributes.key_type = Type::Aes; assert!(!key_slot.is_key_type_ok(0, &attributes)); // Type::RawData => NOK attributes.key_type = Type::RawData; assert!(!key_slot.is_key_type_ok(0, &attributes)); // KeyType::Aes // Type::EccKeyPair => NOK key_slot.config.key_type = rust_cryptoauthlib::KeyType::Aes; attributes.key_type = Type::EccKeyPair { curve_family: EccFamily::SecpR1, }; assert!(!key_slot.is_key_type_ok(0, &attributes)); // Type::Aes => OK attributes.key_type = Type::Aes; assert!(key_slot.is_key_type_ok(0, &attributes)); // Type::RawData => NOK attributes.key_type = Type::RawData; assert!(!key_slot.is_key_type_ok(0, &attributes)); // KeyType::ShaOrText // Type::EccKeyPair => NOK key_slot.config.key_type = rust_cryptoauthlib::KeyType::ShaOrText; attributes.key_type = Type::EccKeyPair { curve_family: EccFamily::SecpR1, }; assert!(!key_slot.is_key_type_ok(0, &attributes)); // Type::Aes => NOK attributes.key_type = Type::Aes; assert!(!key_slot.is_key_type_ok(0, &attributes)); } #[test] fn test_is_usage_flags_ok() { // SlotConfig init let slot_config = SlotConfig { write_config: rust_cryptoauthlib::WriteConfig::Always, key_type: rust_cryptoauthlib::KeyType::P256EccKey, read_key: ReadKey { encrypt_read: false, slot_number: 0, }, ecc_key_attr: EccKeyAttr { is_private: true, ext_sign: true, int_sign: false, ecdh_operation: false, ecdh_secret_out: false, }, x509id: 0, auth_key: 0, write_key: 0, is_secret: false, limited_use: false, no_mac: true, persistent_disable: false, req_auth: false, req_random: false, lockable: false, pub_info: true, }; // AteccKeySlot init let mut key_slot = AteccKeySlot { ref_count: 1, status: KeySlotStatus::Busy, config: slot_config, }; // ECC Key Attributes let mut attributes = Attributes { lifetime: Lifetime::Persistent, key_type: Type::EccKeyPair { curve_family: EccFamily::SecpR1, }, bits: 256, policy: Policy { usage_flags: { let mut flags = UsageFlags::default(); let _ = flags.set_verify_hash().set_export().set_copy(); flags }, permitted_algorithms: AsymmetricSignature::DeterministicEcdsa { hash_alg: Hash::Sha256.into(), } .into(), }, }; // Type::EccKeyPair // && export && copy == true => OK assert!(key_slot.is_usage_flags_ok(&attributes)); // && pub_info == false => OK key_slot.config.pub_info = false; assert!(!key_slot.is_usage_flags_ok(&attributes)); // && pub_info == false => NOK key_slot.config.pub_info = false; assert!(!key_slot.is_usage_flags_ok(&attributes)); // && is_private == false => NOK key_slot.config.ecc_key_attr.is_private = false; assert!(key_slot.is_usage_flags_ok(&attributes)); // && export && copy == false => OK let mut flags = UsageFlags::default(); let _ = flags.set_verify_hash(); attributes.policy.usage_flags = flags; assert!(key_slot.is_usage_flags_ok(&attributes)); // KeyType::Aes => NOK let mut flags = UsageFlags::default(); let _ = flags.set_verify_hash().set_export().set_copy(); attributes.policy.usage_flags = flags; key_slot.config.key_type = rust_cryptoauthlib::KeyType::Aes; assert!(!key_slot.is_usage_flags_ok(&attributes)); // && verify_hash == false => OK attributes.policy.usage_flags = UsageFlags::default(); assert!(key_slot.is_usage_flags_ok(&attributes)); } #[test] fn test_is_permitted_algorithms_ok() { // SlotConfig init let slot_config = SlotConfig { write_config: rust_cryptoauthlib::WriteConfig::Encrypt, key_type: rust_cryptoauthlib::KeyType::P256EccKey, read_key: ReadKey { encrypt_read: false, slot_number: 0, }, ecc_key_attr: EccKeyAttr { is_private: true, ext_sign: true, int_sign: false, ecdh_operation: false, ecdh_secret_out: false, }, x509id: 0, auth_key: 0, write_key: 0, is_secret: true, limited_use: false, no_mac: false, persistent_disable: false, req_auth: false, req_random: false, lockable: false, pub_info: true, }; // AteccKeySlot init let mut key_slot = AteccKeySlot { ref_count: 1, status: KeySlotStatus::Busy, config: slot_config, }; // ECC Key Attributes let mut attributes = Attributes { lifetime: Lifetime::Persistent, key_type: Type::EccPublicKey { curve_family: EccFamily::SecpR1, }, bits: 256, policy: Policy { usage_flags: { let mut flags = UsageFlags::default(); let _ = flags .set_sign_hash() .set_verify_hash() .set_sign_message() .set_export() .set_copy(); flags }, permitted_algorithms: AsymmetricSignature::Ecdsa { hash_alg: Hash::Sha256.into(), } .into(), }, }; // KeyType::P256EccKey // && AsymmetricSignature::Ecdsa => OK assert!(key_slot.is_permitted_algorithms_ok(&attributes, None)); // && FullLengthMac::Hmac => NOK attributes.policy.permitted_algorithms = Mac::FullLength(FullLengthMac::Hmac { hash_alg: Hash::Sha256, }) .into(); assert!(!key_slot.is_permitted_algorithms_ok(&attributes, None)); // && AsymmetricSignature::DeterministicEcdsa => NOK attributes.policy.permitted_algorithms = AsymmetricSignature::DeterministicEcdsa { hash_alg: Hash::Sha256.into(), } .into(); assert!(!key_slot.is_permitted_algorithms_ok(&attributes, None)); // && RawKeyAgreement::Ecdh => OK attributes.policy.permitted_algorithms = KeyAgreement::Raw(RawKeyAgreement::Ecdh).into(); assert!(key_slot.is_permitted_algorithms_ok(&attributes, None)); // KeyType::Aes // && Aead::AeadWithDefaultLengthTag => OK attributes.policy.permitted_algorithms = Aead::AeadWithDefaultLengthTag(AeadWithDefaultLengthTag::Ccm).into(); key_slot.config.key_type = rust_cryptoauthlib::KeyType::Aes; assert!(key_slot.is_permitted_algorithms_ok(&attributes, None)); // && Cipher(Cipher::CbcPkcs7) => OK attributes.policy.permitted_algorithms = Algorithm::Cipher(Cipher::CbcPkcs7); assert!(key_slot.is_permitted_algorithms_ok(&attributes, None)); } } parsec-service-1.3.0/src/providers/cryptoauthlib/key_slot_storage.rs000064400000000000000000000107761046102023000241730ustar 00000000000000// Copyright 2021 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use crate::providers::cryptoauthlib::key_slot::{AteccKeySlot, KeySlotStatus}; use log::warn; use parsec_interface::operations::psa_key_attributes::Attributes; use parsec_interface::requests::{Opcode, ResponseStatus}; use std::sync::RwLock; #[derive(Debug)] pub struct KeySlotStorage { storage: RwLock<[AteccKeySlot; rust_cryptoauthlib::ATCA_ATECC_SLOTS_COUNT as usize]>, } impl KeySlotStorage { pub fn new() -> KeySlotStorage { KeySlotStorage { storage: RwLock::new( [AteccKeySlot::default(); rust_cryptoauthlib::ATCA_ATECC_SLOTS_COUNT as usize], ), } } /// Validate KeyInfo data store entry against hardware. /// Mark slot busy when all checks pass. /// Expected to be called from Provider::new() only. pub fn key_validate_and_mark_busy( &self, key_id: u8, key_attr: &Attributes, ) -> Result, String> { let mut key_slots = self.storage.write().unwrap(); // Get CryptoAuthLibProvider mapping of KeyIdentity to key info and check // (1) if the key info matches ATECC configuration - drop KeyIdentity if not // (2) if there are no two key identities mapping to a single ATECC slot - warning only ATM // check (1) match key_slots[key_id as usize].key_attr_vs_config(key_id, key_attr, None) { Ok(_) => (), Err(err) => { let error = std::format!("ATECC slot configuration mismatch: {}", err); return Err(error); } }; // check(2) match key_slots[key_id as usize].reference_check_and_set() { Ok(_) => (), Err(slot) => { let warning = std::format!("Superfluous reference(s) to ATECC slot {:?}", slot); return Ok(Some(warning)); } }; // Slot 'key_id' validated - trying to mark it busy match key_slots[key_id as usize].set_slot_status(KeySlotStatus::Busy) { Ok(()) => Ok(None), Err(err) => { let error = std::format!("Unable to set hardware slot status: {}", err); Err(error) } } } /// Lock protected per slot hardware configuration setter pub fn set_hw_config(&self, hw_config: &[rust_cryptoauthlib::AtcaSlot]) -> Result<(), String> { // RwLock protection let mut key_slots = self.storage.write().unwrap(); for slot in hw_config.iter().cloned() { if slot.is_valid() { key_slots[slot.id as usize] = AteccKeySlot { ref_count: 0u8, status: { match slot.is_locked { true => KeySlotStatus::Locked, _ => KeySlotStatus::Free, } }, config: slot.config, }; } } Ok(()) } /// Lock protected set slot status wrapper pub fn set_slot_status( &self, slot_id: usize, status: KeySlotStatus, ) -> Result<(), ResponseStatus> { let mut key_slots = self.storage.write().unwrap(); key_slots[slot_id].set_slot_status(status) } /// Iterate through key_slots and find a free one with configuration matching attributes. /// If found, the slot is marked Busy. pub fn find_suitable_slot( &self, key_attr: &Attributes, op: Option, ) -> Result { let mut key_slots = self.storage.write().unwrap(); for slot in 0..rust_cryptoauthlib::ATCA_ATECC_SLOTS_COUNT { if !key_slots[slot as usize].is_free() { continue; } match key_slots[slot as usize].key_attr_vs_config(slot, key_attr, op) { Ok(_) => { match key_slots[slot as usize].set_slot_status(KeySlotStatus::Busy) { Ok(()) => return Ok(slot), Err(err) => { warn!( "find_suitable_slot() - slot {} cannot be marked as busy", slot ); return Err(err); } }; } Err(_) => continue, } } Err(ResponseStatus::PsaErrorInsufficientStorage) } } parsec-service-1.3.0/src/providers/cryptoauthlib/mod.rs000064400000000000000000000604201046102023000213640ustar 00000000000000// Copyright 2021 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Microchip CryptoAuthentication Library provider //! //! This provider implements Parsec operations using CryptoAuthentication //! Library backed by the ATECCx08 cryptochip. use super::Provide; use crate::authenticators::ApplicationIdentity; use crate::key_info_managers::{KeyIdentity, KeyInfoManagerClient}; use crate::providers::cryptoauthlib::key_slot_storage::KeySlotStorage; use crate::providers::ProviderIdentity; use derivative::Derivative; use log::{error, trace, warn}; use parsec_interface::operations::list_providers::ProviderInfo; use parsec_interface::operations::list_providers::Uuid; use parsec_interface::operations::{list_clients, list_keys}; use parsec_interface::requests::{Opcode, ProviderId, ResponseStatus, Result}; use std::collections::HashSet; use std::io::{Error, ErrorKind}; use parsec_interface::operations::{ psa_aead_decrypt, psa_aead_encrypt, psa_cipher_decrypt, psa_cipher_encrypt, psa_destroy_key, psa_export_key, psa_export_public_key, psa_generate_key, psa_generate_random, psa_hash_compare, psa_hash_compute, psa_import_key, psa_raw_key_agreement, psa_sign_hash, psa_sign_message, psa_verify_hash, psa_verify_message, }; mod access_keys; mod aead; mod asym_sign; mod cipher; mod generate_random; mod hash; mod key_agreement; mod key_management; mod key_slot; mod key_slot_storage; /// CryptoAuthLib provider structure #[derive(Derivative)] #[derivative(Debug)] pub struct Provider { #[derivative(Debug = "ignore")] device: rust_cryptoauthlib::AteccDevice, provider_id: ProviderId, // The identity of the provider including uuid & name. provider_identity: ProviderIdentity, #[derivative(Debug = "ignore")] key_info_store: KeyInfoManagerClient, key_slots: KeySlotStorage, supported_opcodes: HashSet, } impl Provider { /// The default provider name for cryptoauthlib provider pub const DEFAULT_PROVIDER_NAME: &'static str = "cryptoauthlib-provider"; /// The UUID for this provider pub const PROVIDER_UUID: &'static str = "b8ba81e2-e9f7-4bdd-b096-a29d0019960c"; /// Creates and initialises an instance of CryptoAuthLibProvider fn new( provider_name: String, key_info_store: KeyInfoManagerClient, atca_iface: rust_cryptoauthlib::AtcaIfaceCfg, access_key_file_name: Option, ) -> Option { // First define communication channel with the device then set it up let device = match rust_cryptoauthlib::setup_atecc_device(atca_iface) { Ok(dev) => dev, Err(err) => { error!("ATECC device initialization failed: {}", err); return None; } }; // ATECC is useful for non-trivial usage only when its configuration is locked if !device.is_configuration_locked() { error!("Error: configuration is not locked."); return None; } let mut cryptoauthlib_provider = Provider { device, provider_id: ProviderId::CryptoAuthLib, provider_identity: ProviderIdentity { name: provider_name, uuid: String::from(Self::PROVIDER_UUID), }, key_info_store, key_slots: KeySlotStorage::new(), supported_opcodes: HashSet::new(), }; // Get the configuration from ATECC... let mut atecc_config_vec = Vec::::new(); let err = cryptoauthlib_provider .device .get_config(&mut atecc_config_vec); if rust_cryptoauthlib::AtcaStatus::AtcaSuccess != err { error!("atecc_get_config failed: {}", err); return None; } // ... and set the key slots configuration as read from hardware if let Err(err) = cryptoauthlib_provider .key_slots .set_hw_config(&atecc_config_vec) { error!("Applying hardware configuration failed: {}", err); return None; } // Validate key info store against hardware configuration. // Delete invalid entries or invalid mappings. // Mark the slots free/busy appropriately. let mut to_remove: Vec = Vec::new(); match cryptoauthlib_provider.key_info_store.get_all() { Ok(key_identities) => { for key_identity in key_identities.iter() { match cryptoauthlib_provider .key_info_store .does_not_exist(key_identity) { Ok(x) => x, Err(err) => { warn!("Error getting the Key ID for KeyIdentity:\n{}\n(error: {}), continuing...", key_identity, err ); to_remove.push(key_identity.clone()); continue; } }; let key_info_id = match cryptoauthlib_provider .key_info_store .get_key_id::(key_identity) { Ok(x) => x, Err(err) => { warn!( "Could not get key info id for KeyIdentity {:?} because {}", key_identity, err ); to_remove.push(key_identity.clone()); continue; } }; let key_info_attributes = match cryptoauthlib_provider .key_info_store .get_key_attributes(key_identity) { Ok(x) => x, Err(err) => { warn!( "Could not get key attributes for KeyIdentity {:?} because {}", key_identity, err ); to_remove.push(key_identity.clone()); continue; } }; match cryptoauthlib_provider .key_slots .key_validate_and_mark_busy(key_info_id, &key_info_attributes) { Ok(None) => (), Ok(Some(warning)) => { warn!("{} for KeyIdentity {:?}", warning, key_identity) } Err(err) => { warn!("{} for KeyIdentity {:?}", err, key_identity); to_remove.push(key_identity.clone()); continue; } } } } Err(err) => { error!("Key Info Manager error: {}", err); return None; } }; for key_identity in to_remove.iter() { if let Err(err) = cryptoauthlib_provider .key_info_store .remove_key_info(key_identity) { error!("Key Info Manager error: {}", err); return None; } } if cryptoauthlib_provider.set_opcodes().is_none() { warn!("Failed to setup opcodes for cryptoauthlib_provider"); } let err = cryptoauthlib_provider.set_access_keys(access_key_file_name); match err { Some(rust_cryptoauthlib::AtcaStatus::AtcaSuccess) => (), _ => { warn!("Unable to set access keys. This is dangerous for a hardware interface."); } } Some(cryptoauthlib_provider) } fn set_opcodes(&mut self) -> Option<()> { match self.device.get_device_type() { rust_cryptoauthlib::AtcaDeviceType::ATECC508A | rust_cryptoauthlib::AtcaDeviceType::ATECC608A | rust_cryptoauthlib::AtcaDeviceType::ATECC108A => { if self.supported_opcodes.insert(Opcode::PsaGenerateKey) && self.supported_opcodes.insert(Opcode::PsaDestroyKey) && self.supported_opcodes.insert(Opcode::PsaHashCompute) && self.supported_opcodes.insert(Opcode::PsaHashCompare) && self.supported_opcodes.insert(Opcode::PsaGenerateRandom) && self.supported_opcodes.insert(Opcode::PsaImportKey) && self.supported_opcodes.insert(Opcode::PsaSignHash) && self.supported_opcodes.insert(Opcode::PsaVerifyHash) && self.supported_opcodes.insert(Opcode::PsaCipherEncrypt) && self.supported_opcodes.insert(Opcode::PsaCipherDecrypt) && self.supported_opcodes.insert(Opcode::PsaSignMessage) && self.supported_opcodes.insert(Opcode::PsaVerifyMessage) && self.supported_opcodes.insert(Opcode::PsaExportPublicKey) && self.supported_opcodes.insert(Opcode::PsaExportKey) && self.supported_opcodes.insert(Opcode::PsaAeadEncrypt) && self.supported_opcodes.insert(Opcode::PsaAeadDecrypt) && self.supported_opcodes.insert(Opcode::PsaRawKeyAgreement) { Some(()) } else { None } } rust_cryptoauthlib::AtcaDeviceType::AtcaTestDevSuccess | rust_cryptoauthlib::AtcaDeviceType::AtcaTestDevFail | rust_cryptoauthlib::AtcaDeviceType::AtcaTestDevFailUnimplemented => { let _ = self.supported_opcodes.insert(Opcode::PsaGenerateRandom); Some(()) } _ => None, } } } impl Provide for Provider { fn describe(&self) -> Result<(ProviderInfo, HashSet)> { trace!("describe ingress"); Ok((ProviderInfo { // Assigned UUID for this provider: b8ba81e2-e9f7-4bdd-b096-a29d0019960c uuid: Uuid::parse_str(Provider::PROVIDER_UUID).or(Err(ResponseStatus::InvalidEncoding))?, description: String::from("User space hardware provider, utilizing MicrochipTech CryptoAuthentication Library for ATECCx08 chips"), vendor: String::from("Arm"), version_maj: 0, version_min: 1, version_rev: 0, id: ProviderId::CryptoAuthLib, }, self.supported_opcodes.iter().copied().collect())) } fn list_keys( &self, application_identity: &ApplicationIdentity, _op: list_keys::Operation, ) -> Result { Ok(list_keys::Result { keys: self.key_info_store.list_keys(application_identity)?, }) } fn list_clients(&self, _op: list_clients::Operation) -> Result { Ok(list_clients::Result { clients: self .key_info_store .list_clients()? .into_iter() .map(|application_identity| application_identity.name().clone()) .collect(), }) } fn psa_hash_compute( &self, op: psa_hash_compute::Operation, ) -> Result { trace!("psa_hash_compute ingress"); if !self.supported_opcodes.contains(&Opcode::PsaHashCompute) { Err(ResponseStatus::PsaErrorNotSupported) } else { self.psa_hash_compute_internal(op) } } fn psa_hash_compare( &self, op: psa_hash_compare::Operation, ) -> Result { trace!("psa_hash_compare ingress"); if !self.supported_opcodes.contains(&Opcode::PsaHashCompare) { Err(ResponseStatus::PsaErrorNotSupported) } else { self.psa_hash_compare_internal(op) } } fn psa_generate_random( &self, op: psa_generate_random::Operation, ) -> Result { trace!("psa_generate_random ingress"); if !self.supported_opcodes.contains(&Opcode::PsaGenerateRandom) { Err(ResponseStatus::PsaErrorNotSupported) } else { self.psa_generate_random_internal(op) } } fn psa_generate_key( &self, application_identity: &ApplicationIdentity, op: psa_generate_key::Operation, ) -> Result { trace!("psa_generate_key ingress"); if !self.supported_opcodes.contains(&Opcode::PsaGenerateKey) { Err(ResponseStatus::PsaErrorNotSupported) } else { self.psa_generate_key_internal(application_identity, op) } } fn psa_destroy_key( &self, application_identity: &ApplicationIdentity, op: psa_destroy_key::Operation, ) -> Result { trace!("psa_destroy_key ingress"); if !self.supported_opcodes.contains(&Opcode::PsaDestroyKey) { Err(ResponseStatus::PsaErrorNotSupported) } else { self.psa_destroy_key_internal(application_identity, op) } } fn psa_import_key( &self, application_identity: &ApplicationIdentity, op: psa_import_key::Operation, ) -> Result { trace!("psa_import_key ingress"); if !self.supported_opcodes.contains(&Opcode::PsaImportKey) { Err(ResponseStatus::PsaErrorNotSupported) } else { self.psa_import_key_internal(application_identity, op) } } fn psa_sign_hash( &self, application_identity: &ApplicationIdentity, op: psa_sign_hash::Operation, ) -> Result { trace!("psa_sign_hash ingress"); if !self.supported_opcodes.contains(&Opcode::PsaSignHash) { Err(ResponseStatus::PsaErrorNotSupported) } else { self.psa_sign_hash_internal(application_identity, op) } } fn psa_verify_hash( &self, application_identity: &ApplicationIdentity, op: psa_verify_hash::Operation, ) -> Result { trace!("psa_verify_hash ingress"); if !self.supported_opcodes.contains(&Opcode::PsaVerifyHash) { Err(ResponseStatus::PsaErrorNotSupported) } else { self.psa_verify_hash_internal(application_identity, op) } } fn psa_cipher_encrypt( &self, application_identity: &ApplicationIdentity, op: psa_cipher_encrypt::Operation, ) -> Result { trace!("psa_cipher_encrypt ingress"); if !self.supported_opcodes.contains(&Opcode::PsaCipherEncrypt) { Err(ResponseStatus::PsaErrorNotSupported) } else { self.psa_cipher_encrypt_internal(application_identity, op) } } fn psa_cipher_decrypt( &self, application_identity: &ApplicationIdentity, op: psa_cipher_decrypt::Operation, ) -> Result { trace!("psa_cipher_decrypt ingress"); if !self.supported_opcodes.contains(&Opcode::PsaCipherDecrypt) { Err(ResponseStatus::PsaErrorNotSupported) } else { self.psa_cipher_decrypt_internal(application_identity, op) } } fn psa_sign_message( &self, application_identity: &ApplicationIdentity, op: psa_sign_message::Operation, ) -> Result { trace!("psa_sign_message ingress"); if !self.supported_opcodes.contains(&Opcode::PsaSignMessage) { Err(ResponseStatus::PsaErrorNotSupported) } else { self.psa_sign_message_internal(application_identity, op) } } fn psa_verify_message( &self, application_identity: &ApplicationIdentity, op: psa_verify_message::Operation, ) -> Result { trace!("psa_verify_message ingress"); if !self.supported_opcodes.contains(&Opcode::PsaVerifyMessage) { Err(ResponseStatus::PsaErrorNotSupported) } else { self.psa_verify_message_internal(application_identity, op) } } fn psa_export_public_key( &self, application_identity: &ApplicationIdentity, op: psa_export_public_key::Operation, ) -> Result { trace!("psa_export_public_key ingress"); if !self.supported_opcodes.contains(&Opcode::PsaExportPublicKey) { Err(ResponseStatus::PsaErrorNotSupported) } else { self.psa_export_public_key_internal(application_identity, op) } } fn psa_export_key( &self, application_identity: &ApplicationIdentity, op: psa_export_key::Operation, ) -> Result { trace!("psa_export_key ingress"); if !self.supported_opcodes.contains(&Opcode::PsaExportKey) { Err(ResponseStatus::PsaErrorNotSupported) } else { self.psa_export_key_internal(application_identity, op) } } fn psa_aead_encrypt( &self, application_identity: &ApplicationIdentity, op: psa_aead_encrypt::Operation, ) -> Result { trace!("psa_aead_encrypt ingress"); if !self.supported_opcodes.contains(&Opcode::PsaAeadEncrypt) { Err(ResponseStatus::PsaErrorNotSupported) } else { self.psa_aead_encrypt_internal(application_identity, op) } } fn psa_aead_decrypt( &self, application_identity: &ApplicationIdentity, op: psa_aead_decrypt::Operation, ) -> Result { trace!("psa_aead_decrypt ingress"); if !self.supported_opcodes.contains(&Opcode::PsaAeadDecrypt) { Err(ResponseStatus::PsaErrorNotSupported) } else { self.psa_aead_decrypt_internal(application_identity, op) } } fn psa_raw_key_agreement( &self, application_identity: &ApplicationIdentity, op: psa_raw_key_agreement::Operation, ) -> Result { trace!("psa_raw_key_agreement ingress"); if !self.supported_opcodes.contains(&Opcode::PsaRawKeyAgreement) { Err(ResponseStatus::PsaErrorNotSupported) } else { self.psa_raw_key_agreement_internal(application_identity, op) } } } /// CryptoAuthentication Library Provider builder #[derive(Default, Derivative)] #[derivative(Debug)] pub struct ProviderBuilder { provider_name: Option, #[derivative(Debug = "ignore")] key_info_store: Option, device_type: Option, iface_type: Option, wake_delay: Option, rx_retries: Option, slave_address: Option, bus: Option, baud: Option, access_key_file_name: Option, } impl ProviderBuilder { /// Create a new CryptoAuthLib builder pub fn new() -> ProviderBuilder { ProviderBuilder { provider_name: None, key_info_store: None, device_type: None, iface_type: None, wake_delay: None, rx_retries: None, slave_address: None, bus: None, baud: None, access_key_file_name: None, } } /// Add a provider name pub fn with_provider_name(mut self, provider_name: String) -> ProviderBuilder { self.provider_name = Some(provider_name); self } /// Add a KeyInfo manager pub fn with_key_info_store(mut self, key_info_store: KeyInfoManagerClient) -> ProviderBuilder { self.key_info_store = Some(key_info_store); self } /// Specify the ATECC device to be used pub fn with_device_type(mut self, device_type: String) -> ProviderBuilder { self.device_type = Some(device_type); self } /// Specify an interface type (expected: "i2c") pub fn with_iface_type(mut self, iface_type: String) -> ProviderBuilder { self.iface_type = Some(iface_type); self } /// Specify a wake delay pub fn with_wake_delay(mut self, wake_delay: Option) -> ProviderBuilder { self.wake_delay = wake_delay; self } /// Specify number of rx retries pub fn with_rx_retries(mut self, rx_retries: Option) -> ProviderBuilder { self.rx_retries = rx_retries; self } /// Specify i2c slave address of ATECC device pub fn with_slave_address(mut self, slave_address: Option) -> ProviderBuilder { self.slave_address = slave_address; self } /// Specify i2c bus for ATECC device pub fn with_bus(mut self, bus: Option) -> ProviderBuilder { self.bus = bus; self } /// Specify i2c baudrate pub fn with_baud(mut self, baud: Option) -> ProviderBuilder { self.baud = baud; self } /// Specify access key file name pub fn with_access_key_file(mut self, access_key_file_name: Option) -> ProviderBuilder { self.access_key_file_name = access_key_file_name; self } /// Attempt to build CryptoAuthLib Provider pub fn build(self) -> std::io::Result { let iface_cfg = match self.iface_type { Some(x) => match x.as_str() { "i2c" => { let atcai2c_iface_cfg = rust_cryptoauthlib::AtcaIfaceI2c::default() .set_slave_address(self.slave_address.ok_or_else(|| { Error::new(ErrorKind::InvalidData, "missing atecc i2c slave address") })?) .set_bus(self.bus.ok_or_else(|| { Error::new(ErrorKind::InvalidData, "missing atecc i2c bus") })?) .set_baud(self.baud.ok_or_else(|| { Error::new(ErrorKind::InvalidData, "missing atecc i2c baud rate") })?); rust_cryptoauthlib::AtcaIfaceCfg::default() .set_iface_type("i2c".to_owned()) .set_devtype(self.device_type.ok_or_else(|| { Error::new(ErrorKind::InvalidData, "missing atecc device type") })?) .set_wake_delay(self.wake_delay.ok_or_else(|| { Error::new(ErrorKind::InvalidData, "missing atecc wake delay") })?) .set_rx_retries(self.rx_retries.ok_or_else(|| { Error::new( ErrorKind::InvalidData, "missing rx retries number for atecc", ) })?) .set_iface( rust_cryptoauthlib::AtcaIface::default().set_atcai2c(atcai2c_iface_cfg), ) } "test-interface" => rust_cryptoauthlib::AtcaIfaceCfg::default() .set_iface_type("test-interface".to_owned()) .set_devtype(self.device_type.ok_or_else(|| { Error::new(ErrorKind::InvalidData, "missing atecc device type") })?), _ => { return Err(Error::new( ErrorKind::InvalidData, "Unsupported inteface type", )) } }, None => return Err(Error::new(ErrorKind::InvalidData, "Missing inteface type")), }; Provider::new( self.provider_name.ok_or_else(|| { std::io::Error::new(std::io::ErrorKind::InvalidData, "missing provider name") })?, self.key_info_store .ok_or_else(|| Error::new(ErrorKind::InvalidData, "missing key info store"))?, iface_cfg, self.access_key_file_name, ) .ok_or_else(|| { Error::new( ErrorKind::InvalidData, "CryptoAuthLib Provider initialization failed", ) }) } } parsec-service-1.3.0/src/providers/mbed_crypto/aead.rs000064400000000000000000000065671046102023000211310ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::Provider; use crate::authenticators::ApplicationIdentity; use crate::key_info_managers::KeyIdentity; use parsec_interface::operations::{psa_aead_decrypt, psa_aead_encrypt}; use parsec_interface::requests::{ResponseStatus, Result}; use psa_crypto::operations::aead; use psa_crypto::types::key; impl Provider { pub(super) fn psa_aead_encrypt_internal( &self, application_identity: &ApplicationIdentity, op: psa_aead_encrypt::Operation, ) -> Result { let key_name = op.key_name.clone(); let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), key_name, ); let key_id = self.key_info_store.get_key_id(&key_identity)?; let _guard = self .key_handle_mutex .lock() .expect("Grabbing key handle mutex failed"); let id = key::Id::from_persistent_key_id(key_id)?; let key_attributes = key::Attributes::from_key_id(id)?; op.validate(key_attributes)?; let alg = op.alg; let buffer_size = key_attributes.aead_encrypt_output_size(alg, op.plaintext.len())?; let mut ciphertext = vec![0u8; buffer_size]; match aead::encrypt( id, alg, &op.nonce, &op.additional_data, &op.plaintext, &mut ciphertext, ) { Ok(output_size) => { ciphertext.resize(output_size, 0); Ok(psa_aead_encrypt::Result { ciphertext: ciphertext.into(), }) } Err(error) => { let error = ResponseStatus::from(error); format_error!("Encrypt status: ", error); Err(error) } } } pub(super) fn psa_aead_decrypt_internal( &self, application_identity: &ApplicationIdentity, op: psa_aead_decrypt::Operation, ) -> Result { let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), op.key_name.clone(), ); let key_id = self.key_info_store.get_key_id(&key_identity)?; let _guard = self .key_handle_mutex .lock() .expect("Grabbing key handle mutex failed"); let id = key::Id::from_persistent_key_id(key_id)?; let key_attributes = key::Attributes::from_key_id(id)?; op.validate(key_attributes)?; let buffer_size = key_attributes.aead_decrypt_output_size(op.alg, op.ciphertext.len())?; let mut plaintext = vec![0u8; buffer_size]; match aead::decrypt( id, op.alg, &op.nonce, &op.additional_data, &op.ciphertext, &mut plaintext, ) { Ok(output_size) => { plaintext.resize(output_size, 0); Ok(psa_aead_decrypt::Result { plaintext: plaintext.into(), }) } Err(error) => { let error = ResponseStatus::from(error); format_error!("Decrypt status: ", error); Err(error) } } } } parsec-service-1.3.0/src/providers/mbed_crypto/asym_encryption.rs000064400000000000000000000065631046102023000234560ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::Provider; use crate::authenticators::ApplicationIdentity; use crate::key_info_managers::KeyIdentity; use parsec_interface::operations::{psa_asymmetric_decrypt, psa_asymmetric_encrypt}; use parsec_interface::requests::{ResponseStatus, Result}; use psa_crypto::operations::asym_encryption; use psa_crypto::types::key; impl Provider { pub(super) fn psa_asymmetric_encrypt_internal( &self, application_identity: &ApplicationIdentity, op: psa_asymmetric_encrypt::Operation, ) -> Result { let key_name = op.key_name.clone(); let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), key_name, ); let key_id = self.key_info_store.get_key_id(&key_identity)?; let _guard = self .key_handle_mutex .lock() .expect("Grabbing key handle mutex failed"); let id = key::Id::from_persistent_key_id(key_id)?; let key_attributes = key::Attributes::from_key_id(id)?; op.validate(key_attributes)?; let salt_buff = op.salt.as_ref().map(|salt| salt.as_slice()); let alg = op.alg; let buffer_size = key_attributes.asymmetric_encrypt_output_size(alg)?; let mut ciphertext = vec![0u8; buffer_size]; match asym_encryption::encrypt(id, alg, &op.plaintext, salt_buff, &mut ciphertext) { Ok(output_size) => { ciphertext.resize(output_size, 0); Ok(psa_asymmetric_encrypt::Result { ciphertext: ciphertext.into(), }) } Err(error) => { let error = ResponseStatus::from(error); format_error!("Encrypt status: ", error); Err(error) } } } pub(super) fn psa_asymmetric_decrypt_internal( &self, application_identity: &ApplicationIdentity, op: psa_asymmetric_decrypt::Operation, ) -> Result { let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), op.key_name.clone(), ); let key_id = self.key_info_store.get_key_id(&key_identity)?; let _guard = self .key_handle_mutex .lock() .expect("Grabbing key handle mutex failed"); let id = key::Id::from_persistent_key_id(key_id)?; let key_attributes = key::Attributes::from_key_id(id)?; op.validate(key_attributes)?; let salt_buff = op.salt.as_ref().map(|salt| salt.as_slice()); let buffer_size = key_attributes.asymmetric_decrypt_output_size(op.alg)?; let mut plaintext = vec![0u8; buffer_size]; match asym_encryption::decrypt(id, op.alg, &op.ciphertext, salt_buff, &mut plaintext) { Ok(output_size) => { plaintext.resize(output_size, 0); Ok(psa_asymmetric_decrypt::Result { plaintext: plaintext.into(), }) } Err(error) => { let error = ResponseStatus::from(error); format_error!("Decrypt status: ", error); Err(error) } } } } parsec-service-1.3.0/src/providers/mbed_crypto/asym_sign.rs000064400000000000000000000056531046102023000222230ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::Provider; use crate::authenticators::ApplicationIdentity; use crate::key_info_managers::KeyIdentity; use parsec_interface::operations::{psa_sign_hash, psa_verify_hash}; use parsec_interface::requests::{ResponseStatus, Result}; use psa_crypto::operations::asym_signature; use psa_crypto::types::key; impl Provider { pub(super) fn psa_sign_hash_internal( &self, application_identity: &ApplicationIdentity, op: psa_sign_hash::Operation, ) -> Result { let key_name = op.key_name.clone(); let alg = op.alg; let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), key_name, ); let key_id = self.key_info_store.get_key_id(&key_identity)?; let _guard = self .key_handle_mutex .lock() .expect("Grabbing key handle mutex failed"); let id = key::Id::from_persistent_key_id(key_id)?; let key_attributes = key::Attributes::from_key_id(id)?; let buffer_size = key_attributes.sign_output_size(alg)?; let mut signature = vec![0u8; buffer_size]; op.validate(key_attributes)?; match asym_signature::sign_hash(id, alg, &(op.hash), &mut signature) { Ok(size) => { signature.resize(size, 0); Ok(psa_sign_hash::Result { signature: signature.into(), }) } Err(error) => { let error = ResponseStatus::from(error); format_error!("Sign status: ", error); Err(error) } } } pub(super) fn psa_verify_hash_internal( &self, application_identity: &ApplicationIdentity, op: psa_verify_hash::Operation, ) -> Result { let key_name = op.key_name.clone(); let alg = op.alg; let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), key_name, ); let key_id = self.key_info_store.get_key_id(&key_identity)?; let _guard = self .key_handle_mutex .lock() .expect("Grabbing key handle mutex failed"); let id = key::Id::from_persistent_key_id(key_id)?; let key_attributes = key::Attributes::from_key_id(id)?; op.validate(key_attributes)?; let id = key::Id::from_persistent_key_id(key_id)?; match asym_signature::verify_hash(id, alg, &(op.hash), &(op.signature)) { Ok(()) => Ok(psa_verify_hash::Result {}), Err(error) => { let error = ResponseStatus::from(error); format_error!("Verify status: ", error); Err(error) } } } } parsec-service-1.3.0/src/providers/mbed_crypto/capability_discovery.rs000064400000000000000000000061551046102023000244400ustar 00000000000000// Copyright 2021 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::Provider; use crate::authenticators::ApplicationIdentity; use crate::providers::crypto_capability::CanDoCrypto; use log::{info, trace}; use parsec_interface::operations::can_do_crypto; use parsec_interface::operations::psa_key_attributes::{Attributes, Type}; use parsec_interface::requests::ResponseStatus::PsaErrorNotSupported; use parsec_interface::requests::Result; impl CanDoCrypto for Provider { fn can_do_crypto_internal( &self, _app_identity: &ApplicationIdentity, op: can_do_crypto::Operation, ) -> Result { trace!("can_do_crypto_internal"); // Check if psa-crypto can convert the attributes into PSA structure // The conversion includes some validity checks. op.attributes.can_convert_into_psa().map_err(|_| { info!("Unsupported key attributes {:?}", op.attributes); PsaErrorNotSupported })?; Ok(can_do_crypto::Result) } fn use_check_internal(&self, attributes: Attributes) -> Result { trace!("use_check_internal"); let _ = Provider::check_key_size(attributes, false).map_err(|_| { info!("Unsupported key size {}", attributes.bits); PsaErrorNotSupported })?; Ok(can_do_crypto::Result) } fn generate_check_internal(&self, attributes: Attributes) -> Result { trace!("generate_check_internal"); let _ = Provider::check_key_size(attributes, false).map_err(|_| { info!("Unsupported key size {}", attributes.bits); PsaErrorNotSupported })?; match attributes.key_type { Type::RsaKeyPair | Type::EccKeyPair { .. } | Type::DhKeyPair { .. } | Type::RawData | Type::Aes | Type::Camellia | Type::Chacha20 => Ok(can_do_crypto::Result), _ => { info!("Unsupported key type {:?}", attributes.key_type); Err(PsaErrorNotSupported) } } } fn import_check_internal(&self, attributes: Attributes) -> Result { trace!("import_check_internal"); let _ = Provider::check_key_size(attributes, true).map_err(|_| { info!("Unsupported key size {}", attributes.bits); PsaErrorNotSupported })?; // We can import public keys and all the types we can generate. match attributes.key_type { Type::RsaPublicKey | Type::EccPublicKey { .. } | Type::DhPublicKey { .. } => { Ok(can_do_crypto::Result) } Type::RsaKeyPair | Type::EccKeyPair { .. } | Type::DhKeyPair { .. } | Type::RawData | Type::Aes | Type::Camellia | Type::Chacha20 => Ok(can_do_crypto::Result), _ => { info!("Unsupported key type {:?}", attributes.key_type); Err(PsaErrorNotSupported) } } } } parsec-service-1.3.0/src/providers/mbed_crypto/generate_random.rs000064400000000000000000000020241046102023000233510ustar 00000000000000use super::Provider; use parsec_interface::operations::psa_generate_random; use parsec_interface::requests::{ResponseStatus, Result}; use psa_crypto::operations::other::generate_random; impl Provider { pub(super) fn psa_generate_random_internal( &self, op: psa_generate_random::Operation, ) -> Result { let buffer_size = op.size; if buffer_size > crate::utils::GlobalConfig::buffer_size_limit() { let error = ResponseStatus::ResponseTooLarge; format_error!("Generate random status", error); return Err(error); } let mut buffer = vec![0u8; buffer_size]; match generate_random(&mut buffer) { Ok(_) => Ok(psa_generate_random::Result { random_bytes: buffer.into(), }), Err(error) => { let error = ResponseStatus::from(error); format_error!("Generate random status", error); Err(error) } } } } parsec-service-1.3.0/src/providers/mbed_crypto/hash.rs000064400000000000000000000025561046102023000211540ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::Provider; use parsec_interface::operations::{psa_hash_compare, psa_hash_compute}; use parsec_interface::requests::{ResponseStatus, Result}; use psa_crypto::operations::hash; impl Provider { pub(super) fn psa_hash_compute_internal( &self, op: psa_hash_compute::Operation, ) -> Result { let mut hash = vec![0u8; op.alg.hash_length()]; match hash::hash_compute(op.alg, &op.input, &mut hash) { Ok(hash_size) => { hash.resize(hash_size, 0); Ok(psa_hash_compute::Result { hash: hash.into() }) } Err(error) => { let error = ResponseStatus::from(error); format_error!("Has compute status: ", error); Err(error) } } } pub(super) fn psa_hash_compare_internal( &self, op: psa_hash_compare::Operation, ) -> Result { match hash::hash_compare(op.alg, &op.input, &op.hash) { Ok(()) => Ok(psa_hash_compare::Result), Err(error) => { let error = ResponseStatus::from(error); format_error!("Hash compare status: ", error); Err(error) } } } } parsec-service-1.3.0/src/providers/mbed_crypto/key_agreement.rs000064400000000000000000000035661046102023000230520ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::Provider; use crate::authenticators::ApplicationIdentity; use crate::key_info_managers::KeyIdentity; use parsec_interface::operations::psa_raw_key_agreement; use parsec_interface::requests::{ResponseStatus, Result}; use parsec_interface::secrecy::Secret; use psa_crypto::operations::key_agreement; use psa_crypto::types::key; impl Provider { pub(super) fn psa_raw_key_agreement( &self, application_identity: &ApplicationIdentity, op: psa_raw_key_agreement::Operation, ) -> Result { let key_name = op.private_key_name.clone(); let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), key_name, ); let key_id = self.key_info_store.get_key_id(&key_identity)?; let _guard = self .key_handle_mutex .lock() .expect("Grabbing key handle mutex failed"); let id = key::Id::from_persistent_key_id(key_id)?; let key_attributes = key::Attributes::from_key_id(id)?; op.validate(key_attributes)?; let buffer_size = key_attributes.raw_key_agreement_output_size(op.alg)?; let mut shared_secret = vec![0u8; buffer_size]; match key_agreement::raw_key_agreement(op.alg, id, &op.peer_key, &mut shared_secret) { Ok(output_size) => { shared_secret.resize(output_size, 0); Ok(psa_raw_key_agreement::Result { shared_secret: Secret::new(shared_secret), }) } Err(error) => { let error = ResponseStatus::from(error); format_error!("Raw key agreement status: ", error); Err(error) } } } } parsec-service-1.3.0/src/providers/mbed_crypto/key_management.rs000064400000000000000000000226661046102023000232210ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::Provider; use crate::authenticators::ApplicationIdentity; use crate::key_info_managers::KeyIdentity; use log::error; use parsec_interface::operations::psa_key_attributes::{Attributes, Type}; use parsec_interface::operations::utils_deprecated_primitives::CheckDeprecated; use parsec_interface::operations::{ psa_destroy_key, psa_export_key, psa_export_public_key, psa_generate_key, psa_import_key, }; use parsec_interface::requests::{ResponseStatus, Result}; use parsec_interface::secrecy::{ExposeSecret, Secret}; use psa_crypto::operations::key_management as psa_crypto_key_management; use psa_crypto::types::key; use std::sync::atomic::{AtomicU32, Ordering::Relaxed}; /// Creates a new PSA Key ID pub fn create_key_id(max_current_id: &AtomicU32) -> Result { // fetch_add adds 1 to the old value and returns the old value, so add 1 to local value for new ID let new_key_id = max_current_id.fetch_add(1, Relaxed) + 1; if new_key_id > key::PSA_KEY_ID_USER_MAX { // If storing key failed and no other keys were created in the mean time, it is safe to // decrement the key counter. max_current_id.store(key::PSA_KEY_ID_USER_MAX, Relaxed); error!( "PSA max key ID limit of {} reached", key::PSA_KEY_ID_USER_MAX ); return Err(ResponseStatus::PsaErrorInsufficientMemory); } Ok(new_key_id) } impl Provider { pub(super) fn psa_generate_key_internal( &self, application_identity: &ApplicationIdentity, op: psa_generate_key::Operation, ) -> Result { return_on_deprecated!(op, "The key requested to generate is deprecated"); let key_name = op.key_name; let key_attributes = Provider::check_key_size(op.attributes, false)?; let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), key_name, ); self.key_info_store.does_not_exist(&key_identity)?; let key_id = create_key_id(&self.id_counter)?; let _guard = self .key_handle_mutex .lock() .expect("Grabbing key handle mutex failed"); match psa_crypto_key_management::generate(key_attributes, Some(key_id)) { Ok(key) => { if let Err(e) = self.key_info_store .insert_key_info(key_identity, &key_id, key_attributes) { // Safe as this thread should be the only one accessing this key yet. if unsafe { psa_crypto_key_management::destroy(key) }.is_err() { error!("Failed to destroy the previously generated key."); } Err(e) } else { Ok(psa_generate_key::Result {}) } } Err(error) => { let error = ResponseStatus::from(error); format_error!("Generate key status: ", error); Err(error) } } } pub(super) fn psa_import_key_internal( &self, application_identity: &ApplicationIdentity, op: psa_import_key::Operation, ) -> Result { warn_on_deprecated!(op, "The key requested to import is deprecated"); let key_name = op.key_name; let key_attributes = Provider::check_key_size(op.attributes, true)?; let key_data = op.data; let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), key_name, ); self.key_info_store.does_not_exist(&key_identity)?; let key_id = create_key_id(&self.id_counter)?; let _guard = self .key_handle_mutex .lock() .expect("Grabbing key handle mutex failed"); match psa_crypto_key_management::import( key_attributes, Some(key_id), key_data.expose_secret(), ) { Ok(key) => { if let Err(e) = self.key_info_store .insert_key_info(key_identity, &key_id, key_attributes) { // Safe as this thread should be the only one accessing this key yet. if unsafe { psa_crypto_key_management::destroy(key) }.is_err() { error!("Failed to destroy the previously imported key."); } Err(e) } else { Ok(psa_import_key::Result {}) } } Err(error) => { let error = ResponseStatus::from(error); format_error!("Import key status: ", error); Err(error) } } } /// Check if key size is correct for the key type pub fn check_key_size(attributes: Attributes, is_import: bool) -> Result { // For some operations like import 0 size is permitted if is_import && attributes.bits == 0 { return Ok(attributes); } match attributes.key_type { Type::RsaKeyPair | Type::RsaPublicKey => match attributes.bits { 1024 | 2048 | 4096 => Ok(attributes), _ => { error!( "Requested RSA key size is not supported ({})", attributes.bits ); Err(ResponseStatus::PsaErrorInvalidArgument) } }, _ => { // We don't (yet?) implement checks for other key types Ok(attributes) } } } pub(super) fn psa_export_public_key_internal( &self, application_identity: &ApplicationIdentity, op: psa_export_public_key::Operation, ) -> Result { let key_name = op.key_name; let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), key_name, ); let key_id = self.key_info_store.get_key_id(&key_identity)?; let _guard = self .key_handle_mutex .lock() .expect("Grabbing key handle mutex failed"); let id = key::Id::from_persistent_key_id(key_id)?; let key_attributes = key::Attributes::from_key_id(id)?; let buffer_size = key_attributes.export_public_key_output_size()?; let mut buffer = vec![0u8; buffer_size]; let export_length = psa_crypto_key_management::export_public(id, &mut buffer)?; buffer.resize(export_length, 0); Ok(psa_export_public_key::Result { data: buffer.into(), }) } pub(super) fn psa_export_key_internal( &self, application_identity: &ApplicationIdentity, op: psa_export_key::Operation, ) -> Result { let key_name = op.key_name; let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), key_name, ); let key_id = self.key_info_store.get_key_id(&key_identity)?; let _guard = self .key_handle_mutex .lock() .expect("Grabbing key handle mutex failed"); let id = key::Id::from_persistent_key_id(key_id)?; let key_attributes = key::Attributes::from_key_id(id)?; let buffer_size = key_attributes.export_key_output_size()?; let mut buffer = vec![0u8; buffer_size]; let export_length = psa_crypto_key_management::export(id, &mut buffer)?; buffer.resize(export_length, 0); Ok(psa_export_key::Result { data: Secret::new(buffer), }) } pub(super) fn psa_destroy_key_internal( &self, application_identity: &ApplicationIdentity, op: psa_destroy_key::Operation, ) -> Result { let key_name = op.key_name; let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), key_name, ); let key_id = self.key_info_store.get_key_id(&key_identity)?; self.key_info_store.remove_key_info(&key_identity)?; let _guard = self .key_handle_mutex .lock() .expect("Grabbing key handle mutex failed"); let destroy_key_status; // Safety: // * at this point the provider has been instantiated so Mbed Crypto has been initialized // * self.key_handle_mutex prevents concurrent accesses // * self.key_slot_semaphore prevents overflowing key slots let id = key::Id::from_persistent_key_id(key_id)?; unsafe { destroy_key_status = psa_crypto_key_management::destroy(id); } match destroy_key_status { Ok(()) => Ok(psa_destroy_key::Result {}), Err(error) => { // In that case we would have a zombie key in the Mbed Crypto backend. The key is // maybe still there but can not be accessible from Parsec anymore. let error = ResponseStatus::from(error); format_error!("Destroy key status: ", error); Err(error) } } } } parsec-service-1.3.0/src/providers/mbed_crypto/mod.rs000064400000000000000000000336141046102023000210070ustar 00000000000000// Copyright 2019 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Mbed Crypto provider //! //! This provider is a software based implementation of PSA Crypto, Mbed Crypto. use super::Provide; use crate::authenticators::ApplicationIdentity; use crate::key_info_managers::{KeyIdentity, KeyInfoManagerClient}; use crate::providers::crypto_capability::CanDoCrypto; use crate::providers::ProviderIdentity; use derivative::Derivative; use log::{error, trace}; use parsec_interface::operations::list_providers::Uuid; use parsec_interface::operations::{ can_do_crypto, psa_aead_decrypt, psa_aead_encrypt, psa_asymmetric_decrypt, psa_asymmetric_encrypt, psa_destroy_key, psa_export_key, psa_export_public_key, psa_generate_key, psa_generate_random, psa_hash_compare, psa_hash_compute, psa_import_key, psa_raw_key_agreement, psa_sign_hash, psa_verify_hash, }; use parsec_interface::operations::{list_clients, list_keys, list_providers::ProviderInfo}; use parsec_interface::requests::{Opcode, ProviderId, ResponseStatus, Result}; use psa_crypto::types::{key, status}; use std::collections::HashSet; use std::io::{Error, ErrorKind}; use std::sync::{ atomic::{AtomicU32, Ordering::Relaxed}, Mutex, }; mod aead; mod asym_encryption; mod asym_sign; mod capability_discovery; mod generate_random; mod hash; mod key_agreement; pub(super) mod key_management; const SUPPORTED_OPCODES: [Opcode; 16] = [ Opcode::PsaGenerateKey, Opcode::PsaDestroyKey, Opcode::PsaSignHash, Opcode::PsaVerifyHash, Opcode::PsaImportKey, Opcode::PsaExportKey, Opcode::PsaExportPublicKey, Opcode::PsaAsymmetricDecrypt, Opcode::PsaAsymmetricEncrypt, Opcode::PsaAeadEncrypt, Opcode::PsaAeadDecrypt, Opcode::PsaHashCompare, Opcode::PsaHashCompute, Opcode::PsaRawKeyAgreement, Opcode::PsaGenerateRandom, Opcode::CanDoCrypto, ]; /// Mbed Crypto provider structure #[derive(Derivative)] #[derivative(Debug)] pub struct Provider { // The identity of the provider including uuid & name. provider_identity: ProviderIdentity, // When calling write on a reference of key_info_store, a type // std::sync::RwLockWriteGuard is returned. We need to use the // dereference operator (*) to access the inner type dyn ManageKeyInfo + Send + Sync and then // reference it to match with the method prototypes. #[derivative(Debug = "ignore")] key_info_store: KeyInfoManagerClient, // Calls to `psa_open_key`, `psa_generate_key` and `psa_destroy_key` are not thread safe - the slot // allocation mechanism in Mbed Crypto can return the same key slot for overlapping calls. // `key_handle_mutex` is use as a way of securing access to said operations among the threads. // This issue tracks progress on fixing the original problem in Mbed Crypto: // https://github.com/ARMmbed/mbed-crypto/issues/266 key_handle_mutex: Mutex<()>, // Holds the highest ID of all keys (including destroyed keys). New keys will receive an ID of // id_counter + 1. Once id_counter reaches the highest allowed ID, no more keys can be created. id_counter: AtomicU32, } impl Provider { /// The default provider name for mbed-crypto provider pub const DEFAULT_PROVIDER_NAME: &'static str = "mbed-crypto-provider"; /// The UUID for this provider pub const PROVIDER_UUID: &'static str = "1c1139dc-ad7c-47dc-ad6b-db6fdb466552"; /// Creates and initialise a new instance of MbedCryptoProvider. /// Checks if there are not more keys stored in the Key Info Manager than in the MbedCryptoProvider and /// if there, delete them. Adds Key IDs currently in use in the local IDs store. /// Returns `None` if the initialisation failed. fn new(provider_name: String, key_info_store: KeyInfoManagerClient) -> Option { // Safety: this function should be called before any of the other Mbed Crypto functions // are. if let Err(error) = psa_crypto::init() { format_error!("Error when initialising Mbed Crypto", error); return None; } let mbed_crypto_provider = Provider { provider_identity: ProviderIdentity { name: provider_name, uuid: String::from(Self::PROVIDER_UUID), }, key_info_store, key_handle_mutex: Mutex::new(()), id_counter: AtomicU32::new(key::PSA_KEY_ID_USER_MIN), }; let mut max_key_id: key::psa_key_id_t = key::PSA_KEY_ID_USER_MIN; { let mut to_remove: Vec = Vec::new(); // Go through all MbedCryptoProvider key identities to key info mappings and check if they are still // present. // Delete those who are not present and add to the local_store the ones present. match mbed_crypto_provider.key_info_store.get_all() { Ok(key_identities) => { for key_identity in key_identities.iter() { let key_id = match mbed_crypto_provider .key_info_store .get_key_id(key_identity) { Ok(key_id) => key_id, Err(response_status) => { error!("Error getting the Key ID for KeyIdentity:\n{}\n(error: {}), continuing...", key_identity, response_status); to_remove.push(key_identity.clone()); continue; } }; match key::Id::from_persistent_key_id(key_id) { Ok(_) => { if key_id > max_key_id { max_key_id = key_id; } } Err(status::Error::InvalidHandle) => { to_remove.push(key_identity.clone()) } Err(e) => { format_error!("Failed to open persistent Mbed Crypto key", e); return None; } }; } } Err(_) => { return None; } }; for key_identity in to_remove.iter() { mbed_crypto_provider .key_info_store .remove_key_info(key_identity) .ok()?; } } mbed_crypto_provider.id_counter.store(max_key_id, Relaxed); Some(mbed_crypto_provider) } } impl Provide for Provider { fn describe(&self) -> Result<(ProviderInfo, HashSet)> { trace!("describe ingress"); Ok((ProviderInfo { // Assigned UUID for this provider: 1c1139dc-ad7c-47dc-ad6b-db6fdb466552 uuid: Uuid::parse_str(Provider::PROVIDER_UUID).or(Err(ResponseStatus::InvalidEncoding))?, description: String::from("User space software provider, based on Mbed Crypto - the reference implementation of the PSA crypto API"), vendor: String::from("Arm"), version_maj: 0, version_min: 1, version_rev: 0, id: ProviderId::MbedCrypto, }, SUPPORTED_OPCODES.iter().copied().collect())) } fn list_keys( &self, application_identity: &ApplicationIdentity, _op: list_keys::Operation, ) -> Result { trace!("list_keys ingress"); Ok(list_keys::Result { keys: self.key_info_store.list_keys(application_identity)?, }) } fn list_clients(&self, _op: list_clients::Operation) -> Result { trace!("list_clients ingress"); Ok(list_clients::Result { clients: self .key_info_store .list_clients()? .into_iter() .map(|application_identity| application_identity.name().clone()) .collect(), }) } fn psa_generate_key( &self, application_identity: &ApplicationIdentity, op: psa_generate_key::Operation, ) -> Result { trace!("psa_generate_key ingress"); self.psa_generate_key_internal(application_identity, op) } fn psa_import_key( &self, application_identity: &ApplicationIdentity, op: psa_import_key::Operation, ) -> Result { trace!("psa_import_key ingress"); self.psa_import_key_internal(application_identity, op) } fn psa_export_public_key( &self, application_identity: &ApplicationIdentity, op: psa_export_public_key::Operation, ) -> Result { trace!("psa_export_public_key ingress"); self.psa_export_public_key_internal(application_identity, op) } fn psa_export_key( &self, application_identity: &ApplicationIdentity, op: psa_export_key::Operation, ) -> Result { trace!("psa_export_key ingress"); self.psa_export_key_internal(application_identity, op) } fn psa_destroy_key( &self, application_identity: &ApplicationIdentity, op: psa_destroy_key::Operation, ) -> Result { trace!("psa_destroy_key ingress"); self.psa_destroy_key_internal(application_identity, op) } fn psa_sign_hash( &self, application_identity: &ApplicationIdentity, op: psa_sign_hash::Operation, ) -> Result { trace!("psa_sign_hash ingress"); self.psa_sign_hash_internal(application_identity, op) } fn psa_verify_hash( &self, application_identity: &ApplicationIdentity, op: psa_verify_hash::Operation, ) -> Result { trace!("psa_verify_hash ingress"); self.psa_verify_hash_internal(application_identity, op) } fn psa_asymmetric_encrypt( &self, application_identity: &ApplicationIdentity, op: psa_asymmetric_encrypt::Operation, ) -> Result { trace!("psa_asymmetric_encrypt ingress"); self.psa_asymmetric_encrypt_internal(application_identity, op) } fn psa_asymmetric_decrypt( &self, application_identity: &ApplicationIdentity, op: psa_asymmetric_decrypt::Operation, ) -> Result { trace!("psa_asymmetric_decrypt ingress"); self.psa_asymmetric_decrypt_internal(application_identity, op) } fn psa_aead_encrypt( &self, application_identity: &ApplicationIdentity, op: psa_aead_encrypt::Operation, ) -> Result { trace!("psa_aead_encrypt ingress"); self.psa_aead_encrypt_internal(application_identity, op) } fn psa_aead_decrypt( &self, application_identity: &ApplicationIdentity, op: psa_aead_decrypt::Operation, ) -> Result { trace!("psa_aead_decrypt ingress"); self.psa_aead_decrypt_internal(application_identity, op) } fn psa_hash_compute( &self, op: psa_hash_compute::Operation, ) -> Result { trace!("psa_hash_compute ingress"); self.psa_hash_compute_internal(op) } fn psa_hash_compare( &self, op: psa_hash_compare::Operation, ) -> Result { trace!("psa_hash_compare ingress"); self.psa_hash_compare_internal(op) } fn psa_raw_key_agreement( &self, application_identity: &ApplicationIdentity, op: psa_raw_key_agreement::Operation, ) -> Result { trace!("psa_raw_key_agreement ingress"); self.psa_raw_key_agreement(application_identity, op) } fn psa_generate_random( &self, op: psa_generate_random::Operation, ) -> Result { trace!("psa_generate_random ingress"); self.psa_generate_random_internal(op) } fn can_do_crypto( &self, application_identity: &ApplicationIdentity, op: can_do_crypto::Operation, ) -> Result { trace!("can_do_crypto ingress"); self.can_do_crypto_main(application_identity, op) } } /// Mbed Crypto provider builder #[derive(Default, Derivative)] #[derivative(Debug)] pub struct ProviderBuilder { provider_name: Option, #[derivative(Debug = "ignore")] key_info_store: Option, } impl ProviderBuilder { /// Create a new provider builder pub fn new() -> ProviderBuilder { ProviderBuilder { provider_name: None, key_info_store: None, } } /// Add a provider name pub fn with_provider_name(mut self, provider_name: String) -> ProviderBuilder { self.provider_name = Some(provider_name); self } /// Add a KeyInfo manager pub fn with_key_info_store(mut self, key_info_store: KeyInfoManagerClient) -> ProviderBuilder { self.key_info_store = Some(key_info_store); self } /// Build into a MbedProvider pub fn build(self) -> std::io::Result { Provider::new( self.provider_name.ok_or_else(|| { std::io::Error::new(std::io::ErrorKind::InvalidData, "missing provider name") })?, self.key_info_store .ok_or_else(|| Error::new(ErrorKind::InvalidData, "missing key info store"))?, ) .ok_or_else(|| { Error::new( ErrorKind::InvalidData, "MbedCrypto Provider initialization failed", ) }) } } parsec-service-1.3.0/src/providers/mod.rs000064400000000000000000000357661046102023000165120ustar 00000000000000// Copyright 2019 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Core inter-op with underlying hardware //! //! [Providers](https://parallaxsecond.github.io/parsec-book/parsec_service/providers.html) //! are the real implementors of the operations that Parsec claims to support. They map to //! functionality in the underlying hardware which allows the PSA Crypto operations to be //! backed by a hardware root of trust. use log::trace; use parsec_interface::requests::Opcode; use std::collections::HashSet; use std::convert::TryFrom; use std::fmt; pub mod core; pub mod crypto_capability; #[cfg(feature = "pkcs11-provider")] //TODO: To remove when #301 is merged #[allow(clippy::all)] pub mod pkcs11; #[cfg(feature = "mbed-crypto-provider")] pub mod mbed_crypto; #[cfg(feature = "tpm-provider")] pub mod tpm; #[cfg(feature = "cryptoauthlib-provider")] pub mod cryptoauthlib; #[cfg(feature = "trusted-service-provider")] pub mod trusted_service; use crate::authenticators::ApplicationIdentity; use parsec_interface::operations::{ attest_key, can_do_crypto, delete_client, list_authenticators, list_clients, list_keys, list_opcodes, list_providers, ping, prepare_key_attestation, psa_aead_decrypt, psa_aead_encrypt, psa_asymmetric_decrypt, psa_asymmetric_encrypt, psa_cipher_decrypt, psa_cipher_encrypt, psa_destroy_key, psa_export_key, psa_export_public_key, psa_generate_key, psa_generate_random, psa_hash_compare, psa_hash_compute, psa_import_key, psa_raw_key_agreement, psa_sign_hash, psa_sign_message, psa_verify_hash, psa_verify_message, }; use parsec_interface::requests::{ResponseStatus, Result}; use parsec_interface::requests::ProviderId; /// The ProviderIdentity struct specifies a unique uuid-name /// combination to form a unique provider identity. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct ProviderIdentity { /// The uuid of the provider uuid: String, /// The name of the provider set in the config, defaults to a suitable name. name: String, } impl fmt::Display for ProviderIdentity { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "ProviderIdentity: [uuid=\"{}\", name=\"{}\"]", self.uuid, self.name ) } } impl ProviderIdentity { /// Creates a new instance of ProviderIdentity. pub fn new(uuid: String, name: String) -> ProviderIdentity { ProviderIdentity { uuid, name } } /// Get the uuid of the provider pub fn uuid(&self) -> &String { &self.uuid } /// Get the name of the provider pub fn name(&self) -> &String { &self.name } } impl TryFrom for ProviderId { type Error = String; fn try_from(provider_identity: ProviderIdentity) -> ::std::result::Result { let provider_id = match provider_identity.uuid.as_str() { #[cfg(feature = "cryptoauthlib-provider")] crate::providers::cryptoauthlib::Provider::PROVIDER_UUID => Ok(ProviderId::CryptoAuthLib), #[cfg(feature = "mbed-crypto-provider")] crate::providers::mbed_crypto::Provider::PROVIDER_UUID => Ok(ProviderId::MbedCrypto), #[cfg(feature = "pkcs11-provider")] crate::providers::pkcs11::Provider::PROVIDER_UUID => Ok(ProviderId::Pkcs11), #[cfg(feature = "tpm-provider")] crate::providers::tpm::Provider::PROVIDER_UUID => Ok(ProviderId::Tpm), #[cfg(feature = "trusted-service-provider")] crate::providers::trusted_service::Provider::PROVIDER_UUID => Ok(ProviderId::TrustedService), _ => Err(format!("Cannot convert from ProviderIdentity to ProviderId.\nProvider \"{}\" is not recognised.\nCould be it does not exist, or Parsec was not compiled with the required provider feature flags.", provider_identity.uuid)), }?; Ok(provider_id) } } /// Provider interface for servicing client operations /// /// Definition of the interface that a provider must implement to /// be linked into the service through a backend handler. /// /// The methods with no default are used on a service-level by the /// core provider and so must be supported by all providers. pub trait Provide { /// Return a description of the current provider. /// /// The descriptions are gathered in the Core Provider and returned for a ListProviders operation. fn describe(&self) -> Result<(list_providers::ProviderInfo, HashSet)>; /// List the providers running in the service. fn list_providers(&self, _op: list_providers::Operation) -> Result { trace!("list_providers ingress"); Err(ResponseStatus::PsaErrorNotSupported) } /// List the opcodes supported by the given provider. fn list_opcodes(&self, _op: list_opcodes::Operation) -> Result { trace!("list_opcodes ingress"); Err(ResponseStatus::PsaErrorNotSupported) } /// List the authenticators supported by the given provider. fn list_authenticators( &self, _op: list_authenticators::Operation, ) -> Result { trace!("list_authenticators ingress"); Err(ResponseStatus::PsaErrorNotSupported) } /// Lists all keys belonging to the application. fn list_keys( &self, _application_identity: &ApplicationIdentity, _op: list_keys::Operation, ) -> Result; /// Lists all clients currently having data in the service. fn list_clients(&self, _op: list_clients::Operation) -> Result; /// Delete all data a client has in the service.. fn delete_client( &self, _application_identity: &ApplicationIdentity, _op: delete_client::Operation, ) -> Result { trace!("delete_client ingress"); Err(ResponseStatus::PsaErrorNotSupported) } /// Execute a Ping operation to get the wire protocol version major and minor information. /// /// # Errors /// /// This operation will only fail if not implemented. It will never fail when being called on /// the `CoreProvider`. fn ping(&self, _op: ping::Operation) -> Result { trace!("ping ingress"); Err(ResponseStatus::PsaErrorNotSupported) } /// Execute a GenerateKey operation. /// /// Providers should try, in a best-effort way, to handle failures in a way that it is possible /// to create a key with the same name later on. /// /// For providers using a Key Info Manager to map a key name with a provider-specific key /// identification, the following algorithm can be followed: /// 1. generate unique key ID /// 2. try key creation with it. If successfull go to 3 else return an error. /// 3. store the mappings between key name and key ID. If successfull return success, else go to 4. /// 4. try to delete the key created. If failed, log it and return the error from 3. fn psa_generate_key( &self, _application_identity: &ApplicationIdentity, _op: psa_generate_key::Operation, ) -> Result { trace!("psa_generate_key ingress"); Err(ResponseStatus::PsaErrorNotSupported) } /// Execute an ImportKey operation. /// /// Providers should try, in a best-effort way, to handle failures in a way that it is possible /// to import a key with the same name later on. /// /// For providers using a Key Info Manager to map a key name with a provider-specific key /// identification, the following algorithm can be followed: /// 1. generate unique key ID /// 2. try key import with it. If successfull go to 3 else return an error. /// 3. store the mappings between key name and key ID. If successfull return success, else go to 4. /// 4. try to delete the key imported. If failed, log it and return the error from 3. fn psa_import_key( &self, _application_identity: &ApplicationIdentity, _op: psa_import_key::Operation, ) -> Result { trace!("psa_import_key ingress"); Err(ResponseStatus::PsaErrorNotSupported) } /// Execute an ExportPublicKey operation. fn psa_export_public_key( &self, _application_identity: &ApplicationIdentity, _op: psa_export_public_key::Operation, ) -> Result { trace!("psa_export_public_key ingress"); Err(ResponseStatus::PsaErrorNotSupported) } /// Execute an ExportKey operation. fn psa_export_key( &self, _application_identity: &ApplicationIdentity, _op: psa_export_key::Operation, ) -> Result { trace!("psa_export_key ingress"); Err(ResponseStatus::PsaErrorNotSupported) } /// Execute a DestroyKey operation. /// /// Providers should try, in a best-effort way, to handle failures in a way that it is possible /// to generate or create a key with the same name than the one destroyed later on. /// /// For providers using a Key Info Manager to map a key name with a provider-specific key /// identification, the following algorithm can be followed: /// 1. get the key ID from the key name using the KIM /// 2. destroy the key mappings /// 3. try to destroy the key fn psa_destroy_key( &self, _application_identity: &ApplicationIdentity, _op: psa_destroy_key::Operation, ) -> Result { trace!("psa_destroy_key ingress"); Err(ResponseStatus::PsaErrorNotSupported) } /// Execute a SignHash operation. This operation only signs the short digest given but does not /// hash it. fn psa_sign_hash( &self, _application_identity: &ApplicationIdentity, _op: psa_sign_hash::Operation, ) -> Result { trace!("psa_sign_hash ingress"); Err(ResponseStatus::PsaErrorNotSupported) } /// Execute a VerifyHash operation. fn psa_verify_hash( &self, _application_identity: &ApplicationIdentity, _op: psa_verify_hash::Operation, ) -> Result { trace!("psa_verify_hash ingress"); Err(ResponseStatus::PsaErrorNotSupported) } /// Execute an AsymmetricEncrypt operation. fn psa_asymmetric_encrypt( &self, _application_identity: &ApplicationIdentity, _op: psa_asymmetric_encrypt::Operation, ) -> Result { trace!("psa_asymmetric_encrypt ingress"); Err(ResponseStatus::PsaErrorNotSupported) } /// Execute an AsymmetricDecrypt operation. fn psa_asymmetric_decrypt( &self, _application_identity: &ApplicationIdentity, _op: psa_asymmetric_decrypt::Operation, ) -> Result { trace!("psa_asymmetric_decrypt ingress"); Err(ResponseStatus::PsaErrorNotSupported) } /// Execute an AeadEncrypt operation. fn psa_aead_encrypt( &self, _application_identity: &ApplicationIdentity, _op: psa_aead_encrypt::Operation, ) -> Result { trace!("psa_aead_encrypt ingress"); Err(ResponseStatus::PsaErrorNotSupported) } /// Execute an AeadDecrypt operation. fn psa_aead_decrypt( &self, _application_identity: &ApplicationIdentity, _op: psa_aead_decrypt::Operation, ) -> Result { trace!("psa_aead_decrypt ingress"); Err(ResponseStatus::PsaErrorNotSupported) } /// Execute a HashCompute operation. fn psa_hash_compute( &self, _op: psa_hash_compute::Operation, ) -> Result { trace!("psa_hash_compute ingress"); Err(ResponseStatus::PsaErrorNotSupported) } /// Execute a HashCompare operation. fn psa_hash_compare( &self, _op: psa_hash_compare::Operation, ) -> Result { trace!("psa_hash_compare ingress"); Err(ResponseStatus::PsaErrorNotSupported) } /// Execute a RawKeyAgreement operation. fn psa_raw_key_agreement( &self, _application_identity: &ApplicationIdentity, _op: psa_raw_key_agreement::Operation, ) -> Result { trace!("psa_raw_key_agreement ingress"); Err(ResponseStatus::PsaErrorNotSupported) } /// Execute a GenerateRandom operation. fn psa_generate_random( &self, _op: psa_generate_random::Operation, ) -> Result { trace!("psa_generate_random ingress"); Err(ResponseStatus::PsaErrorNotSupported) } /// Encrypt a short message with a symmetric cipher. fn psa_cipher_encrypt( &self, _application_identity: &ApplicationIdentity, _op: psa_cipher_encrypt::Operation, ) -> Result { trace!("psa_cipher_encrypt ingress"); Err(ResponseStatus::PsaErrorNotSupported) } /// Decrypt a short message with a symmetric cipher. fn psa_cipher_decrypt( &self, _application_identity: &ApplicationIdentity, _op: psa_cipher_decrypt::Operation, ) -> Result { trace!("psa_cipher_decrypt ingress"); Err(ResponseStatus::PsaErrorNotSupported) } /// Sign a message with a private key. fn psa_sign_message( &self, _application_identity: &ApplicationIdentity, _op: psa_sign_message::Operation, ) -> Result { trace!("psa_sign_message ingress"); Err(ResponseStatus::PsaErrorNotSupported) } /// Verify the signature of a message using a public key. fn psa_verify_message( &self, _application_identity: &ApplicationIdentity, _op: psa_verify_message::Operation, ) -> Result { trace!("psa_verify_message ingress"); Err(ResponseStatus::PsaErrorNotSupported) } ///Check if the crypto operation is supported by provider. fn can_do_crypto( &self, _application_identity: &ApplicationIdentity, _op: can_do_crypto::Operation, ) -> Result { trace!("can_do_crypto main ingress"); Err(ResponseStatus::PsaErrorNotSupported) } /// Prepare a key attestation operation. fn prepare_key_attestation( &self, _application_identity: &ApplicationIdentity, _op: prepare_key_attestation::Operation, ) -> Result { trace!("prepare_key_attestation ingress"); Err(ResponseStatus::PsaErrorNotSupported) } /// Attest a key. fn attest_key( &self, _application_identity: &ApplicationIdentity, _op: attest_key::Operation, ) -> Result { trace!("attest_key ingress"); Err(ResponseStatus::PsaErrorNotSupported) } } parsec-service-1.3.0/src/providers/pkcs11/asym_encryption.rs000064400000000000000000000125431046102023000222440ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::utils::{algorithm_to_mechanism, to_response_status}; use super::KeyPairType; use super::Provider; use crate::authenticators::ApplicationIdentity; use crate::key_info_managers::KeyIdentity; use cryptoki::error::Error; use cryptoki::error::RvError; use log::{info, trace}; use parsec_interface::operations::psa_algorithm::{Algorithm, AsymmetricEncryption}; use parsec_interface::operations::{psa_asymmetric_decrypt, psa_asymmetric_encrypt}; use parsec_interface::requests::{ResponseStatus, Result}; impl Provider { pub(super) fn psa_asymmetric_encrypt_internal( &self, application_identity: &ApplicationIdentity, op: psa_asymmetric_encrypt::Operation, ) -> Result { let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), op.key_name.clone(), ); let key_id = self.key_info_store.get_key_id(&key_identity)?; let key_attributes = self.key_info_store.get_key_attributes(&key_identity)?; op.validate(key_attributes)?; let mech = algorithm_to_mechanism(Algorithm::from(op.alg)).map_err(to_response_status)?; let session = self.new_session()?; let key = self.find_key(&session, key_id, KeyPairType::PublicKey)?; info!("Located encrypting key."); trace!("Encrypt* commands"); Ok(psa_asymmetric_encrypt::Result { ciphertext: session .encrypt(&mech, key, &op.plaintext) .map_err(to_response_status)? .into(), }) } pub(super) fn psa_asymmetric_decrypt_internal( &self, application_identity: &ApplicationIdentity, op: psa_asymmetric_decrypt::Operation, ) -> Result { let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), op.key_name.clone(), ); let key_id = self.key_info_store.get_key_id(&key_identity)?; let key_attributes = self.key_info_store.get_key_attributes(&key_identity)?; op.validate(key_attributes)?; let mech = algorithm_to_mechanism(Algorithm::from(op.alg)).map_err(to_response_status)?; let session = self.new_session()?; let key = self.find_key(&session, key_id, KeyPairType::PrivateKey)?; info!("Located decrypting key."); trace!("Decrypt* command"); Ok(psa_asymmetric_decrypt::Result { plaintext: session .decrypt(&mech, key, &op.ciphertext) .map_err(|e| { // If the algorithm is RSA with PKCS#1 v1.5 padding and we get CKR_ENCRYPTED_DATA_INVALID back, // it means the padding has been deemed invalid and we should let the caller know // about that. This allows clients to mitigate attacks that leverage padding // oracles a la Bleichenbacher. // See https://cryptosense.com/blog/why-pkcs1v1-5-encryption-should-be-put-out-of-our-misery // for more details. if let Algorithm::AsymmetricEncryption(AsymmetricEncryption::RsaPkcs1v15Crypt) = key_attributes.policy.permitted_algorithms { match e { Error::Pkcs11(RvError::EncryptedDataInvalid) => { return ResponseStatus::PsaErrorInvalidPadding } _ => (), } } to_response_status(e) })? .into(), }) } pub(super) fn software_psa_asymmetric_encrypt_internal( &self, application_identity: &ApplicationIdentity, op: psa_asymmetric_encrypt::Operation, ) -> Result { let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), op.key_name.clone(), ); let key_attributes = self.key_info_store.get_key_attributes(&key_identity)?; op.validate(key_attributes)?; let alg = op.alg; let salt_buff = op.salt.as_ref().map(|salt| salt.as_slice()); let buffer_size = key_attributes.asymmetric_encrypt_output_size(alg)?; let mut ciphertext = vec![0u8; buffer_size]; let pub_key_id = self.move_pub_key_to_psa_crypto(&key_identity)?; info!("Encrypting plaintext with PSA Crypto"); let res = match psa_crypto::operations::asym_encryption::encrypt( pub_key_id, alg, &op.plaintext, salt_buff, &mut ciphertext, ) { Ok(output_size) => { ciphertext.resize(output_size, 0); Ok(psa_asymmetric_encrypt::Result { ciphertext: ciphertext.into(), }) } Err(error) => { let error = ResponseStatus::from(error); format_error!("Asymmetric encryption failed", error); Err(error) } }; let _ = self.remove_psa_crypto_pub_key(pub_key_id); res } } parsec-service-1.3.0/src/providers/pkcs11/asym_sign.rs000064400000000000000000000112431046102023000210060ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::utils::{algorithm_to_mechanism, to_response_status}; use super::Provider; use super::{utils, KeyPairType}; use crate::authenticators::ApplicationIdentity; use crate::key_info_managers::KeyIdentity; use log::{info, trace}; use parsec_interface::operations::psa_algorithm::Algorithm; use parsec_interface::operations::psa_key_attributes::Type; use parsec_interface::operations::{psa_sign_hash, psa_verify_hash}; use parsec_interface::requests::{ResponseStatus, Result}; impl Provider { pub(super) fn psa_sign_hash_internal( &self, application_identity: &ApplicationIdentity, op: psa_sign_hash::Operation, ) -> Result { let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), op.key_name.clone(), ); let key_id = self.key_info_store.get_key_id(&key_identity)?; let key_attributes = self.key_info_store.get_key_attributes(&key_identity)?; op.validate(key_attributes)?; let mech = algorithm_to_mechanism(Algorithm::from(op.alg)).map_err(to_response_status)?; let session = self.new_session()?; let key = self.find_key(&session, key_id, KeyPairType::PrivateKey)?; info!("Located signing key."); let hash = match key_attributes.key_type { // For RSA signatures we need to format the hash into a DigestInfo structure Type::RsaKeyPair => utils::digest_info(op.alg, op.hash.to_vec())?.into(), // For ECDSA the format we get it in is sufficient. Type::EccKeyPair { .. } => op.hash, _ => return Err(ResponseStatus::PsaErrorNotSupported), // this shouldn't be reachable }; trace!("Sign* command"); Ok(psa_sign_hash::Result { signature: session .sign(&mech, key, &hash) .map_err(to_response_status)? .into(), }) } pub(super) fn psa_verify_hash_internal( &self, application_identity: &ApplicationIdentity, op: psa_verify_hash::Operation, ) -> Result { let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), op.key_name.clone(), ); let key_id = self.key_info_store.get_key_id(&key_identity)?; let key_attributes = self.key_info_store.get_key_attributes(&key_identity)?; op.validate(key_attributes)?; let mech = algorithm_to_mechanism(Algorithm::from(op.alg)).map_err(to_response_status)?; let session = self.new_session()?; let key = self.find_key(&session, key_id, KeyPairType::PublicKey)?; info!("Located public key."); let hash = match key_attributes.key_type { // For RSA signatures we need to format the hash into a DigestInfo structure Type::RsaKeyPair | Type::RsaPublicKey => { utils::digest_info(op.alg, op.hash.to_vec())?.into() } // For ECDSA the format we get it in is sufficient. Type::EccKeyPair { .. } | Type::EccPublicKey { .. } => op.hash, _ => return Err(ResponseStatus::PsaErrorNotSupported), // this shouldn't be reachable }; trace!("Verify* command"); session .verify(&mech, key, &hash, &op.signature) .map_err(to_response_status)?; Ok(psa_verify_hash::Result {}) } pub(super) fn software_psa_verify_hash_internal( &self, application_identity: &ApplicationIdentity, op: psa_verify_hash::Operation, ) -> Result { let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), op.key_name.clone(), ); let key_attributes = self.key_info_store.get_key_attributes(&key_identity)?; op.validate(key_attributes)?; let pub_key_id = self.move_pub_key_to_psa_crypto(&key_identity)?; info!("Verifying signature with PSA Crypto"); let res = match psa_crypto::operations::asym_signature::verify_hash( pub_key_id, op.alg, &op.hash, &op.signature, ) { Ok(()) => Ok(psa_verify_hash::Result {}), Err(error) => { let error = ResponseStatus::from(error); format_error!("Verify hash failed", error); Err(error) } }; let _ = self.remove_psa_crypto_pub_key(pub_key_id); res } } parsec-service-1.3.0/src/providers/pkcs11/capability_discovery.rs000064400000000000000000000116461046102023000232340ustar 00000000000000// Copyright 2021 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 #![allow(trivial_numeric_casts)] use super::utils::algorithm_to_mechanism; use super::{utils, Provider}; use crate::authenticators::ApplicationIdentity; use crate::providers::crypto_capability::CanDoCrypto; use crate::providers::pkcs11::to_response_status; use cryptoki::mechanism::{MechanismInfo, MechanismType}; use cryptoki::types::Ulong; use log::{info, trace}; use parsec_interface::operations::can_do_crypto; use parsec_interface::operations::psa_algorithm::*; use parsec_interface::operations::psa_key_attributes::{Attributes, Type}; use parsec_interface::requests::ResponseStatus::PsaErrorNotSupported; use parsec_interface::requests::Result; impl CanDoCrypto for Provider { fn can_do_crypto_internal( &self, _application_identity: &ApplicationIdentity, op: can_do_crypto::Operation, ) -> Result { trace!("can_do_crypto_internal"); // Check attributes compatibility with the provider match op.attributes.key_type { Type::RsaKeyPair | Type::RsaPublicKey => { // Check for supported Hash for RSA PKCS#1 v1.5 signature algorithm. match op.attributes.policy.permitted_algorithms { Algorithm::AsymmetricSignature( alg @ AsymmetricSignature::RsaPkcs1v15Sign { .. }, ) => { let _ = utils::digest_info(alg, vec![0, 1]).map_err(|_| { info!("Unsupported Hash in signature algorithm {:?}", alg); PsaErrorNotSupported })?; } _ => (), } Ok(can_do_crypto::Result {}) } Type::EccKeyPair { curve_family } | Type::EccPublicKey { curve_family } => { let _ = utils::ec_params(curve_family, op.attributes.bits).map_err(|_| { info!( "Unsupported EC curve family {} or key size {}", curve_family, op.attributes.bits ); PsaErrorNotSupported })?; Ok(can_do_crypto::Result) } _ => { info!("Unsupported key type {:?}", op.attributes.key_type); Err(PsaErrorNotSupported) } } } fn use_check_internal(&self, attributes: Attributes) -> Result { trace!("use_check_internal"); let supported_mechanisms: Vec = self .backend .get_mechanism_list(self.slot_number) .map_err(to_response_status)?; let mechanism = algorithm_to_mechanism(attributes.policy.permitted_algorithms) .map_err(to_response_status)?; if !(supported_mechanisms.contains(&mechanism.mechanism_type())) { info!("Mechanism {:?} is not supported", mechanism); return Err(PsaErrorNotSupported); } let mechanism_info: MechanismInfo = self .backend .get_mechanism_info(self.slot_number, mechanism.mechanism_type()) .map_err(to_response_status)?; if std::any::type_name::() == std::any::type_name::() { if !(attributes.bits >= mechanism_info.min_key_size() && attributes.bits <= mechanism_info.max_key_size()) { info!( "Incorrect key size {} for mechanism {:?}", attributes.bits, mechanism ); return Err(PsaErrorNotSupported); } } else { if !(attributes.bits >= mechanism_info.min_key_size() && attributes.bits <= mechanism_info.max_key_size()) { info!( "Incorrect key size {} for mechanism {:?}", attributes.bits, mechanism ); return Err(PsaErrorNotSupported); } } Ok(can_do_crypto::Result) } fn generate_check_internal(&self, attributes: Attributes) -> Result { trace!("generate_check_internal"); match attributes.key_type { Type::RsaKeyPair | Type::EccKeyPair { .. } => Ok(can_do_crypto::Result), _ => { info!("Unsupported key type {:?}", attributes.key_type); Err(PsaErrorNotSupported) } } } fn import_check_internal(&self, attributes: Attributes) -> Result { trace!("import_check_internal"); match attributes.key_type { Type::RsaPublicKey | Type::EccPublicKey { .. } => Ok(can_do_crypto::Result), _ => { info!("Unsupported key type {:?}", attributes.key_type); Err(PsaErrorNotSupported) } } } } parsec-service-1.3.0/src/providers/pkcs11/generate_random.rs000064400000000000000000000016551046102023000221550ustar 00000000000000// Copyright 2022 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::utils::to_response_status; use super::Provider; use log::error; use parsec_interface::operations::psa_generate_random; use parsec_interface::requests::{ResponseStatus, Result}; use std::convert::TryFrom; impl Provider { pub(super) fn psa_generate_random_internal( &self, op: psa_generate_random::Operation, ) -> Result { let length = u32::try_from(op.size).or_else(|_| { let error = ResponseStatus::PsaErrorGenericError; error!("Requested size is too large"); Err(error) })?; let session = self.new_session()?; Ok(psa_generate_random::Result { random_bytes: session .generate_random_vec(length) .map_err(to_response_status)? .into(), }) } } parsec-service-1.3.0/src/providers/pkcs11/key_management.rs000064400000000000000000000506341046102023000220100ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::utils::{algorithm_to_mechanism, to_response_status}; use super::{utils, KeyPairType, Provider}; use crate::authenticators::ApplicationIdentity; use crate::key_info_managers::KeyIdentity; use cryptoki::mechanism::{Mechanism, MechanismType}; use cryptoki::object::{Attribute, AttributeType, KeyType, ObjectClass, ObjectHandle}; use cryptoki::session::Session; use log::{error, info, trace}; use parsec_interface::operations::psa_key_attributes::{EccFamily, Id, Lifetime, Type}; use parsec_interface::operations::utils_deprecated_primitives::CheckDeprecated; use parsec_interface::operations::{ psa_destroy_key, psa_export_public_key, psa_generate_key, psa_import_key, }; use parsec_interface::requests::{ResponseStatus, Result}; use parsec_interface::secrecy::ExposeSecret; use picky_asn1::wrapper::{IntegerAsn1, OctetStringAsn1}; use picky_asn1_x509::RsaPublicKey; use std::convert::TryInto; impl Provider { /// Find the PKCS 11 object handle corresponding to the key ID and the key type (public, /// private or any key type) given as parameters for the current session. pub(super) fn find_key( &self, session: &Session, key_id: u32, key_type: KeyPairType, ) -> Result { let mut template = vec![Attribute::Id(key_id.to_be_bytes().to_vec())]; match key_type { KeyPairType::PublicKey => template.push(Attribute::Class(ObjectClass::PUBLIC_KEY)), KeyPairType::PrivateKey => template.push(Attribute::Class(ObjectClass::PRIVATE_KEY)), KeyPairType::Any => (), } trace!("FindObjects commands"); let objects = session .find_objects(&template) .map_err(to_response_status)?; if objects.is_empty() { Err(ResponseStatus::PsaErrorDoesNotExist) } else { Ok(objects[0]) } } pub(super) fn move_pub_key_to_psa_crypto(&self, key_identity: &KeyIdentity) -> Result { info!("Attempting to export public key"); let export_operation = psa_export_public_key::Operation { key_name: key_identity.key_name().to_owned(), }; let psa_export_public_key::Result { data } = self.psa_export_public_key_internal(key_identity.application(), export_operation)?; info!("Importing public key into PSA Crypto"); let mut attributes = self.key_info_store.get_key_attributes(&key_identity)?; attributes.lifetime = Lifetime::Volatile; attributes.key_type = match attributes.key_type { Type::RsaKeyPair | Type::RsaPublicKey => Type::RsaPublicKey, Type::EccKeyPair { curve_family } | Type::EccPublicKey { curve_family } => { Type::EccPublicKey { curve_family } } Type::DhKeyPair { group_family } | Type::DhPublicKey { group_family } => { Type::DhPublicKey { group_family } } _ => return Err(ResponseStatus::PsaErrorInvalidArgument), }; let id = psa_crypto::operations::key_management::import(attributes, None, &data)?; Ok(id) } pub(super) fn remove_psa_crypto_pub_key(&self, pub_key_id: Id) -> Result<()> { info!("Removing public key stored in PSA."); unsafe { psa_crypto::operations::key_management::destroy(pub_key_id) }.map_err(|e| { error!("Failed to remove public key from PSA Crypto."); e })?; Ok(()) } pub(super) fn psa_generate_key_internal( &self, application_identity: &ApplicationIdentity, op: psa_generate_key::Operation, ) -> Result { return_on_deprecated!(op, "The key requested to generate is deprecated"); if op.attributes.key_type.is_public_key() { error!("A public key type can not be generated."); return Err(ResponseStatus::PsaErrorInvalidArgument); } let key_name = op.key_name; let key_attributes = op.attributes; if key_attributes.policy.usage_flags.export() && !self.allow_export { error!("The configuration of this provider does not allow it to generate keys that can be exported."); return Err(ResponseStatus::PsaErrorNotPermitted); } let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), key_name, ); self.key_info_store.does_not_exist(&key_identity)?; let session = self.new_session()?; let key_id = self.create_key_id(); let mut pub_template = vec![ Attribute::Id(key_id.to_be_bytes().to_vec()), Attribute::Token(true.into()), Attribute::AllowedMechanisms(vec![algorithm_to_mechanism( key_attributes.policy.permitted_algorithms, ) .map_err(to_response_status)? .mechanism_type()]), ]; let mut priv_template = pub_template.clone(); priv_template.push(Attribute::Class(ObjectClass::PRIVATE_KEY)); pub_template.push(Attribute::Class(ObjectClass::PUBLIC_KEY)); pub_template.push(Attribute::Private(false.into())); utils::key_pair_usage_flags_to_pkcs11_attributes( key_attributes.policy.usage_flags, &mut pub_template, &mut priv_template, ); let mech = match key_attributes.key_type { Type::RsaKeyPair => { pub_template.push(Attribute::PublicExponent(utils::PUBLIC_EXPONENT.to_vec())); pub_template.push(Attribute::ModulusBits( key_attributes.bits.try_into().map_err(to_response_status)?, )); Ok(Mechanism::RsaPkcsKeyPairGen) } Type::EccKeyPair { curve_family } => { pub_template.push(Attribute::EcParams( picky_asn1_der::to_vec(&utils::ec_params(curve_family, key_attributes.bits)?) .map_err(|e| { error!("Failed to generate EC parameters: {}", e); ResponseStatus::PsaErrorGenericError })?, )); Ok(Mechanism::EccKeyPairGen) } _ => Err(ResponseStatus::PsaErrorNotSupported), }?; match session.generate_key_pair(&mech, &pub_template, &priv_template) { Ok((public, private)) => { if let Err(e) = self.key_info_store .insert_key_info(key_identity, &key_id, key_attributes) { format_error!("Failed to insert the mappings, deleting the key", e); if let Err(e) = session.destroy_object(public) { format_error!("Failed to destroy public part of the key", e); } if let Err(e) = session.destroy_object(private) { format_error!("Failed to destroy private part of the key", e); } Err(e) } else { Ok(psa_generate_key::Result {}) } } Err(error) => { format_error!("Generate key status", error); Err(to_response_status(error)) } } } pub(super) fn psa_import_key_internal( &self, application_identity: &ApplicationIdentity, op: psa_import_key::Operation, ) -> Result { warn_on_deprecated!(op, "The key requested to import is deprecated"); let key_name = op.key_name; let key_attributes = op.attributes; if key_attributes.policy.usage_flags.export() && !self.allow_export { error!("The configuration of this provider does not allow it to generate keys that can be exported."); return Err(ResponseStatus::PsaErrorNotPermitted); } if op.data.expose_secret().is_empty() { error!("Key data is empty"); return Err(ResponseStatus::PsaErrorInvalidArgument); } let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), key_name, ); self.key_info_store.does_not_exist(&key_identity)?; let session = self.new_session()?; let key_id = self.create_key_id(); let mut template: Vec = Vec::new(); template.push(Attribute::Class(ObjectClass::PUBLIC_KEY)); template.push(Attribute::Token(true.into())); template.push(Attribute::Verify(true.into())); template.push(Attribute::Id(key_id.to_be_bytes().to_vec())); match op.attributes.key_type { Type::RsaPublicKey => { self.handle_rsa_public_import_attrib( op.data.expose_secret(), key_attributes.bits, &mut template, )?; } Type::EccPublicKey { curve_family } => { self.handle_ecc_public_import_attrib( op.data.expose_secret(), key_attributes.bits, curve_family, &mut template, )?; } _ => { error!( "The pkcs11 provider does not support the {:?} key type.", op.attributes.key_type ); return Err(ResponseStatus::PsaErrorNotSupported); } } trace!("CreateObject command"); match session.create_object(&template) { Ok(key) => { if let Err(e) = self.key_info_store .insert_key_info(key_identity, &key_id, key_attributes) { format_error!("Failed to insert the mappings, deleting the key.", e); if let Err(e) = session.destroy_object(key) { format_error!("Failed to destroy public key: ", e); } Err(e) } else { Ok(psa_import_key::Result {}) } } Err(error) => { format_error!("Import key status: ", error); Err(to_response_status(error)) } } } pub(super) fn handle_rsa_public_import_attrib( &self, key_data: &[u8], bits: usize, template: &mut Vec, ) -> Result<()> { let public_key: RsaPublicKey = picky_asn1_der::from_bytes(key_data).map_err(|e| { format_error!("Failed to parse RsaPublicKey data", e); ResponseStatus::PsaErrorInvalidArgument })?; if public_key.modulus.is_negative() || public_key.public_exponent.is_negative() { error!("Only positive modulus and public exponent are supported."); return Err(ResponseStatus::PsaErrorInvalidArgument); } let modulus_object = public_key.modulus.as_unsigned_bytes_be(); let exponent_object = public_key.public_exponent.as_unsigned_bytes_be(); if bits != 0 && modulus_object.len() * 8 != bits { if crate::utils::GlobalConfig::log_error_details() { error!( "`bits` field of key attributes (value: {}) must be either 0 or equal to the size of the key in `data` (value: {}).", bits, modulus_object.len() * 8 ); } else { error!("`bits` field of key attributes must be either 0 or equal to the size of the key in `data`."); } return Err(ResponseStatus::PsaErrorInvalidArgument); } template.push(Attribute::Modulus(modulus_object.into())); template.push(Attribute::PublicExponent(exponent_object.into())); template.push(Attribute::Encrypt(true.into())); template.push(Attribute::Private(false.into())); template.push(Attribute::AllowedMechanisms(vec![MechanismType::RSA_PKCS])); template.push(Attribute::KeyType(KeyType::RSA)); Ok(()) } pub(super) fn handle_ecc_public_import_attrib( &self, key_data: &[u8], bits: usize, curve_family: EccFamily, template: &mut Vec, ) -> Result<()> { match curve_family { EccFamily::Montgomery => { // Montgomery curves aren't supported because their format differs from what // we need below. // In any case, the list of curves for which we can create `EcParams` below // is even shorter than that. error!("Importing EC keys using Montgomery curves is not currently supported."); return Err(ResponseStatus::PsaErrorNotSupported); } _ => (), } // For the format of ECC public keys, see: // https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_export_public_key.html#description let key_len = ((key_data.len() - 1) / 2) * 8; let bits = if bits == 0 { key_len } else { bits }; if bits != key_len { if crate::utils::GlobalConfig::log_error_details() { error!( "`bits` field of key attributes (value: {}) must be either 0 or equal to half the size of the key in `data` (value: {}) for Weierstrass curves.", bits, key_len ); } else { error!("`bits` field of key attributes must be either 0 or equal to half the size of the key in `data` for Weierstrass curves."); } return Err(ResponseStatus::PsaErrorInvalidArgument); } // The format expected by PKCS11 is an ASN.1 OctetString containing the // data that the PSA Crypto interface specifies. // See ECPoint in [SEC1](https://www.secg.org/sec1-v2.pdf). PKCS11 mandates using // [ANSI X9.62 ECPoint](https://cryptsoft.com/pkcs11doc/v220/group__SEC__12__3__3__ECDSA__PUBLIC__KEY__OBJECTS.html), // however SEC1 is an equivalent spec. let key_data = picky_asn1_der::to_vec(&OctetStringAsn1(key_data.to_vec())).map_err(|e| { error!("Failed to generate EC Point OctetString: {}", e); ResponseStatus::PsaErrorInvalidArgument })?; template.push(Attribute::EcPoint(key_data)); template.push(Attribute::Private(false.into())); template.push(Attribute::AllowedMechanisms(vec![MechanismType::ECDSA])); template.push(Attribute::KeyType(KeyType::EC)); template.push(Attribute::EcParams( picky_asn1_der::to_vec(&utils::ec_params(curve_family, bits)?).map_err(|e| { error!("Failed to generate EC parameters: {}", e); ResponseStatus::PsaErrorGenericError })?, )); Ok(()) } pub(super) fn psa_export_public_key_internal( &self, application_identity: &ApplicationIdentity, op: psa_export_public_key::Operation, ) -> Result { let key_name = op.key_name; let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), key_name, ); let key_attributes = self.key_info_store.get_key_attributes(&key_identity)?; let key_id = self.key_info_store.get_key_id(&key_identity)?; let session = self.new_session()?; let key = self.find_key(&session, key_id, KeyPairType::PublicKey)?; info!("Located key for export."); let data = match key_attributes.key_type { Type::RsaKeyPair | Type::RsaPublicKey => { self.export_public_rsa_internal(key, &session)? } Type::EccKeyPair { .. } | Type::EccPublicKey { .. } => { self.export_public_ec_internal(key, &session)? } _ => { return Err(ResponseStatus::PsaErrorNotSupported); } }; Ok(psa_export_public_key::Result { data: data.into() }) } fn export_public_rsa_internal(&self, key: ObjectHandle, session: &Session) -> Result> { let mut attributes = session .get_attributes( key, &[AttributeType::Modulus, AttributeType::PublicExponent], ) .map_err(to_response_status)?; if attributes.len() != 2 { error!("Expected to find modulus and public exponent attributes in public key."); return Err(ResponseStatus::PsaErrorCommunicationFailure); } let modulus = if let Attribute::Modulus(vec) = attributes.remove(0) { IntegerAsn1::from_bytes_be_unsigned(vec) } else { error!("Expected to find modulus attribute."); return Err(ResponseStatus::PsaErrorCommunicationFailure); }; let public_exponent = if let Attribute::PublicExponent(vec) = attributes.remove(0) { IntegerAsn1::from_bytes_be_unsigned(vec) } else { error!("Expected to find public exponent attribute."); return Err(ResponseStatus::PsaErrorCommunicationFailure); }; let key = RsaPublicKey { modulus, public_exponent, }; Ok(picky_asn1_der::to_vec(&key).map_err(|err| { format_error!("Could not serialise key elements", err); ResponseStatus::PsaErrorCommunicationFailure })?) } fn export_public_ec_internal(&self, key: ObjectHandle, session: &Session) -> Result> { let mut attributes = session .get_attributes(key, &[AttributeType::EcPoint]) .map_err(to_response_status)?; if attributes.len() != 1 { error!("Expected to find EC point attribute in public key."); return Err(ResponseStatus::PsaErrorCommunicationFailure); } if let Attribute::EcPoint(data) = attributes.remove(0) { // The format provided by PKCS11 is an ASN.1 OctetString containing the // data that the PSA Crypto interface expects. // See ECPoint in [SEC1](https://www.secg.org/sec1-v2.pdf). PKCS11 mandates using // [ANSI X9.62 ECPoint](https://cryptsoft.com/pkcs11doc/v220/group__SEC__12__3__3__ECDSA__PUBLIC__KEY__OBJECTS.html), // however SEC1 is an equivalent spec. let parsed_data = picky_asn1_der::from_bytes::(&data); match parsed_data { Ok(key_data) => Ok(key_data.0), // Some PKCS#11 implementations provide the EC_POINT as raw bytes and fail to wrap in ASN.1 OctetString as // mandated by the spec. If the ASN.1 parse fails, then we assume that the raw bytes are the EC_POINT data, // and return those instead of failing, trading off strictness for broader interoperability. Err(_) => Ok(data), } } else { error!("Expected to find modulus attribute."); Err(ResponseStatus::PsaErrorCommunicationFailure) } } pub(super) fn psa_destroy_key_internal( &self, application_identity: &ApplicationIdentity, op: psa_destroy_key::Operation, ) -> Result { let key_name = op.key_name; let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), key_name, ); let key_id = self.key_info_store.get_key_id(&key_identity)?; let _ = self.key_info_store.remove_key_info(&key_identity)?; let session = self.new_session()?; let first_key = self.find_key(&session, key_id, KeyPairType::Any)?; session .destroy_object(first_key) .map_err(to_response_status)?; // Second key is optional. match self.find_key(&session, key_id, KeyPairType::Any) { Ok(key) => { trace!("DestroyObject command"); session.destroy_object(key).map_err(to_response_status) } // A second key is optional. Err(ResponseStatus::PsaErrorDoesNotExist) => Ok(()), Err(e) => { format_error!("Error destroying key", e); Err(e) } }?; Ok(psa_destroy_key::Result {}) } } parsec-service-1.3.0/src/providers/pkcs11/key_metadata.rs000064400000000000000000000007601046102023000214470ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::Provider; impl Provider { pub(super) fn create_key_id(&self) -> u32 { let mut local_ids_handle = self.local_ids.write().expect("Local ID lock poisoned"); let mut key_id = rand::random::(); while local_ids_handle.contains(&key_id) { key_id = rand::random::(); } let _ = local_ids_handle.insert(key_id); key_id } } parsec-service-1.3.0/src/providers/pkcs11/mod.rs000064400000000000000000000532751046102023000176070ustar 00000000000000// Copyright 2019 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! PKCS 11 provider //! //! This provider allows clients to access any PKCS 11 compliant device //! through the Parsec interface. use super::Provide; use crate::authenticators::ApplicationIdentity; use crate::key_info_managers::{KeyIdentity, KeyInfoManagerClient}; use crate::providers::crypto_capability::CanDoCrypto; use crate::providers::ProviderIdentity; use cryptoki::context::{CInitializeArgs, Pkcs11}; use cryptoki::error::{Error as Pkcs11Error, RvError}; use cryptoki::session::{Session, UserType}; use cryptoki::slot::Slot; use cryptoki::types::AuthPin; use derivative::Derivative; use log::{error, info, trace, warn}; use parsec_interface::operations::list_providers::Uuid; use parsec_interface::operations::{ can_do_crypto, psa_asymmetric_decrypt, psa_asymmetric_encrypt, psa_destroy_key, psa_export_public_key, psa_generate_key, psa_generate_random, psa_import_key, psa_sign_hash, psa_verify_hash, }; use parsec_interface::operations::{list_clients, list_keys, list_providers::ProviderInfo}; use parsec_interface::requests::{Opcode, ProviderId, ResponseStatus, Result}; use parsec_interface::secrecy::{ExposeSecret, SecretString}; use std::collections::HashSet; use std::convert::From; use std::convert::TryFrom; use std::io::{Error, ErrorKind}; use std::str::FromStr; use std::sync::RwLock; use utils::{to_response_status, KeyPairType}; use zeroize::{Zeroize, Zeroizing}; type LocalIdStore = HashSet; mod asym_encryption; mod asym_sign; mod capability_discovery; mod generate_random; mod key_management; mod key_metadata; mod utils; const SUPPORTED_OPCODES: [Opcode; 10] = [ Opcode::PsaGenerateKey, Opcode::PsaDestroyKey, Opcode::PsaSignHash, Opcode::PsaVerifyHash, Opcode::PsaImportKey, Opcode::PsaExportPublicKey, Opcode::PsaAsymmetricDecrypt, Opcode::PsaAsymmetricEncrypt, Opcode::CanDoCrypto, Opcode::PsaGenerateRandom, ]; const PIN_STRING_PREFIX: &str = "str:"; const PIN_HEX_PREFIX: &str = "hex:"; /// Provider for Public Key Cryptography Standard #11 /// /// Operations for this provider are serviced through a PKCS11 interface, /// allowing any libraries exposing said interface to be loaded and used /// at runtime. #[derive(Derivative)] #[derivative(Debug)] pub struct Provider { // The identity of the provider including uuid & name. provider_identity: ProviderIdentity, #[derivative(Debug = "ignore")] key_info_store: KeyInfoManagerClient, local_ids: RwLock, #[derivative(Debug = "ignore")] backend: Pkcs11, slot_number: Slot, software_public_operations: bool, allow_export: bool, user_pin: Option, } impl Provider { /// The default provider name for pkcs11 provider pub const DEFAULT_PROVIDER_NAME: &'static str = "pkcs11-provider"; /// The UUID for this provider pub const PROVIDER_UUID: &'static str = "30e39502-eba6-4d60-a4af-c518b7f5e38f"; /// Creates and initialise a new instance of Pkcs11Provider. /// Checks if there are not more keys stored in the Key Info Manager than in the PKCS 11 library /// and if there are, delete them. Adds Key IDs currently in use in the local IDs store. /// Returns `None` if the initialisation failed. fn new( provider_name: String, key_info_store: KeyInfoManagerClient, backend: Pkcs11, slot_number: Slot, user_pin: Option, software_public_operations: bool, allow_export: bool, ) -> Option { #[allow(clippy::mutex_atomic)] let pkcs11_provider = Provider { provider_identity: ProviderIdentity { name: provider_name, uuid: String::from(Self::PROVIDER_UUID), }, key_info_store, local_ids: RwLock::new(HashSet::new()), backend, slot_number, software_public_operations, allow_export, user_pin, }; { let mut local_ids_handle = pkcs11_provider .local_ids .write() .expect("Local ID lock poisoned"); let mut to_remove: Vec = Vec::new(); // Go through all PKCS 11 key identities to key info mappings and check if they are still // present. // Delete those who are not present and add to the local_store the ones present. match pkcs11_provider.key_info_store.get_all() { Ok(key_identities) => { let session = pkcs11_provider.new_session().ok()?; for key_identity in key_identities.iter().cloned() { let key_id = match pkcs11_provider.key_info_store.get_key_id(&key_identity) { Ok(id) => id, Err(ResponseStatus::PsaErrorDoesNotExist) => { error!("Stored key info missing for KeyIdentity {}.", key_identity); continue; } Err(e) => { format_error!( format!( "Stored key info invalid for KeyIdentity {}.", key_identity ), e ); to_remove.push(key_identity.clone()); continue; } }; match pkcs11_provider.find_key(&session, key_id, KeyPairType::Any) { Ok(_) => { if crate::utils::GlobalConfig::log_error_details() { warn!( "Key {} found in the PKCS 11 library, adding it.", key_identity ); } else { warn!("Key found in the PKCS 11 library, adding it."); } let _ = local_ids_handle.insert(key_id); } Err(ResponseStatus::PsaErrorDoesNotExist) => { if crate::utils::GlobalConfig::log_error_details() { warn!( "Key {} not found in the PKCS 11 library, deleting it.", key_identity ); } else { warn!("Key not found in the PKCS 11 library, deleting it."); } to_remove.push(key_identity.clone()); } Err(e) => { format_error!("Error finding key objects", e); return None; } } } } Err(string) => { format_error!("Key Info Manager error", string); return None; } }; for key_identity in to_remove.iter() { if pkcs11_provider .key_info_store .remove_key_info(&key_identity) .is_err() { return None; } } } if pkcs11_provider.software_public_operations { psa_crypto::init().expect( "Failed to initialize PSA Crypto for public key operation software support", ); } Some(pkcs11_provider) } // Create a new session with the following properties: // * without callback // * read/write session // * serial session // * logged in if the pin is set // * set on the slot in the provider fn new_session(&self) -> Result { let session = self .backend .open_rw_session(self.slot_number) .map_err(to_response_status)?; if self.user_pin.is_some() { let mut pin = Zeroizing::new(self.user_pin.as_ref().unwrap().expose_secret().clone()); if pin.starts_with(PIN_HEX_PREFIX) { if let Ok(mut raw_pin) = hex::decode(pin.split_off(PIN_HEX_PREFIX.len())) { pin = Zeroizing::new(String::from_utf8_lossy(&raw_pin.as_slice()).to_string()); raw_pin.zeroize(); } } else if pin.starts_with(PIN_STRING_PREFIX) { pin = pin.split_off(PIN_STRING_PREFIX.len()).into(); } session .login(UserType::User, Some(&AuthPin::new(pin.to_string()))) .or_else(|e| { if let Pkcs11Error::Pkcs11(RvError::UserAlreadyLoggedIn) = e { Ok(()) } else { Err(e) } }) .map_err(to_response_status)?; } Ok(session) } } impl Provide for Provider { fn describe(&self) -> Result<(ProviderInfo, HashSet)> { trace!("describe ingress"); Ok(( ProviderInfo { // Assigned UUID for this provider: 30e39502-eba6-4d60-a4af-c518b7f5e38f uuid: Uuid::parse_str(Provider::PROVIDER_UUID) .or(Err(ResponseStatus::InvalidEncoding))?, description: String::from( "PKCS #11 provider, interfacing with a PKCS #11 library.", ), vendor: String::from("OASIS Standard."), version_maj: 0, version_min: 1, version_rev: 0, id: ProviderId::Pkcs11, }, SUPPORTED_OPCODES.iter().copied().collect(), )) } fn list_keys( &self, application_identity: &ApplicationIdentity, _op: list_keys::Operation, ) -> Result { trace!("list_keys ingress"); Ok(list_keys::Result { keys: self.key_info_store.list_keys(application_identity)?, }) } fn list_clients(&self, _op: list_clients::Operation) -> Result { trace!("list_clients ingress"); Ok(list_clients::Result { clients: self .key_info_store .list_clients()? .into_iter() .map(|application_identity| application_identity.name().clone()) .collect(), }) } fn psa_generate_random( &self, op: psa_generate_random::Operation, ) -> Result { trace!("psa_generate_random ingress"); self.psa_generate_random_internal(op) } fn psa_generate_key( &self, application_identity: &ApplicationIdentity, op: psa_generate_key::Operation, ) -> Result { trace!("psa_generate_key ingress"); self.psa_generate_key_internal(application_identity, op) } fn psa_import_key( &self, application_identity: &ApplicationIdentity, op: psa_import_key::Operation, ) -> Result { trace!("psa_import_key ingress"); self.psa_import_key_internal(application_identity, op) } fn psa_export_public_key( &self, application_identity: &ApplicationIdentity, op: psa_export_public_key::Operation, ) -> Result { trace!("psa_export_public_key ingress"); self.psa_export_public_key_internal(application_identity, op) } fn psa_destroy_key( &self, application_identity: &ApplicationIdentity, op: psa_destroy_key::Operation, ) -> Result { trace!("psa_destroy_key ingress"); self.psa_destroy_key_internal(application_identity, op) } fn psa_sign_hash( &self, application_identity: &ApplicationIdentity, op: psa_sign_hash::Operation, ) -> Result { trace!("psa_sign_hash ingress"); self.psa_sign_hash_internal(application_identity, op) } fn psa_verify_hash( &self, application_identity: &ApplicationIdentity, op: psa_verify_hash::Operation, ) -> Result { if self.software_public_operations { trace!("software_psa_verify_hash ingress"); self.software_psa_verify_hash_internal(application_identity, op) } else { trace!("pkcs11_psa_verify_hash ingress"); self.psa_verify_hash_internal(application_identity, op) } } fn psa_asymmetric_encrypt( &self, application_identity: &ApplicationIdentity, op: psa_asymmetric_encrypt::Operation, ) -> Result { if self.software_public_operations { trace!("software_psa_asymmetric_encrypt ingress"); self.software_psa_asymmetric_encrypt_internal(application_identity, op) } else { trace!("psa_asymmetric_encrypt ingress"); self.psa_asymmetric_encrypt_internal(application_identity, op) } } fn psa_asymmetric_decrypt( &self, application_identity: &ApplicationIdentity, op: psa_asymmetric_decrypt::Operation, ) -> Result { trace!("psa_asymmetric_decrypt ingress"); self.psa_asymmetric_decrypt_internal(application_identity, op) } /// Check if the crypto operation is supported by PKCS11 provider /// by using CanDoCrypto trait. fn can_do_crypto( &self, application_identity: &ApplicationIdentity, op: can_do_crypto::Operation, ) -> Result { trace!("can_do_crypto ingress"); self.can_do_crypto_main(application_identity, op) } } /// Builder for Pkcs11Provider /// /// This builder contains some confidential information that is passed to the Pkcs11Provider. The /// Pkcs11Provider will zeroize this data when dropping. This data will not be cloned when /// building. #[derive(Default, Derivative)] #[derivative(Debug)] pub struct ProviderBuilder { provider_name: Option, #[derivative(Debug = "ignore")] key_info_store: Option, pkcs11_library_path: Option, slot_number: Option, serial_number: Option, user_pin: Option, software_public_operations: Option, allow_export: Option, } impl ProviderBuilder { /// Create a new Pkcs11Provider builder pub fn new() -> ProviderBuilder { ProviderBuilder { provider_name: None, key_info_store: None, pkcs11_library_path: None, slot_number: None, serial_number: None, user_pin: None, software_public_operations: None, allow_export: None, } } /// Add a provider name pub fn with_provider_name(mut self, provider_name: String) -> ProviderBuilder { self.provider_name = Some(provider_name); self } /// Add a KeyInfo manager pub fn with_key_info_store(mut self, key_info_store: KeyInfoManagerClient) -> ProviderBuilder { self.key_info_store = Some(key_info_store); self } /// Specify the path of the PKCS11 library pub fn with_pkcs11_library_path(mut self, pkcs11_library_path: String) -> ProviderBuilder { self.pkcs11_library_path = Some(pkcs11_library_path); self } /// Specify the slot number used pub fn with_slot_number(mut self, slot_number: Option) -> ProviderBuilder { self.slot_number = slot_number; self } /// Specify the token serial number used pub fn with_serial_number(mut self, serial_number: Option) -> ProviderBuilder { self.serial_number = serial_number; self } /// Specify the user pin pub fn with_user_pin(mut self, mut user_pin: Option) -> ProviderBuilder { self.user_pin = match user_pin { // The conversion form a String is infallible. Some(ref pin) => Some(SecretString::from_str(&pin).unwrap()), None => None, }; user_pin.zeroize(); self } /// Specify the `software_public_operations` flag pub fn with_software_public_operations( mut self, software_public_operations: Option, ) -> ProviderBuilder { self.software_public_operations = software_public_operations; self } /// Specify the `allow_export` flag pub fn with_allow_export(mut self, allow_export: Option) -> ProviderBuilder { self.allow_export = allow_export; self } /// Attempt to build a PKCS11 provider pub fn build(self) -> std::io::Result { let library_path = self .pkcs11_library_path .ok_or_else(|| Error::new(ErrorKind::InvalidData, "missing library path"))?; info!( "Building a PKCS 11 provider with library \'{}\'", library_path ); let backend = Pkcs11::new(library_path).map_err(|e| { format_error!("Error creating a PKCS 11 context", e); Error::new(ErrorKind::InvalidData, "error creating PKCS 11 context") })?; trace!("Initialize command"); backend .initialize(CInitializeArgs::OsThreads) .map_err(|e| { format_error!("Error initializing PKCS 11 context", e); Error::new(ErrorKind::InvalidData, "error initializing PKCS 11 context") })?; let slots = backend.get_slots_with_initialized_token().map_err(|e| { format_error!( "Failed retrieving a valid slot with an initialized token", e ); Error::new( ErrorKind::InvalidData, "Failed retrieving a valid slot with an initialized token", ) })?; let slot_number = match (self.serial_number, self.slot_number) { (Some(serial_number), given_slot) => { let mut slot = None; for current_slot in slots { let current_token = backend.get_token_info(current_slot).map_err(|e| { format_error!("Failed parsing token info", e); Error::new(ErrorKind::InvalidData, "Failed parsing token info") })?; let sn = String::from_utf8(current_token.serial_number().as_bytes().to_vec()) .map_err(|e| { format_error!("Failed parsing token serial number", e); Error::new(ErrorKind::InvalidData, "Failed parsing token serial number") })?; if sn.trim() == serial_number.trim() { slot = Some(current_slot); break; } } match slot { Some(slot) => { if let Some(slot_number) = given_slot { if slot.id() != slot_number { warn!("Provided slot number mismatch!"); warn!("Token is attached to slot {}", slot.id()) } } slot } None => { return Err(Error::new( ErrorKind::InvalidData, "No token with the provided serial number", )) } } } (None, Some(slot_number)) => { let slot = Slot::try_from(slot_number).or_else(|_| { Err(Error::new( ErrorKind::InvalidData, "cannot convert slot value", )) })?; if !slots.contains(&slot) { return Err(Error::new( ErrorKind::InvalidInput, "No available slot with the given number", )); } warn!( "Slot number {} will be used. However, It is preferred to use serial_number as the slot number might change during replug or OS reboot.", slot_number ); slot } (None, None) => { if slots.len() == 1 { slots[0] } else { return Err(Error::new( ErrorKind::InvalidData, "missing slot number or more than one initialized", )); } } }; Ok(Provider::new( self.provider_name.ok_or_else(|| { std::io::Error::new(std::io::ErrorKind::InvalidData, "missing provider name") })?, self.key_info_store .ok_or_else(|| Error::new(ErrorKind::InvalidData, "missing key info store"))?, backend, slot_number, self.user_pin, self.software_public_operations.unwrap_or(false), self.allow_export.unwrap_or(true), ) .ok_or_else(|| Error::new(ErrorKind::InvalidData, "PKCS 11 initialization failed"))?) } } parsec-service-1.3.0/src/providers/pkcs11/utils.rs000064400000000000000000000216151046102023000201610ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use cryptoki::error::{Error, RvError}; use cryptoki::mechanism::rsa; use cryptoki::mechanism::Mechanism; use cryptoki::object::Attribute; use log::error; use parsec_interface::operations::psa_algorithm::{AsymmetricSignature, Hash, SignHash}; use parsec_interface::operations::psa_key_attributes::*; use parsec_interface::requests::ResponseStatus; use parsec_interface::requests::Result as ResponseResult; use picky_asn1::wrapper::ObjectIdentifierAsn1; use picky_asn1_x509::{ algorithm_identifier::EcParameters, AlgorithmIdentifier, DigestInfo, ShaVariant, }; use std::convert::TryInto; // Public exponent value for all RSA keys. pub const PUBLIC_EXPONENT: [u8; 3] = [0x01, 0x00, 0x01]; /// Convert the PKCS 11 library specific error values to ResponseStatus values that are returned on /// the wire protocol /// /// Most of them are PsaErrorCommunicationFailure as, in the general case, the calls to the PKCS11 /// library should suceed with the values crafted by the provider. /// If an error happens in the PKCS11 library, it means that it was badly used by the provider or /// that it failed in an unexpected way and hence the PsaErrorCommunicationFailure error. /// The errors translated to response status are related with signature verification failure, lack /// of memory, hardware failure, corruption detection, lack of entropy and unsupported operations. pub fn to_response_status(error: Error) -> ResponseStatus { match error { Error::LibraryLoading(e) => { format_error!("Conversion of error to PsaErrorCommunicationFailure", e); ResponseStatus::PsaErrorCommunicationFailure } Error::Pkcs11(ck_rv) => rv_to_response_status(ck_rv), Error::NotSupported => ResponseStatus::PsaErrorNotSupported, Error::TryFromInt(e) => ResponseStatus::from(e), Error::TryFromSlice(e) => ResponseStatus::from(e), Error::NulError(e) => ResponseStatus::from(e), error => { format_error!("Conversion of error to PsaErrorCommunicationFailure", error); ResponseStatus::PsaErrorCommunicationFailure } } } pub fn rv_to_response_status(rv: RvError) -> ResponseStatus { match rv { RvError::HostMemory => ResponseStatus::PsaErrorInsufficientMemory, RvError::DeviceError => ResponseStatus::PsaErrorHardwareFailure, RvError::DeviceMemory => ResponseStatus::PsaErrorInsufficientStorage, RvError::DeviceRemoved => ResponseStatus::PsaErrorHardwareFailure, RvError::SignatureInvalid => ResponseStatus::PsaErrorInvalidSignature, RvError::SignatureLenRange => ResponseStatus::PsaErrorInvalidSignature, RvError::TokenNotPresent => ResponseStatus::PsaErrorHardwareFailure, RvError::TokenNotRecognized => ResponseStatus::PsaErrorHardwareFailure, RvError::RandomNoRng => ResponseStatus::PsaErrorInsufficientEntropy, RvError::StateUnsaveable => ResponseStatus::PsaErrorHardwareFailure, s @ RvError::CurveNotSupported | s @ RvError::DomainParamsInvalid | s @ RvError::FunctionNotSupported => { if crate::utils::GlobalConfig::log_error_details() { error!("Not supported value ({:?})", s); } ResponseStatus::PsaErrorNotSupported } e => { format_error!("Error converted to PsaErrorCommunicationFailure", e); ResponseStatus::PsaErrorCommunicationFailure } } } // For PKCS 11, a key pair consists of two independant public and private keys. Both will share the // same key ID. pub enum KeyPairType { PublicKey, PrivateKey, Any, } pub fn key_pair_usage_flags_to_pkcs11_attributes( usage_flags: UsageFlags, pub_template: &mut Vec, priv_template: &mut Vec, ) { priv_template.push(Attribute::Sign( (usage_flags.sign_hash() || usage_flags.sign_message()).into(), )); pub_template.push(Attribute::Verify( (usage_flags.verify_hash() || usage_flags.verify_message()).into(), )); pub_template.push(Attribute::Encrypt((usage_flags.encrypt()).into())); priv_template.push(Attribute::Decrypt((usage_flags.decrypt()).into())); priv_template.push(Attribute::Derive((usage_flags.derive()).into())); priv_template.push(Attribute::Extractable((usage_flags.export()).into())); priv_template.push(Attribute::Sensitive((!usage_flags.export()).into())); priv_template.push(Attribute::Copyable((usage_flags.copy()).into())); pub_template.push(Attribute::Copyable((usage_flags.copy()).into())); } /// Format the input data into ASN1 DigestInfo bytes pub fn digest_info(alg: AsymmetricSignature, hash: Vec) -> ResponseResult> { let oid = match alg { AsymmetricSignature::RsaPkcs1v15Sign { hash_alg: SignHash::Specific(Hash::Sha224), } => AlgorithmIdentifier::new_sha(ShaVariant::SHA2_224), AsymmetricSignature::RsaPkcs1v15Sign { hash_alg: SignHash::Specific(Hash::Sha256), } => AlgorithmIdentifier::new_sha(ShaVariant::SHA2_256), AsymmetricSignature::RsaPkcs1v15Sign { hash_alg: SignHash::Specific(Hash::Sha384), } => AlgorithmIdentifier::new_sha(ShaVariant::SHA2_384), AsymmetricSignature::RsaPkcs1v15Sign { hash_alg: SignHash::Specific(Hash::Sha512), } => AlgorithmIdentifier::new_sha(ShaVariant::SHA2_512), _ => return Err(ResponseStatus::PsaErrorNotSupported), }; picky_asn1_der::to_vec(&DigestInfo { oid, digest: hash.into(), }) // should not fail - if it does, there's some error in our stack .map_err(|_| ResponseStatus::PsaErrorGenericError) } pub fn ec_params(ecc_family: EccFamily, bits: usize) -> ResponseResult { Ok(EcParameters::NamedCurve(match (ecc_family, bits) { // The following "unwrap()" should be ok, as they cover constant conversions (EccFamily::SecpR1, 192) => { ObjectIdentifierAsn1(String::from("1.2.840.10045.3.1.1").try_into().unwrap()) } (EccFamily::SecpR1, 224) => { ObjectIdentifierAsn1(String::from("1.3.132.0.33").try_into().unwrap()) } (EccFamily::SecpR1, 256) => { ObjectIdentifierAsn1(String::from("1.2.840.10045.3.1.7").try_into().unwrap()) } (EccFamily::SecpR1, 384) => { ObjectIdentifierAsn1(String::from("1.3.132.0.34").try_into().unwrap()) } (EccFamily::SecpR1, 521) => { ObjectIdentifierAsn1(String::from("1.3.132.0.35").try_into().unwrap()) } _ => return Err(ResponseStatus::PsaErrorNotSupported), })) } #[allow(deprecated)] /// Convert a PSA Crypto Hash algorithm to a MGF type pub fn pkcsmgftype_from_psa_crypto_hash(alg: Hash) -> Result { match alg { Hash::Sha1 => Ok(rsa::PkcsMgfType::MGF1_SHA1), Hash::Sha224 => Ok(rsa::PkcsMgfType::MGF1_SHA224), Hash::Sha256 => Ok(rsa::PkcsMgfType::MGF1_SHA256), Hash::Sha384 => Ok(rsa::PkcsMgfType::MGF1_SHA384), Hash::Sha512 => Ok(rsa::PkcsMgfType::MGF1_SHA512), alg => { error!("{:?} is not a supported MGF1 algorithm", alg); Err(Error::NotSupported) } } } #[allow(deprecated)] pub fn algorithm_to_mechanism<'a>( alg: psa_crypto::types::algorithm::Algorithm, ) -> Result, Error> { use psa_crypto::types::algorithm::{Algorithm, AsymmetricEncryption}; match alg { Algorithm::Hash(Hash::Sha1) => Ok(Mechanism::Sha1), Algorithm::Hash(Hash::Sha256) => Ok(Mechanism::Sha256), Algorithm::Hash(Hash::Sha384) => Ok(Mechanism::Sha384), Algorithm::Hash(Hash::Sha512) => Ok(Mechanism::Sha512), Algorithm::AsymmetricSignature(AsymmetricSignature::RsaPkcs1v15Sign { .. }) | Algorithm::AsymmetricEncryption(AsymmetricEncryption::RsaPkcs1v15Crypt { .. }) => { Ok(Mechanism::RsaPkcs) } Algorithm::AsymmetricSignature(AsymmetricSignature::RsaPss { hash_alg: SignHash::Specific(hash_alg), }) => Ok(Mechanism::RsaPkcsPss(rsa::PkcsPssParams { hash_alg: algorithm_to_mechanism(Algorithm::from(hash_alg))?.mechanism_type(), mgf: pkcsmgftype_from_psa_crypto_hash(hash_alg)?, s_len: hash_alg.hash_length().try_into()?, })), Algorithm::AsymmetricSignature(AsymmetricSignature::Ecdsa { .. }) => Ok(Mechanism::Ecdsa), Algorithm::AsymmetricEncryption(AsymmetricEncryption::RsaOaep { hash_alg }) => { Ok(Mechanism::from(rsa::PkcsOaepParams::new( algorithm_to_mechanism(Algorithm::from(hash_alg))?.mechanism_type(), pkcsmgftype_from_psa_crypto_hash(hash_alg)?, rsa::PkcsOaepSource::empty(), ))) } alg => { error!("{:?} is not a supported algorithm", alg); Err(Error::NotSupported) } } } parsec-service-1.3.0/src/providers/tpm/asym_encryption.rs000064400000000000000000000123571046102023000217450ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::{utils, Provider}; use crate::authenticators::ApplicationIdentity; use crate::key_info_managers::KeyIdentity; use parsec_interface::operations::psa_algorithm::{Algorithm, AsymmetricEncryption}; use parsec_interface::operations::{psa_asymmetric_decrypt, psa_asymmetric_encrypt}; use parsec_interface::requests::{ResponseStatus, Result}; use std::convert::TryInto; use std::ops::Deref; use tss_esapi::{constants::Tss2ResponseCodeKind, Error}; impl Provider { pub(super) fn psa_asymmetric_encrypt_internal( &self, application_identity: &ApplicationIdentity, op: psa_asymmetric_encrypt::Operation, ) -> Result { let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), op.key_name.clone(), ); let password_context = self.get_key_ctx(&key_identity)?; let key_attributes = self.key_info_store.get_key_attributes(&key_identity)?; let mut esapi_context = self .esapi_context .lock() .expect("ESAPI Context lock poisoned"); op.validate(key_attributes)?; match esapi_context.rsa_encrypt( password_context.key_material().clone(), utils::parsec_to_tpm_params(key_attributes)?, Some( password_context .auth_value() .try_into() .map_err(utils::to_response_status)?, ), op.plaintext .deref() .clone() .try_into() .map_err(utils::to_response_status)?, match op.salt { Some(salt) => Some( salt.deref() .to_vec() .try_into() .map_err(utils::to_response_status)?, ), None => None, }, ) { Ok(ciphertext) => Ok(psa_asymmetric_encrypt::Result { ciphertext: ciphertext.value().to_vec().into(), }), Err(tss_error) => { let error = utils::to_response_status(tss_error); format_error!("Encryption failed", tss_error); Err(error) } } } pub(super) fn psa_asymmetric_decrypt_internal( &self, application_identity: &ApplicationIdentity, op: psa_asymmetric_decrypt::Operation, ) -> Result { let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), op.key_name.clone(), ); let password_context = self.get_key_ctx(&key_identity)?; let key_attributes = self.key_info_store.get_key_attributes(&key_identity)?; let mut esapi_context = self .esapi_context .lock() .expect("ESAPI Context lock poisoned"); op.validate(key_attributes)?; match esapi_context.rsa_decrypt( password_context.key_material().clone(), utils::parsec_to_tpm_params(key_attributes)?, Some( password_context .auth_value() .try_into() .map_err(utils::to_response_status)?, ), op.ciphertext .deref() .clone() .try_into() .map_err(utils::to_response_status)?, match op.salt { Some(salt) => Some( salt.deref() .to_vec() .try_into() .map_err(utils::to_response_status)?, ), None => None, }, ) { Ok(plaintext) => Ok(psa_asymmetric_decrypt::Result { plaintext: plaintext.value().to_vec().into(), }), Err(tss_error) => { // If the algorithm is RSA with PKCS#1 v1.5 padding and we get TPM_RC_VALUE back, // it means the padding has been deemed invalid and we should let the caller know // about that. This allows clients to mitigate attacks that leverage padding // oracles a la Bleichenbacher. // See https://cryptosense.com/blog/why-pkcs1v1-5-encryption-should-be-put-out-of-our-misery // for more details. if let Algorithm::AsymmetricEncryption(AsymmetricEncryption::RsaPkcs1v15Crypt) = key_attributes.policy.permitted_algorithms { if let Error::Tss2Error(e) = tss_error { if Some(Tss2ResponseCodeKind::Value) == e.kind() { format_error!("Wrong plaintext padding", tss_error); return Err(ResponseStatus::PsaErrorInvalidPadding); } } } let error = utils::to_response_status(tss_error); format_error!("Encryption failed", tss_error); Err(error) } } } } parsec-service-1.3.0/src/providers/tpm/asym_sign.rs000064400000000000000000000105441046102023000205070ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::{utils, Provider}; use crate::authenticators::ApplicationIdentity; use crate::key_info_managers::KeyIdentity; use log::error; use parsec_interface::operations::psa_algorithm::*; use parsec_interface::operations::{psa_sign_hash, psa_verify_hash}; use parsec_interface::requests::{ResponseStatus, Result}; use std::convert::TryFrom; use tss_esapi::structures::{Auth, Digest}; impl Provider { pub(super) fn psa_sign_hash_internal( &self, application_identity: &ApplicationIdentity, op: psa_sign_hash::Operation, ) -> Result { let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), op.key_name.clone(), ); let password_context = self.get_key_ctx(&key_identity)?; let key_attributes = self.key_info_store.get_key_attributes(&key_identity)?; let mut esapi_context = self .esapi_context .lock() .expect("ESAPI Context lock poisoned"); match op.alg { AsymmetricSignature::RsaPkcs1v15Sign { .. } => (), AsymmetricSignature::Ecdsa { .. } => (), _ => { if crate::utils::GlobalConfig::log_error_details() { error!( "Requested algorithm is not supported by the TPM provider: {:?}", op.alg ); } else { error!("Requested algorithm is not supported by the TPM provider"); } return Err(ResponseStatus::PsaErrorNotSupported); } } op.validate(key_attributes)?; let signature = esapi_context .sign( password_context.key_material().clone(), utils::parsec_to_tpm_params(key_attributes)?, Some( Auth::try_from(password_context.auth_value()) .map_err(utils::to_response_status)?, ), Digest::try_from((*op.hash).clone()).map_err(utils::to_response_status)?, ) .map_err(|e| { if crate::utils::GlobalConfig::log_error_details() { error!("Error signing: {}.", e); } utils::to_response_status(e) })?; Ok(psa_sign_hash::Result { signature: utils::signature_data_to_bytes(signature, key_attributes)?.into(), }) } pub(super) fn psa_verify_hash_internal( &self, application_identity: &ApplicationIdentity, op: psa_verify_hash::Operation, ) -> Result { let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), op.key_name.clone(), ); let password_context = self.get_key_ctx(&key_identity)?; let key_attributes = self.key_info_store.get_key_attributes(&key_identity)?; let mut esapi_context = self .esapi_context .lock() .expect("ESAPI Context lock poisoned"); match op.alg { AsymmetricSignature::RsaPkcs1v15Sign { .. } => (), AsymmetricSignature::Ecdsa { .. } => (), _ => { if crate::utils::GlobalConfig::log_error_details() { error!( "Requested algorithm is not supported by the TPM provider: {:?}", op.alg ); } else { error!("Requested algorithm is not supported by the TPM provider"); } return Err(ResponseStatus::PsaErrorNotSupported); } } op.validate(key_attributes)?; let signature = utils::parsec_to_tpm_signature(op.signature, key_attributes, op.alg)?; let _ = esapi_context .verify_signature( password_context.key_material().clone(), utils::parsec_to_tpm_params(key_attributes)?, Digest::try_from((*op.hash).clone()).map_err(utils::to_response_status)?, signature, ) .map_err(utils::to_response_status)?; Ok(psa_verify_hash::Result {}) } } parsec-service-1.3.0/src/providers/tpm/capability_discovery.rs000064400000000000000000000052041046102023000227230ustar 00000000000000// Copyright 2021 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::{utils, Provider}; use crate::authenticators::ApplicationIdentity; use crate::providers::crypto_capability::CanDoCrypto; use log::{info, trace}; use parsec_interface::operations::can_do_crypto; use parsec_interface::operations::psa_key_attributes::{Attributes, Type}; use parsec_interface::requests::ResponseStatus::PsaErrorNotSupported; use parsec_interface::requests::Result; impl CanDoCrypto for Provider { fn can_do_crypto_internal( &self, _application_identity: &ApplicationIdentity, op: can_do_crypto::Operation, ) -> Result { trace!("can_do_crypto_internal"); // Check attributes compatibility with the provider match op.attributes.key_type { Type::RsaKeyPair | Type::RsaPublicKey => { let _ = utils::rsa_key_bits(op.attributes.bits).map_err(|_| PsaErrorNotSupported)?; Ok(can_do_crypto::Result) } Type::EccKeyPair { .. } | Type::EccPublicKey { .. } => { let _ = utils::convert_curve_to_tpm(op.attributes)?; Ok(can_do_crypto::Result) } _ => { info!("Unsupported key type {:?}", op.attributes.key_type); Err(PsaErrorNotSupported) } } } fn use_check_internal(&self, attributes: Attributes) -> Result { trace!("use_check_internal"); let _ = utils::parsec_to_tpm_params(attributes).map_err(|_| PsaErrorNotSupported)?; // TO-DO we also need to check capabilities of used TMP module. // TPM_GetCapability support in the tss-esapi crate is required. Ok(can_do_crypto::Result) } fn generate_check_internal(&self, attributes: Attributes) -> Result { trace!("generate_check_internal"); match attributes.key_type { Type::RsaKeyPair | Type::EccKeyPair { .. } => Ok(can_do_crypto::Result), _ => { info!("Unsupported key type {:?}", attributes.key_type); Err(PsaErrorNotSupported) } } } fn import_check_internal(&self, attributes: Attributes) -> Result { trace!("import_check_internal"); match attributes.key_type { Type::RsaPublicKey | Type::EccPublicKey { .. } => Ok(can_do_crypto::Result), _ => { info!("Unsupported key type {:?}", attributes.key_type); Err(PsaErrorNotSupported) } } } } parsec-service-1.3.0/src/providers/tpm/generate_random.rs000064400000000000000000000017351046102023000216520ustar 00000000000000// Copyright 2022 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::utils; use super::Provider; use parsec_interface::operations::psa_generate_random; use parsec_interface::requests::Result; impl Provider { pub(super) fn psa_generate_random_internal( &self, op: psa_generate_random::Operation, ) -> Result { let size = op.size; let mut esapi_context = self .esapi_context .lock() .expect("ESAPI Context lock poisoned"); let random_bytes = esapi_context .as_mut() .execute_without_session(|esapi_context| esapi_context.get_random(size)) .map_err(|e| { format_error!("Failed to get random bytes", e); utils::to_response_status(e) })?; Ok(psa_generate_random::Result { random_bytes: random_bytes.value().to_vec().into(), }) } } parsec-service-1.3.0/src/providers/tpm/key_attestation.rs000064400000000000000000000150651046102023000217300ustar 00000000000000// Copyright 2021 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::{utils, Provider}; use crate::authenticators::ApplicationIdentity; use crate::key_info_managers::KeyIdentity; use log::error; use parsec_interface::operations::{attest_key, prepare_key_attestation}; use parsec_interface::requests::{ResponseStatus, Result}; use parsec_interface::secrecy::zeroize::Zeroizing; use std::convert::TryFrom; use tss_esapi::constants::response_code::Tss2ResponseCodeKind; use tss_esapi::Error; use tss_esapi::{abstraction::transient::ObjectWrapper, structures::Auth}; impl Provider { pub(super) fn prepare_key_attestation_internal( &self, application_identity: &ApplicationIdentity, op: prepare_key_attestation::Operation, ) -> Result { match op { prepare_key_attestation::Operation::ActivateCredential { attested_key_name, attesting_key_name, } => self.prepare_activate_credential( application_identity, attested_key_name, attesting_key_name, ), _ => { error!("Key attestation mechanism is not supported"); Err(ResponseStatus::PsaErrorNotSupported) } } } // Get the parameters required for a MakeCredential operation // // If the `attesting_key_name` is not given, a default, RSA decryption // Endorsement Key will be used. fn prepare_activate_credential( &self, application_identity: &ApplicationIdentity, attested_key_name: String, attesting_key_name: Option, ) -> Result { if attesting_key_name.is_some() { error!("Attesting with a non-default key is currently not supported"); return Err(ResponseStatus::PsaErrorNotSupported); } let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), attested_key_name, ); let pass_context = self.get_key_ctx(&key_identity)?; let key_attributes = self.key_info_store.get_key_attributes(&key_identity)?; let params = utils::parsec_to_tpm_params(key_attributes)?; let auth = Some( Auth::try_from(pass_context.auth_value().to_vec()) .map_err(utils::to_response_status)?, ); let attested_key = ObjectWrapper { material: pass_context.key_material().clone(), auth, params, }; let mut esapi_context = self .esapi_context .lock() .expect("ESAPI Context lock poisoned"); let params = esapi_context .get_make_cred_params(attested_key, None) .map_err(|e| { format_error!("Failed to get MakeCredential parameters", e); key_attest_response_status(e) })?; Ok(prepare_key_attestation::Result::ActivateCredential { name: params.name.into(), attesting_key_pub: utils::ek_pub_key_to_bytes(params.attesting_key_pub)?.into(), public: params.public.into(), }) } pub(super) fn attest_key_internal( &self, application_identity: &ApplicationIdentity, op: attest_key::Operation, ) -> Result { match op { attest_key::Operation::ActivateCredential { attested_key_name, attesting_key_name, credential_blob, secret, } => self.activate_credential( application_identity, attested_key_name, attesting_key_name, credential_blob, secret, ), _ => { error!("Key attestation mechanism is not supported"); Err(ResponseStatus::PsaErrorNotSupported) } } } fn activate_credential( &self, application_identity: &ApplicationIdentity, attested_key_name: String, attesting_key_name: Option, credential_blob: Zeroizing>, secret: Zeroizing>, ) -> Result { if attesting_key_name.is_some() { error!("Attesting with a non-default key is currently not supported"); return Err(ResponseStatus::PsaErrorNotSupported); } let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), attested_key_name, ); let pass_context = self.get_key_ctx(&key_identity)?; let key_attributes = self.key_info_store.get_key_attributes(&key_identity)?; let params = utils::parsec_to_tpm_params(key_attributes)?; let auth = Some( Auth::try_from(pass_context.auth_value().to_vec()) .map_err(utils::to_response_status)?, ); let attested_key = ObjectWrapper { material: pass_context.key_material().clone(), auth, params, }; let mut esapi_context = self .esapi_context .lock() .expect("ESAPI Context lock poisoned"); let credential = esapi_context .activate_credential( attested_key, None, credential_blob.to_vec(), secret.to_vec(), ) .map_err(|e| { format_error!("Failed to activate credential", e); key_attest_response_status(e) })?; Ok(attest_key::Result::ActivateCredential { credential: credential.into(), }) } } fn key_attest_response_status(error: Error) -> ResponseStatus { match error { Error::Tss2Error(e) => match e.kind() { Some(Tss2ResponseCodeKind::BadAuth) => { error!("Wrong authentication value for attesting key"); ResponseStatus::PsaErrorGenericError } Some(Tss2ResponseCodeKind::Value) => { error!("Wrong parameter value for key attestation"); ResponseStatus::PsaErrorInvalidArgument } Some(Tss2ResponseCodeKind::Size) => { error!("Wrong parameter size for key attestation"); ResponseStatus::PsaErrorInvalidArgument } _ => utils::to_response_status(error), }, _ => utils::to_response_status(error), } } parsec-service-1.3.0/src/providers/tpm/key_management.rs000064400000000000000000000167101046102023000215030ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::utils; #[allow(deprecated)] use super::utils::LegacyPasswordContext; use super::utils::PasswordContext; use super::Provider; use crate::authenticators::ApplicationIdentity; use crate::key_info_managers::KeyIdentity; use log::error; use parsec_interface::operations::psa_key_attributes::*; use parsec_interface::operations::utils_deprecated_primitives::CheckDeprecated; use parsec_interface::operations::{ psa_destroy_key, psa_export_public_key, psa_generate_key, psa_import_key, }; use parsec_interface::requests::{ResponseStatus, Result}; use parsec_interface::secrecy::ExposeSecret; use std::convert::TryInto; const AUTH_VAL_LEN: usize = 32; impl Provider { #[allow(deprecated)] pub(super) fn get_key_ctx(&self, key_identity: &KeyIdentity) -> Result { // Try to deserialize into the new format self.key_info_store .get_key_id::(key_identity) .or_else(|e| { // If it failed, check if it was a deserialization error if let ResponseStatus::InvalidEncoding = e { // Try to deserialize into legacy format let legacy_ctx = self .key_info_store .get_key_id::(key_identity)?; // Try to migrate the key context to the new format let mut esapi_context = self .esapi_context .lock() .expect("ESAPI Context lock poisoned"); let password_ctx = PasswordContext::new( esapi_context .migrate_key_from_ctx( legacy_ctx.context, Some( legacy_ctx .auth_value .clone() .try_into() .map_err(utils::to_response_status)?, ), ) .map_err(utils::to_response_status)?, legacy_ctx.auth_value, ); // Grab key attributes and replace legacy entry with new one let attributes = self.key_info_store.get_key_attributes(key_identity)?; self.key_info_store.replace_key_info( key_identity.clone(), &password_ctx, attributes, )?; Ok(password_ctx) } else { Err(e) } }) } pub(super) fn psa_generate_key_internal( &self, application_identity: &ApplicationIdentity, op: psa_generate_key::Operation, ) -> Result { return_on_deprecated!(op, "The key requested to generate is deprecated"); let key_name = op.key_name; let attributes = op.attributes; let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), key_name, ); self.key_info_store.does_not_exist(&key_identity)?; if op.attributes.key_type.is_public_key() { error!("A public key type can not be generated."); return Err(ResponseStatus::PsaErrorInvalidArgument); } let mut esapi_context = self .esapi_context .lock() .expect("ESAPI Context lock poisoned"); let (key_material, auth_value) = esapi_context .create_key(utils::parsec_to_tpm_params(attributes)?, AUTH_VAL_LEN) .map_err(|e| { format_error!("Error creating a RSA signing key", e); utils::to_response_status(e) })?; // We hardcode the AUTH_VAL_LEN, so we can assume there is an auth_value let auth_value = auth_value.unwrap(); self.key_info_store.insert_key_info( key_identity, &PasswordContext::new(key_material, auth_value.value().to_vec()), attributes, )?; Ok(psa_generate_key::Result {}) } pub(super) fn psa_import_key_internal( &self, application_identity: &ApplicationIdentity, op: psa_import_key::Operation, ) -> Result { warn_on_deprecated!(op, "The key requested to import is deprecated"); match op.attributes.key_type { Type::RsaPublicKey | Type::EccPublicKey { .. } => (), _ => { error!( "The TPM provider does not support importing for the {:?} key type.", op.attributes.key_type ); return Err(ResponseStatus::PsaErrorNotSupported); } } let key_name = op.key_name; let attributes = op.attributes; let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), key_name, ); let key_data = op.data; self.key_info_store.does_not_exist(&key_identity)?; let mut esapi_context = self .esapi_context .lock() .expect("ESAPI Context lock poisoned"); let attributes = utils::adjust_attributes_key_bits(attributes, key_data.expose_secret())?; let key_params = utils::parsec_to_tpm_params(attributes)?; let pub_key = utils::bytes_to_pub_key(key_data.expose_secret().to_vec(), &attributes)?; let key_material = esapi_context .load_external_public_key(pub_key, key_params) .map_err(|e| { format_error!("Error creating a RSA signing key", e); utils::to_response_status(e) })?; self.key_info_store.insert_key_info( key_identity, &PasswordContext::new(key_material, Vec::new()), attributes, )?; Ok(psa_import_key::Result {}) } pub(super) fn psa_export_public_key_internal( &self, application_identity: &ApplicationIdentity, op: psa_export_public_key::Operation, ) -> Result { let key_name = op.key_name; let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), key_name, ); let password_context = self.get_key_ctx(&key_identity)?; let key_attributes = self.key_info_store.get_key_attributes(&key_identity)?; Ok(psa_export_public_key::Result { data: utils::pub_key_to_bytes( password_context.key_material().public().clone(), key_attributes, )? .into(), }) } pub(super) fn psa_destroy_key_internal( &self, application_identity: &ApplicationIdentity, op: psa_destroy_key::Operation, ) -> Result { let key_name = op.key_name; let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), key_name, ); self.key_info_store.remove_key_info(&key_identity)?; Ok(psa_destroy_key::Result {}) } } parsec-service-1.3.0/src/providers/tpm/mod.rs000064400000000000000000000365111046102023000172770ustar 00000000000000// Copyright 2019 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! TPM 2.0 provider //! //! Provider allowing clients to use hardware or software TPM 2.0 implementations //! for their Parsec operations. use super::Provide; use crate::authenticators::ApplicationIdentity; use crate::key_info_managers::KeyInfoManagerClient; use crate::providers::crypto_capability::CanDoCrypto; use crate::providers::ProviderIdentity; use derivative::Derivative; use log::{info, trace}; use parsec_interface::operations::list_providers::Uuid; use parsec_interface::operations::{ attest_key, can_do_crypto, prepare_key_attestation, psa_asymmetric_decrypt, psa_asymmetric_encrypt, psa_destroy_key, psa_export_public_key, psa_generate_key, psa_generate_random, psa_import_key, psa_sign_hash, psa_verify_hash, }; use parsec_interface::operations::{list_clients, list_keys, list_providers::ProviderInfo}; use parsec_interface::requests::{Opcode, ProviderId, ResponseStatus, Result}; use std::collections::HashSet; use std::io::ErrorKind; use std::str::FromStr; use std::sync::Mutex; use tss_esapi::interface_types::algorithm::HashingAlgorithm; use tss_esapi::interface_types::resource_handles::Hierarchy; use tss_esapi::structures::{SymmetricCipherParameters, SymmetricDefinitionObject}; use tss_esapi::Tcti; use zeroize::Zeroize; mod asym_encryption; mod asym_sign; mod capability_discovery; mod generate_random; mod key_attestation; mod key_management; mod utils; const SUPPORTED_OPCODES: [Opcode; 12] = [ Opcode::PsaGenerateKey, Opcode::PsaGenerateRandom, Opcode::PsaDestroyKey, Opcode::PsaSignHash, Opcode::PsaVerifyHash, Opcode::PsaImportKey, Opcode::PsaExportPublicKey, Opcode::PsaAsymmetricDecrypt, Opcode::PsaAsymmetricEncrypt, Opcode::CanDoCrypto, Opcode::AttestKey, Opcode::PrepareKeyAttestation, ]; const ROOT_KEY_SIZE: u16 = 2048; const ROOT_KEY_AUTH_SIZE: usize = 32; const AUTH_STRING_PREFIX: &str = "str:"; const AUTH_HEX_PREFIX: &str = "hex:"; /// Provider for Trusted Platform Modules /// /// Operations for this provider are serviced using the TPM 2.0 software stack, /// on top of the Enhanced System API. This implementation can be used with any /// implementation compliant with the specification, be it hardware or software /// (e.g. firmware TPMs). #[derive(Derivative)] #[derivative(Debug)] pub struct Provider { // The identity of the provider including uuid & name. provider_identity: ProviderIdentity, // The Mutex is needed both because interior mutability is needed to the ESAPI Context // structure that is shared between threads and because two threads are not allowed the same // ESAPI context simultaneously. esapi_context: Mutex, // The Key Info Manager stores the key context and its associated authValue (a PasswordContext // structure). #[derivative(Debug = "ignore")] key_info_store: KeyInfoManagerClient, } impl Provider { /// The default provider name for tpm provider pub const DEFAULT_PROVIDER_NAME: &'static str = "tpm-provider"; /// The UUID for this provider pub const PROVIDER_UUID: &'static str = "1e4954a4-ff21-46d3-ab0c-661eeb667e1d"; // Creates and initialise a new instance of TpmProvider. fn new( provider_name: String, key_info_store: KeyInfoManagerClient, esapi_context: tss_esapi::TransientKeyContext, ) -> Provider { Provider { provider_identity: ProviderIdentity { name: provider_name, uuid: String::from(Self::PROVIDER_UUID), }, esapi_context: Mutex::new(esapi_context), key_info_store, } } } impl Provide for Provider { fn describe(&self) -> Result<(ProviderInfo, HashSet)> { trace!("describe ingress"); Ok((ProviderInfo { // Assigned UUID for this provider: 1e4954a4-ff21-46d3-ab0c-661eeb667e1d uuid: Uuid::parse_str(Provider::PROVIDER_UUID).or(Err(ResponseStatus::InvalidEncoding))?, description: String::from("TPM provider, interfacing with a library implementing the TCG TSS 2.0 Enhanced System API specification."), vendor: String::from("Trusted Computing Group (TCG)"), version_maj: 0, version_min: 1, version_rev: 0, id: ProviderId::Tpm, }, SUPPORTED_OPCODES.iter().copied().collect())) } fn list_keys( &self, application_identity: &ApplicationIdentity, _op: list_keys::Operation, ) -> Result { trace!("list_keys ingress"); Ok(list_keys::Result { keys: self.key_info_store.list_keys(application_identity)?, }) } fn list_clients(&self, _op: list_clients::Operation) -> Result { trace!("list_clients ingress"); Ok(list_clients::Result { clients: self .key_info_store .list_clients()? .into_iter() .map(|application_identity| application_identity.name().clone()) .collect(), }) } fn psa_generate_random( &self, op: psa_generate_random::Operation, ) -> Result { trace!("psa_generate_random ingress"); self.psa_generate_random_internal(op) } fn psa_generate_key( &self, application_identity: &ApplicationIdentity, op: psa_generate_key::Operation, ) -> Result { trace!("psa_generate_key ingress"); self.psa_generate_key_internal(application_identity, op) } fn psa_import_key( &self, application_identity: &ApplicationIdentity, op: psa_import_key::Operation, ) -> Result { trace!("psa_import_key ingress"); self.psa_import_key_internal(application_identity, op) } fn psa_export_public_key( &self, application_identity: &ApplicationIdentity, op: psa_export_public_key::Operation, ) -> Result { trace!("psa_export_public_key ingress"); self.psa_export_public_key_internal(application_identity, op) } fn psa_destroy_key( &self, application_identity: &ApplicationIdentity, op: psa_destroy_key::Operation, ) -> Result { trace!("psa_destroy_key ingress"); self.psa_destroy_key_internal(application_identity, op) } fn psa_sign_hash( &self, application_identity: &ApplicationIdentity, op: psa_sign_hash::Operation, ) -> Result { trace!("psa_sign_hash ingress"); self.psa_sign_hash_internal(application_identity, op) } fn psa_verify_hash( &self, application_identity: &ApplicationIdentity, op: psa_verify_hash::Operation, ) -> Result { trace!("psa_verify_hash ingress"); self.psa_verify_hash_internal(application_identity, op) } fn psa_asymmetric_encrypt( &self, application_identity: &ApplicationIdentity, op: psa_asymmetric_encrypt::Operation, ) -> Result { trace!("psa_asymmetric_encrypt ingress"); self.psa_asymmetric_encrypt_internal(application_identity, op) } fn psa_asymmetric_decrypt( &self, application_identity: &ApplicationIdentity, op: psa_asymmetric_decrypt::Operation, ) -> Result { trace!("psa_asymmetric_decrypt ingress"); self.psa_asymmetric_decrypt_internal(application_identity, op) } /// Check if the crypto operation is supported by TPM provider /// by using CanDoCrypto trait. fn can_do_crypto( &self, application_identity: &ApplicationIdentity, op: can_do_crypto::Operation, ) -> Result { trace!("can_do_crypto TPM ingress"); self.can_do_crypto_main(application_identity, op) } fn prepare_key_attestation( &self, application_identity: &ApplicationIdentity, op: prepare_key_attestation::Operation, ) -> Result { trace!("prepare_key_attestation ingress"); self.prepare_key_attestation_internal(application_identity, op) } fn attest_key( &self, application_identity: &ApplicationIdentity, op: attest_key::Operation, ) -> Result { trace!("attest_key ingress"); self.attest_key_internal(application_identity, op) } } impl Drop for Provider { fn drop(&mut self) { info!("Dropping the TPM Provider."); } } /// Builder for TpmProvider /// /// This builder contains some confidential information that is passed to the TpmProvider. The /// TpmProvider will zeroize this data when dropping. This data will not be cloned when /// building. #[derive(Default, Derivative)] #[derivative(Debug)] pub struct ProviderBuilder { provider_name: Option, #[derivative(Debug = "ignore")] key_info_store: Option, tcti: Option, owner_hierarchy_auth: Option, endorsement_hierarchy_auth: Option, } impl ProviderBuilder { /// Create a new TPM provider builder pub fn new() -> ProviderBuilder { ProviderBuilder { provider_name: None, key_info_store: None, tcti: None, owner_hierarchy_auth: None, endorsement_hierarchy_auth: None, } } /// Add a provider name pub fn with_provider_name(mut self, provider_name: String) -> ProviderBuilder { self.provider_name = Some(provider_name); self } /// Add a KeyInfo manager pub fn with_key_info_store(mut self, key_info_store: KeyInfoManagerClient) -> ProviderBuilder { self.key_info_store = Some(key_info_store); self } /// Specify the TCTI used for this provider pub fn with_tcti(mut self, tcti: &str) -> ProviderBuilder { self.tcti = Some(tcti.to_owned()); self } /// Specify the owner hierarchy authentication to use pub fn with_owner_hierarchy_auth(mut self, owner_hierarchy_auth: String) -> ProviderBuilder { self.owner_hierarchy_auth = Some(owner_hierarchy_auth); self } /// Specify the endorsement hierarchy authentication to use pub fn with_endorsement_hierarchy_auth( mut self, endorsement_hierarchy_auth: String, ) -> ProviderBuilder { self.endorsement_hierarchy_auth = Some(endorsement_hierarchy_auth); self } fn get_hierarchy_auth(&mut self, mut auth: Option) -> std::io::Result> { match auth.take() { None => Err(std::io::Error::new( ErrorKind::InvalidData, "missing owner hierarchy auth", )), Some(mut auth) if auth.starts_with(AUTH_STRING_PREFIX) => { Ok(auth.split_off(AUTH_STRING_PREFIX.len()).into()) } Some(mut auth) if auth.starts_with(AUTH_HEX_PREFIX) => Ok(hex::decode( auth.split_off(AUTH_STRING_PREFIX.len()), ) .map_err(|_| { std::io::Error::new(ErrorKind::InvalidData, "invalid hex owner hierarchy auth") })?), Some(auth) => Ok(auth.into()), } } /// Identify the best cipher for our needs supported by the TPM. /// /// The algorithms sought are the following, in the given order: /// * AES-256 in CFB mode /// * AES-128 in CFB mode /// /// The method is unsafe because it relies on creating a TSS Context which could cause /// undefined behaviour if multiple such contexts are opened concurrently. unsafe fn find_default_context_cipher(&self) -> std::io::Result { info!("Checking for ciphers supported by the TPM."); let ciphers = [ SymmetricDefinitionObject::AES_256_CFB, SymmetricDefinitionObject::AES_128_CFB, ]; let mut ctx = tss_esapi::Context::new( Tcti::from_str(self.tcti.as_ref().ok_or_else(|| { std::io::Error::new(ErrorKind::InvalidData, "TCTI configuration missing") })?) .map_err(|_| { std::io::Error::new(ErrorKind::InvalidData, "Invalid TCTI configuration string") })?, ) .map_err(|e| { format_error!("Error when creating TSS Context", e); std::io::Error::new(ErrorKind::InvalidData, "failed initializing TSS context") })?; for cipher in ciphers.iter() { if ctx .test_parms(tss_esapi::structures::PublicParameters::SymCipher( SymmetricCipherParameters::new(*cipher), )) .is_ok() { return Ok(*cipher); } } Err(std::io::Error::new( ErrorKind::Other, "desired ciphers not supported", )) } /// Create an instance of TpmProvider /// /// # Safety /// /// Undefined behaviour might appear if two instances of TransientObjectContext are created /// using a same TCTI that does not handle multiple applications concurrently. pub unsafe fn build(mut self) -> std::io::Result { let owner_auth_unparsed = self.owner_hierarchy_auth.take(); let owner_auth = self.get_hierarchy_auth(owner_auth_unparsed)?; let default_cipher = self.find_default_context_cipher()?; let tcti = Tcti::from_str(self.tcti.as_ref().ok_or_else(|| { std::io::Error::new(ErrorKind::InvalidData, "TCTI configuration missing") })?) .map_err(|_| { std::io::Error::new(ErrorKind::InvalidData, "Invalid TCTI configuration string") })?; self.tcti.zeroize(); self.owner_hierarchy_auth.zeroize(); let mut builder = tss_esapi::abstraction::transient::TransientKeyContextBuilder::new() .with_tcti(tcti) .with_root_key_size(ROOT_KEY_SIZE) .with_root_key_auth_size(ROOT_KEY_AUTH_SIZE) .with_hierarchy_auth(Hierarchy::Owner, owner_auth) .with_root_hierarchy(Hierarchy::Owner) .with_session_hash_alg(HashingAlgorithm::Sha256) .with_default_context_cipher(default_cipher); if self.endorsement_hierarchy_auth.is_some() { let endorsement_auth_unparsed = self.endorsement_hierarchy_auth.take(); let endorsement_auth = self.get_hierarchy_auth(endorsement_auth_unparsed)?; builder = builder.with_hierarchy_auth(Hierarchy::Endorsement, endorsement_auth); self.endorsement_hierarchy_auth.zeroize(); } Ok(Provider::new( self.provider_name.ok_or_else(|| { std::io::Error::new(std::io::ErrorKind::InvalidData, "missing provider name") })?, self.key_info_store.ok_or_else(|| { std::io::Error::new(ErrorKind::InvalidData, "missing key info store") })?, builder.build().map_err(|e| { format_error!("Error creating TSS Transient Object Context", e); std::io::Error::new(ErrorKind::InvalidData, "failed initializing TSS context") })?, )) } } parsec-service-1.3.0/src/providers/tpm/utils.rs000064400000000000000000000556741046102023000176730ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 #![allow(deprecated)] use log::error; use parsec_interface::operations::psa_algorithm::*; use parsec_interface::operations::psa_key_attributes::*; use parsec_interface::requests::{ResponseStatus, Result}; use picky_asn1::wrapper::IntegerAsn1; use picky_asn1_x509::RsaPublicKey; use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; use tss_esapi::abstraction::transient::{KeyMaterial, KeyParams}; use tss_esapi::constants::response_code::Tss2ResponseCodeKind; use tss_esapi::interface_types::{ algorithm::HashingAlgorithm, ecc::EccCurve, key_bits::RsaKeyBits, }; use tss_esapi::structures::{ EccScheme, EccSignature, HashScheme, RsaExponent, RsaScheme, RsaSignature, Signature, }; use tss_esapi::tss2_esys::TPMS_CONTEXT; use tss_esapi::utils::{PublicKey, TpmsContext}; use tss_esapi::Error; use zeroize::{Zeroize, Zeroizing}; const PUBLIC_EXPONENT_BYTES: [u8; 3] = [0x01, 0x00, 0x01]; /// Convert the TSS library specific error values to ResponseStatus values that are returned on /// the wire protocol /// /// Most of them are PsaErrorCommunicationFailure as, in the general case, the calls to the TSS /// library should suceed with the values crafted by the provider. /// If an error happens in the TSS library, it means that it was badly used by the provider or that /// it failed in an unexpected way and hence the PsaErrorCommunicationFailure error. /// The errors translated to response status are related with signature verification failure, lack /// of memory, hardware failure, corruption detection, lack of entropy and unsupported operations. pub fn to_response_status(error: Error) -> ResponseStatus { match error { Error::WrapperError(e) => { format_error!("Conversion to PsaErrorCommunicationFailure", e); ResponseStatus::PsaErrorCommunicationFailure } Error::Tss2Error(e) => { if let Some(kind) = e.kind() { match kind { Tss2ResponseCodeKind::Success => ResponseStatus::Success, Tss2ResponseCodeKind::Signature => ResponseStatus::PsaErrorInvalidSignature, Tss2ResponseCodeKind::ObjectMemory => { ResponseStatus::PsaErrorInsufficientMemory } Tss2ResponseCodeKind::SessionMemory => { ResponseStatus::PsaErrorInsufficientMemory } Tss2ResponseCodeKind::Memory => ResponseStatus::PsaErrorInsufficientMemory, Tss2ResponseCodeKind::Retry => ResponseStatus::PsaErrorHardwareFailure, s @ Tss2ResponseCodeKind::Asymmetric | s @ Tss2ResponseCodeKind::Hash | s @ Tss2ResponseCodeKind::KeySize | s @ Tss2ResponseCodeKind::Mgf | s @ Tss2ResponseCodeKind::Mode | s @ Tss2ResponseCodeKind::Kdf | s @ Tss2ResponseCodeKind::Scheme | s @ Tss2ResponseCodeKind::Symmetric | s @ Tss2ResponseCodeKind::Curve => { if crate::utils::GlobalConfig::log_error_details() { error!("Not supported value ({:?})", s); } ResponseStatus::PsaErrorNotSupported } e => { if crate::utils::GlobalConfig::log_error_details() { error!( "Error \"{:?}\" converted to PsaErrorCommunicationFailure.", e ); } else { error!("Error converted to PsaErrorCommunicationFailure."); } ResponseStatus::PsaErrorCommunicationFailure } } } else { if crate::utils::GlobalConfig::log_error_details() { error!( "Can not encode value {} into on of the possible TSS return values.", e ); } else { error!("Can not encode value into on of the possible TSS return values."); } ResponseStatus::InvalidEncoding } } } } // The PasswordContext is what is stored by the Key Info Manager. #[derive(Serialize, Deserialize, Zeroize)] pub struct PasswordContext { /// This value is kept for legacy purposes, to aid in the migration process context: TpmsContext, /// This value is confidential and needs to be zeroized by its new owner. auth_value: Vec, /// Public and private parts of the key key_material: KeyMaterial, } impl PasswordContext { /// Create a new [PasswordContext] pub fn new(key_material: KeyMaterial, auth_value: Vec) -> Self { PasswordContext { context: TPMS_CONTEXT::default().try_into().unwrap(), // the default value is guaranteed to work auth_value, key_material, } } /// Get a slice of bytes representing the authentication value of the key pub fn auth_value(&self) -> &[u8] { &self.auth_value } /// Get reference to the [KeyMaterial] of the key pub fn key_material(&self) -> &KeyMaterial { &self.key_material } } // LegacyPasswordContext that stored key contexts only. #[deprecated] #[derive(Serialize, Deserialize, Zeroize)] pub struct LegacyPasswordContext { pub context: TpmsContext, /// This value is confidential and needs to be zeroized by its new owner. pub auth_value: Vec, } pub fn parsec_to_tpm_params(attributes: Attributes) -> Result { match attributes.key_type { Type::RsaKeyPair | Type::RsaPublicKey => { let size = rsa_key_bits(attributes.bits)?; match attributes.policy.permitted_algorithms { Algorithm::AsymmetricSignature(alg) if alg.is_rsa_alg() => Ok(KeyParams::Rsa { size, scheme: match alg { AsymmetricSignature::RsaPkcs1v15Sign { hash_alg: SignHash::Specific(hash), } => RsaScheme::RsaSsa(HashScheme::new(convert_hash_to_tpm(hash)?)), AsymmetricSignature::RsaPss { hash_alg: SignHash::Specific(hash), } => RsaScheme::RsaPss(HashScheme::new(convert_hash_to_tpm(hash)?)), _ => return Err(ResponseStatus::PsaErrorNotSupported), }, pub_exponent: RsaExponent::create(0).unwrap(), }), Algorithm::AsymmetricEncryption(alg) => Ok(KeyParams::Rsa { size, scheme: match alg { AsymmetricEncryption::RsaPkcs1v15Crypt => RsaScheme::RsaEs, AsymmetricEncryption::RsaOaep { hash_alg } => { RsaScheme::Oaep(HashScheme::new(convert_hash_to_tpm(hash_alg)?)) } }, pub_exponent: RsaExponent::create(0).unwrap(), }), alg => { error!( "Permitted algorithm {:?} not supported with RSA key pair.", alg ); Err(ResponseStatus::PsaErrorInvalidArgument) } } } Type::EccKeyPair { .. } | Type::EccPublicKey { .. } => Ok(KeyParams::Ecc { scheme: match attributes.policy.permitted_algorithms { Algorithm::AsymmetricSignature(AsymmetricSignature::Ecdsa { hash_alg: SignHash::Specific(hash), }) => EccScheme::EcDsa(HashScheme::new(convert_hash_to_tpm(hash)?)), Algorithm::AsymmetricSignature(AsymmetricSignature::EcdsaAny) | Algorithm::AsymmetricSignature(AsymmetricSignature::DeterministicEcdsa { .. }) => return Err(ResponseStatus::PsaErrorNotSupported), _ => { error!( "Wrong algorithm provided for ECC key: {:?}", attributes.policy.permitted_algorithms ); return Err(ResponseStatus::PsaErrorInvalidArgument); } }, curve: convert_curve_to_tpm(attributes)?, }), _ => Err(ResponseStatus::PsaErrorNotSupported), } } /// Modifies the `bits` field of the key attributes to match the key length. /// /// This is a problem when importing a key where its number of bits is left /// unspecified and up to the service to deduce. pub fn adjust_attributes_key_bits( mut attributes: Attributes, key_data: &[u8], ) -> Result { if attributes.bits != 0 { return Ok(attributes); } // For a breakdown of the key formats we support see: // * for public keys: https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_export_public_key.html#description // * private keys currently not supported match attributes.key_type { Type::RsaPublicKey => { let public_key: RsaPublicKey = picky_asn1_der::from_bytes(key_data).map_err(|err| { format_error!("Could not deserialise key elements", err); ResponseStatus::PsaErrorInvalidArgument })?; attributes.bits = public_key.modulus.as_unsigned_bytes_be().len() * 8; Ok(attributes) } Type::EccPublicKey { .. } => { if key_data.is_empty() || key_data.len() % 2 == 0 { return Err(ResponseStatus::PsaErrorInvalidArgument); } attributes.bits = ((key_data.len() - 1) / 2) * 8; Ok(attributes) } _ => Ok(attributes), } } #[allow(deprecated)] fn convert_hash_to_tpm(hash: Hash) -> Result { match hash { Hash::Sha1 => Ok(HashingAlgorithm::Sha1), Hash::Sha256 => Ok(HashingAlgorithm::Sha256), Hash::Sha384 => Ok(HashingAlgorithm::Sha384), Hash::Sha512 => Ok(HashingAlgorithm::Sha512), Hash::Sha3_256 => Ok(HashingAlgorithm::Sha3_256), Hash::Sha3_384 => Ok(HashingAlgorithm::Sha3_384), Hash::Sha3_512 => Ok(HashingAlgorithm::Sha3_512), _ => { error!("Requested hash is not supported ({:?})", hash); Err(ResponseStatus::PsaErrorNotSupported) } } } pub fn rsa_key_bits(bits: usize) -> Result { let size_u16 = u16::try_from(bits).map_err(|_| { error!("Requested RSA key size is not supported ({})", bits); ResponseStatus::PsaErrorInvalidArgument })?; RsaKeyBits::try_from(size_u16).map_err(|_| { error!("Requested RSA key size is not supported ({})", size_u16); ResponseStatus::PsaErrorInvalidArgument }) } pub fn convert_curve_to_tpm(key_attributes: Attributes) -> Result { match key_attributes.key_type { Type::EccKeyPair { curve_family: EccFamily::SecpR1, } | Type::EccPublicKey { curve_family: EccFamily::SecpR1, } => match key_attributes.bits { 192 => Ok(EccCurve::NistP192), 224 => Ok(EccCurve::NistP224), 256 => Ok(EccCurve::NistP256), 384 => Ok(EccCurve::NistP384), 521 => Ok(EccCurve::NistP521), _ => { error!( "Requested ECC key size is not supported ({})", key_attributes.bits ); Err(ResponseStatus::PsaErrorNotSupported) } }, _ => { error!( "Requested key type is not supported ({:?})", key_attributes.key_type ); Err(ResponseStatus::PsaErrorNotSupported) } } } pub fn pub_key_to_bytes(pub_key: PublicKey, key_attributes: Attributes) -> Result> { match pub_key { PublicKey::Rsa(key) => picky_asn1_der::to_vec(&RsaPublicKey { modulus: IntegerAsn1::from_bytes_be_unsigned(key), public_exponent: IntegerAsn1::from_bytes_be_signed(PUBLIC_EXPONENT_BYTES.to_vec()), }) .or(Err(ResponseStatus::PsaErrorGenericError)), PublicKey::Ecc { x, y } => { let p_byte_size = key_attributes.bits / 8; // should not fail for valid keys if x.len() != p_byte_size || y.len() != p_byte_size { if crate::utils::GlobalConfig::log_error_details() { error!( "Received ECC public key with invalid size: x - {} bytes; y - {} bytes", x.len(), y.len() ); } else { error!("Received ECC public key with invalid size."); } return Err(ResponseStatus::PsaErrorCommunicationFailure); } Ok(elliptic_curve_point_to_octet_string(x, y)) } } } // Points on elliptic curves are represented as defined in section 2.3.3 of https://www.secg.org/sec1-v2.pdf // The (uncompressed) representation is [ 0x04 || x || y ] where x and y are the coordinates of the point fn elliptic_curve_point_to_octet_string(mut x: Vec, mut y: Vec) -> Vec { let mut octet_string = vec![0x04]; octet_string.append(&mut x); octet_string.append(&mut y); octet_string } pub fn bytes_to_pub_key(key_data: Vec, key_attributes: &Attributes) -> Result { match key_attributes.key_type { Type::RsaPublicKey => { let public_key: RsaPublicKey = picky_asn1_der::from_bytes(&key_data).map_err(|err| { format_error!("Could not deserialise key elements", err); ResponseStatus::PsaErrorInvalidArgument })?; validate_rsa_public_key(&public_key, key_attributes)?; Ok(PublicKey::Rsa( public_key.modulus.as_unsigned_bytes_be().to_vec(), )) } Type::EccPublicKey { .. } => { validate_ecc_public_key(&key_data, key_attributes)?; let (x, y) = octet_string_to_elliptic_curve_point(key_data); Ok(PublicKey::Ecc { x, y }) } _ => Err(ResponseStatus::PsaErrorInvalidArgument), } } // Points on elliptic curves are represented as defined in section 2.3.3 of https://www.secg.org/sec1-v2.pdf // The (uncompressed) representation is [ 0x04 || x || y ] where x and y are the coordinates of the point fn octet_string_to_elliptic_curve_point(mut data: Vec) -> (Vec, Vec) { let mut data = data.split_off(1); let key_len = data.len(); let y = data.split_off(key_len / 2); let x = data.to_vec(); (x, y) } pub fn signature_data_to_bytes(data: Signature, key_attributes: Attributes) -> Result> { match data { Signature::RsaSsa(rsa_signature) | Signature::RsaPss(rsa_signature) => { Ok(rsa_signature.signature().value().to_vec()) } Signature::EcDsa(ecc_signature) => { // ECDSA signature data is represented the concatenation of the two result values, r and s, // in big endian format, as described here: // https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_algorithm.html#asymmetricsignature-algorithm let p_byte_size = key_attributes.bits / 8; // should not fail for valid keys if ecc_signature.signature_r().value().len() != p_byte_size || ecc_signature.signature_s().value().len() != p_byte_size { if crate::utils::GlobalConfig::log_error_details() { error!( "Received ECC signature with invalid size: r - {} bytes; s - {} bytes", ecc_signature.signature_r().value().len(), ecc_signature.signature_s().value().len() ); } else { error!("Received ECC signature with invalid size."); } return Err(ResponseStatus::PsaErrorGenericError); } let mut signature = vec![]; signature.append(&mut ecc_signature.signature_r().value().to_vec()); signature.append(&mut ecc_signature.signature_s().value().to_vec()); Ok(signature) } _ => { error!("Unsupported signature type received from TPM"); Err(ResponseStatus::PsaErrorGenericError) } } } pub fn parsec_to_tpm_signature( data: Zeroizing>, key_attributes: Attributes, signature_alg: AsymmetricSignature, ) -> Result { // Ok(Signature { // scheme: convert_asym_scheme_to_tpm(Algorithm::AsymmetricSignature(signature_alg))?, // signature: bytes_to_signature_data(data, key_attributes)?, // }) Ok(match signature_alg { AsymmetricSignature::RsaPkcs1v15Sign { hash_alg: SignHash::Specific(hash), } => Signature::RsaSsa( RsaSignature::create( convert_hash_to_tpm(hash)?, data.to_vec().try_into().map_err(to_response_status)?, ) .map_err(to_response_status)?, ), AsymmetricSignature::RsaPss { hash_alg: SignHash::Specific(hash), } => Signature::RsaPss( RsaSignature::create( convert_hash_to_tpm(hash)?, data.to_vec().try_into().map_err(to_response_status)?, ) .map_err(to_response_status)?, ), AsymmetricSignature::Ecdsa { hash_alg: SignHash::Specific(hash), } => { // ECDSA signature data is represented as the concatenation of the two result values, r and s, // in big endian format, as described here: // https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_algorithm.html#asymmetricsignature-algorithm let p_size = key_attributes.bits / 8; if data.len() != p_size * 2 { if crate::utils::GlobalConfig::log_error_details() { error!( "Signature is not of the correct size - expected {} bytes, got {}.", p_size * 2, data.len() ); } else { error!("Signature is not of the correct size."); } return Err(ResponseStatus::PsaErrorInvalidArgument); } let mut r = data.to_vec(); let s = r.split_off(p_size); Signature::EcDsa( EccSignature::create( convert_hash_to_tpm(hash)?, r.try_into().map_err(to_response_status)?, s.try_into().map_err(to_response_status)?, ) .map_err(to_response_status)?, ) } _ => { error!("Signature type not supported: {:?}", signature_alg); return Err(ResponseStatus::PsaErrorNotSupported); } }) } /// Validates an RsaPublicKey against the attributes we expect. Returns ok on success, otherwise /// returns an error. fn validate_rsa_public_key(public_key: &RsaPublicKey, attributes: &Attributes) -> Result<()> { if public_key.modulus.is_negative() || public_key.public_exponent.is_negative() { error!("Only positive modulus and public exponent are supported."); return Err(ResponseStatus::PsaErrorInvalidArgument); } if public_key.public_exponent.as_unsigned_bytes_be() != PUBLIC_EXPONENT_BYTES { if crate::utils::GlobalConfig::log_error_details() { error!("The TPM Provider only supports 0x10001 as public exponent for RSA public keys, {:?} given.", public_key.public_exponent.as_unsigned_bytes_be()); } else { error!("The TPM Provider only supports 0x10001 as public exponent for RSA public keys"); } return Err(ResponseStatus::PsaErrorNotSupported); } let key_data = public_key.modulus.as_unsigned_bytes_be(); let len = key_data.len(); let key_bits = attributes.bits; if key_bits != 0 && len * 8 != key_bits { if crate::utils::GlobalConfig::log_error_details() { error!( "`bits` field of key attributes (value: {}) must be either 0 or equal to the size of the key in `data` (value: {}) for RSA keys.", attributes.bits, len * 8 ); } else { error!("`bits` field of key attributes must be either 0 or equal to the size of the key in `data` for RSA keys."); } return Err(ResponseStatus::PsaErrorInvalidArgument); } if RsaKeyBits::try_from((len * 8) as u16).is_err() { if crate::utils::GlobalConfig::log_error_details() { error!( "The TPM provider only supports RSA public keys of size 1024, 2048, 3072 and 4096 bits ({} bits given).", len * 8, ); } else { error!( "The TPM provider only supports RSA public keys of size 1024, 2048, 3072 and 4096 bits" ); } return Err(ResponseStatus::PsaErrorNotSupported); } Ok(()) } fn validate_ecc_public_key(public_key: &[u8], attributes: &Attributes) -> Result<()> { if public_key.is_empty() { error!("Public key buffer is empty."); return Err(ResponseStatus::PsaErrorInvalidArgument); } // For the format of ECC public keys, see: // https://parallaxsecond.github.io/parsec-book/parsec_client/operations/psa_export_public_key.html#description if public_key[0] != 0x04 { error!("ECC public key buffer is incorrectly formatted."); return Err(ResponseStatus::PsaErrorInvalidArgument); } let len = public_key.len() - 1; // discard the first byte if attributes.bits != 0 && (len * 8) / 2 != attributes.bits { if crate::utils::GlobalConfig::log_error_details() { error!( "`bits` field of key attributes (value: {}) must be either 0 or equal to half the size of the key in `data` (value: {}) for Weierstrass curves.", attributes.bits, len * 8 ); } else { error!("`bits` field of key attributes must be either 0 or equal to half the size of the key in `data` for Weierstrass curves."); } return Err(ResponseStatus::PsaErrorInvalidArgument); } Ok(()) } pub(super) fn ek_pub_key_to_bytes(ek_public: PublicKey) -> Result> { pub_key_to_bytes( ek_public, Attributes { lifetime: Lifetime::Persistent, key_type: Type::RsaKeyPair, bits: 2048, policy: Policy { usage_flags: Default::default(), permitted_algorithms: Algorithm::None, }, }, ) } parsec-service-1.3.0/src/providers/trusted_service/asym_encryption.rs000064400000000000000000000044471046102023000243600ustar 00000000000000// Copyright 2021 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::Provider; use crate::authenticators::ApplicationIdentity; use crate::key_info_managers::KeyIdentity; use log::error; use parsec_interface::operations::{psa_asymmetric_decrypt, psa_asymmetric_encrypt}; use parsec_interface::requests::Result; impl Provider { pub(super) fn psa_asymmetric_encrypt_internal( &self, application_identity: &ApplicationIdentity, op: psa_asymmetric_encrypt::Operation, ) -> Result { let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), op.key_name.clone(), ); let key_id = self.key_info_store.get_key_id(&key_identity)?; let salt_buff = match &op.salt { Some(salt) => salt.to_vec(), None => Vec::new(), }; match self .context .asym_encrypt(key_id, op.alg, op.plaintext.to_vec(), salt_buff) { Ok(ciphertext) => Ok(psa_asymmetric_encrypt::Result { ciphertext: ciphertext.into(), }), Err(error) => { error!("Encrypt failed with status: {}", error); Err(error) } } } pub(super) fn psa_asymmetric_decrypt_internal( &self, application_identity: &ApplicationIdentity, op: psa_asymmetric_decrypt::Operation, ) -> Result { let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), op.key_name.clone(), ); let key_id = self.key_info_store.get_key_id(&key_identity)?; let salt_buff = match &op.salt { Some(salt) => salt.to_vec(), None => Vec::new(), }; match self .context .asym_decrypt(key_id, op.alg, op.ciphertext.to_vec(), salt_buff) { Ok(plaintext) => Ok(psa_asymmetric_decrypt::Result { plaintext: plaintext.into(), }), Err(error) => { error!("Decrypt failed with status: {}", error); Err(error) } } } } parsec-service-1.3.0/src/providers/trusted_service/asym_sign.rs000064400000000000000000000034021046102023000231140ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::Provider; use crate::authenticators::ApplicationIdentity; use crate::key_info_managers::KeyIdentity; use parsec_interface::operations::{psa_sign_hash, psa_verify_hash}; use parsec_interface::requests::Result; impl Provider { pub(super) fn psa_sign_hash_internal( &self, application_identity: &ApplicationIdentity, op: psa_sign_hash::Operation, ) -> Result { let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), op.key_name.clone(), ); let key_id = self.key_info_store.get_key_id(&key_identity)?; let key_attributes = self.key_info_store.get_key_attributes(&key_identity)?; op.validate(key_attributes)?; Ok(psa_sign_hash::Result { signature: self .context .sign_hash(key_id, op.hash.to_vec(), op.alg)? .into(), }) } pub(super) fn psa_verify_hash_internal( &self, application_identity: &ApplicationIdentity, op: psa_verify_hash::Operation, ) -> Result { let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), op.key_name.clone(), ); let key_id = self.key_info_store.get_key_id(&key_identity)?; let key_attributes = self.key_info_store.get_key_attributes(&key_identity)?; op.validate(key_attributes)?; self.context .verify_hash(key_id, op.hash.to_vec(), op.signature.to_vec(), op.alg)?; Ok(psa_verify_hash::Result {}) } } parsec-service-1.3.0/src/providers/trusted_service/capability_discovery.rs000064400000000000000000000061551046102023000253430ustar 00000000000000// Copyright 2021 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::Provider; use crate::authenticators::ApplicationIdentity; use crate::providers::crypto_capability::CanDoCrypto; use log::{info, trace}; use parsec_interface::operations::can_do_crypto; use parsec_interface::operations::psa_key_attributes::{Attributes, Type}; use parsec_interface::requests::ResponseStatus::PsaErrorNotSupported; use parsec_interface::requests::Result; impl CanDoCrypto for Provider { fn can_do_crypto_internal( &self, _app_identity: &ApplicationIdentity, op: can_do_crypto::Operation, ) -> Result { trace!("can_do_crypto_internal"); // Check if psa-crypto can convert the attributes into PSA structure // The conversion includes some validity checks. op.attributes.can_convert_into_psa().map_err(|_| { info!("Unsupported key attributes {:?}", op.attributes); PsaErrorNotSupported })?; Ok(can_do_crypto::Result) } fn use_check_internal(&self, attributes: Attributes) -> Result { trace!("use_check_internal"); let _ = Provider::check_key_size(attributes, false).map_err(|_| { info!("Unsupported key size {}", attributes.bits); PsaErrorNotSupported })?; Ok(can_do_crypto::Result) } fn generate_check_internal(&self, attributes: Attributes) -> Result { trace!("generate_check_internal"); let _ = Provider::check_key_size(attributes, false).map_err(|_| { info!("Unsupported key size {}", attributes.bits); PsaErrorNotSupported })?; match attributes.key_type { Type::RsaKeyPair | Type::EccKeyPair { .. } | Type::DhKeyPair { .. } | Type::RawData | Type::Aes | Type::Camellia | Type::Chacha20 => Ok(can_do_crypto::Result), _ => { info!("Unsupported key type {:?}", attributes.key_type); Err(PsaErrorNotSupported) } } } fn import_check_internal(&self, attributes: Attributes) -> Result { trace!("import_check_internal"); let _ = Provider::check_key_size(attributes, true).map_err(|_| { info!("Unsupported key size {}", attributes.bits); PsaErrorNotSupported })?; // We can import public keys and all the types we can generate. match attributes.key_type { Type::RsaPublicKey | Type::EccPublicKey { .. } | Type::DhPublicKey { .. } => { Ok(can_do_crypto::Result) } Type::RsaKeyPair | Type::EccKeyPair { .. } | Type::DhKeyPair { .. } | Type::RawData | Type::Aes | Type::Camellia | Type::Chacha20 => Ok(can_do_crypto::Result), _ => { info!("Unsupported key type {:?}", attributes.key_type); Err(PsaErrorNotSupported) } } } } parsec-service-1.3.0/src/providers/trusted_service/context/asym_encryption.rs000064400000000000000000000030341046102023000260330ustar 00000000000000// Copyright 2021 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::ts_protobuf::{ AsymmetricDecryptIn, AsymmetricDecryptOut, AsymmetricEncryptIn, AsymmetricEncryptOut, }; use super::Context; use parsec_interface::operations::psa_algorithm::AsymmetricEncryption; use parsec_interface::requests::ResponseStatus; use std::convert::TryInto; use zeroize::Zeroize; impl Context { pub fn asym_encrypt( &self, key_id: u32, alg: AsymmetricEncryption, mut plaintext: Vec, mut salt: Vec, ) -> Result, ResponseStatus> { let alg = alg.try_into().map_err(|e| { plaintext.zeroize(); salt.zeroize(); e })?; let req = AsymmetricEncryptIn { id: key_id, alg, plaintext, salt, }; let AsymmetricEncryptOut { ciphertext } = self.send_request(&req)?; Ok(ciphertext) } pub fn asym_decrypt( &self, key_id: u32, alg: AsymmetricEncryption, mut ciphertext: Vec, mut salt: Vec, ) -> Result, ResponseStatus> { let alg = alg.try_into().map_err(|e| { ciphertext.zeroize(); salt.zeroize(); e })?; let req = AsymmetricDecryptIn { id: key_id, alg, ciphertext, salt, }; let AsymmetricDecryptOut { plaintext } = self.send_request(&req)?; Ok(plaintext) } } parsec-service-1.3.0/src/providers/trusted_service/context/asym_sign.rs000064400000000000000000000025371046102023000246100ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::error::Error; use super::ts_protobuf::{SignHashIn, SignHashOut, VerifyHashIn}; use super::Context; use log::info; use psa_crypto::types::algorithm::AsymmetricSignature; use std::convert::TryInto; impl Context { /// Sign a hash with an asymmetric key given its ID and the signing algorithm. pub fn sign_hash( &self, key_id: u32, hash: Vec, algorithm: AsymmetricSignature, ) -> Result, Error> { info!("Handling SignHash request"); let proto_req = SignHashIn { id: key_id, hash, alg: algorithm.try_into()?, }; let SignHashOut { signature } = self.send_request(&proto_req)?; Ok(signature) } /// Verify a signature on a hash with an asymmetric key given its ID and the signing algorithm. pub fn verify_hash( &self, key_id: u32, hash: Vec, signature: Vec, algorithm: AsymmetricSignature, ) -> Result<(), Error> { info!("Handling VerifyHash request"); let proto_req = VerifyHashIn { id: key_id, hash, signature, alg: algorithm.try_into()?, }; self.send_request(&proto_req)?; Ok(()) } } parsec-service-1.3.0/src/providers/trusted_service/context/error.rs000064400000000000000000000155341046102023000237510ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use log::error; use psa_crypto::types::status::{Error as PsaError, Status as PsaStatus}; use std::error::Error as ErrorTrait; use std::fmt; /// Wrapper over types of error that the TS Context might return #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum Error { /// errors coming from the Crypto TS service, associated with performed operations. PsaCrypto(PsaError), /// errors returned by the RPC context that mediates communication with the Trusted Service RpcCaller(RpcCallerError), /// errors returned natively by this wrapper Wrapper(WrapperError), } impl Error { /// Transform the `(status, opstatus)` duo returned by the RPC caller /// into a native `Result` pub fn from_status_opstatus(status: i32, opstatus: i32) -> Result<(), Self> { if opstatus != 0 { let error = PsaStatus::from(opstatus).to_result().unwrap_err(); error!("Operation error, opstatus = {}", error); Err(Error::PsaCrypto(error)) } else if status != 0 { let error = RpcCallerError::from(status); error!("RPC caller error, status = {}", error); Err(Error::RpcCaller(error)) } else { Ok(()) } } } impl From for Error { fn from(psa_error: PsaError) -> Error { Error::PsaCrypto(psa_error) } } impl From for Error { fn from(rpc_caller_error: RpcCallerError) -> Error { Error::RpcCaller(rpc_caller_error) } } impl From for Error { fn from(wrapper_error: WrapperError) -> Error { Error::Wrapper(wrapper_error) } } impl From for Error { fn from(_: std::convert::Infallible) -> Self { unimplemented!() } } impl From for Error { fn from(_: std::num::TryFromIntError) -> Self { Error::Wrapper(WrapperError::InvalidParam) } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Error::PsaCrypto(e) => e.fmt(f), Error::Wrapper(e) => e.fmt(f), Error::RpcCaller(e) => e.fmt(f), } } } impl ErrorTrait for Error { fn source(&self) -> Option<&(dyn ErrorTrait + 'static)> { match self { Error::PsaCrypto(e) => Some(e), Error::Wrapper(e) => Some(e), Error::RpcCaller(e) => Some(e), } } } /// Native representation of errors returned by the /// RPC caller #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum RpcCallerError { /// endpoint for connecting to Trusted Services does not exist EndpointDoesNotExist, /// opcode provided for call was invalid InvalidOpcode, /// serialization of requested kind is not supported for the given message SerializationNotSupported, /// provided request body was invalid InvalidRequestBody, /// response body received from Trusted Service was invalid InvalidResponseBody, /// failed to access or use resource ResourceFailure, /// RPC layer was not ready for an operation NotReady, /// transaction handle was invalid InvalidTransaction, /// internal RPC caller error Internal, /// call contained an invalid parameter InvalidParameter, } impl From for RpcCallerError { fn from(e: i32) -> Self { match e { -1 => RpcCallerError::EndpointDoesNotExist, -2 => RpcCallerError::InvalidOpcode, -3 => RpcCallerError::SerializationNotSupported, -4 => RpcCallerError::InvalidRequestBody, -5 => RpcCallerError::InvalidResponseBody, -6 => RpcCallerError::ResourceFailure, -7 => RpcCallerError::NotReady, -8 => RpcCallerError::InvalidTransaction, -9 => RpcCallerError::Internal, -10 => RpcCallerError::InvalidParameter, _ => RpcCallerError::Internal, } } } impl fmt::Display for RpcCallerError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { RpcCallerError::EndpointDoesNotExist => { write!( f, "endpoint for connecting to Trusted Services does not exist" ) } RpcCallerError::InvalidOpcode => { write!(f, "opcode provided for call was invalid") } RpcCallerError::SerializationNotSupported => { write!( f, "serialization of requested kind is not supported for the given message" ) } RpcCallerError::InvalidRequestBody => { write!(f, "provided request body was invalid") } RpcCallerError::InvalidResponseBody => { write!(f, "response body received from Trusted Service was invalid") } RpcCallerError::ResourceFailure => { write!(f, "failed to access or use resource") } RpcCallerError::NotReady => write!(f, "RPC layer was not ready for an operation"), RpcCallerError::InvalidTransaction => { write!(f, "transaction handle was invalid") } RpcCallerError::Internal => write!(f, "internal RPC caller error"), RpcCallerError::InvalidParameter => { write!(f, "call contained an invalid parameter") } } } } impl ErrorTrait for RpcCallerError {} /// Errors returned by this wrapper layer #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub enum WrapperError { /// call handle returned by RPC layer was null CallHandleNull, /// call buffer returned by RPC layer was null CallBufferNull, /// serialization or deserialization of protobuf message failed FailedPbConversion, /// invalid operation status value InvalidOpStatus, /// a parameter passed to the function was invalid InvalidParam, } impl fmt::Display for WrapperError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { WrapperError::CallHandleNull => write!(f, "call handle returned by RPC layer was null"), WrapperError::CallBufferNull => { write!(f, "call buffer returned by RPC layer was null") } WrapperError::FailedPbConversion => write!( f, "serialization or deserialization of protobuf message failed" ), WrapperError::InvalidParam => { write!(f, "a parameter passed to the function was invalid") } WrapperError::InvalidOpStatus => { write!(f, "the RPC layer returned an invalid operation status") } } } } impl ErrorTrait for WrapperError {} parsec-service-1.3.0/src/providers/trusted_service/context/generate_random.rs000064400000000000000000000011321046102023000257370ustar 00000000000000// Copyright 2021 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::error::Error; use super::ts_protobuf::{GenerateRandomIn, GenerateRandomOut}; use super::Context; use log::info; use std::convert::TryInto; impl Context { pub fn generate_random(&self, size: usize) -> Result, Error> { info!("Handling GenerateRandom request"); let open_req: GenerateRandomIn = GenerateRandomIn { size: size.try_into()?, }; let result: GenerateRandomOut = self.send_request(&open_req)?; Ok(result.random_bytes) } } parsec-service-1.3.0/src/providers/trusted_service/context/key_management.rs000064400000000000000000000073001046102023000255740ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::error::Error; use super::ts_protobuf::{ DestroyKeyIn, DestroyKeyOut, ExportKeyIn, ExportPublicKeyIn, GenerateKeyIn, ImportKeyIn, KeyAttributes, KeyLifetime, KeyPolicy, }; use super::Context; use log::info; use psa_crypto::types::key::Attributes; use std::convert::{TryFrom, TryInto}; use zeroize::Zeroize; impl Context { /// Generate a key given its attributes and ID /// /// Lifetime flexibility is not supported: the `lifetime` parameter in the key /// attributes is essentially ignored and replaced with `KeyLifetime::Persistent`. pub fn generate_key(&self, key_attrs: Attributes, id: u32) -> Result<(), Error> { info!("Handling GenerateKey request"); let generate_req = GenerateKeyIn { attributes: Some(KeyAttributes { r#type: u16::try_from(key_attrs.key_type)? as u32, key_bits: key_attrs.bits.try_into()?, lifetime: KeyLifetime::Persistent as u32, id, policy: Some(KeyPolicy { usage: key_attrs.policy.usage_flags.try_into()?, alg: key_attrs.policy.permitted_algorithms.try_into()?, }), }), }; self.send_request(&generate_req)?; Ok(()) } /// Import a key given its attributes, ID, and key data. /// /// Lifetime flexibility is not supported: the `lifetime` parameter in the key /// attributes is essentially ignored and replaced with `KeyLifetime::Persistent`. /// /// Key data must be in the format described by the PSA Crypto format. pub fn import_key(&self, key_attrs: Attributes, id: u32, key_data: &[u8]) -> Result<(), Error> { info!("Handling ImportKey request"); let mut data = key_data.to_vec(); let import_req = ImportKeyIn { attributes: Some(KeyAttributes { r#type: u16::try_from(key_attrs.key_type).map_err(|e| { data.zeroize(); e })? as u32, key_bits: key_attrs.bits.try_into().map_err(|e| { data.zeroize(); e })?, lifetime: KeyLifetime::Persistent as u32, id, policy: Some(KeyPolicy { usage: key_attrs.policy.usage_flags.into(), alg: key_attrs .policy .permitted_algorithms .try_into() .map_err(|e| { data.zeroize(); e })?, }), }), data, }; self.send_request(&import_req)?; Ok(()) } /// Export the public part of a key given its ID. /// /// The public key data is returned in the format specified by the PSA Crypto /// format. pub fn export_public_key(&self, id: u32) -> Result, Error> { info!("Handling ExportPublicKey request"); let req = ExportPublicKeyIn { id }; self.send_request(&req) } /// Export the key given its ID. pub fn export_key(&self, id: u32) -> Result, Error> { info!("Handling ExportKey request"); let req = ExportKeyIn { id }; self.send_request(&req) } /// Destroy a key given its ID. pub fn destroy_key(&self, key_id: u32) -> Result<(), Error> { info!("Handling DestroyKey request"); let destroy_req = DestroyKeyIn { id: key_id }; let _proto_resp: DestroyKeyOut = self.send_request(&destroy_req)?; Ok(()) } } parsec-service-1.3.0/src/providers/trusted_service/context/mod.rs000064400000000000000000000151311046102023000233700ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use error::{Error, WrapperError}; use log::{error, info, trace}; use prost::Message; use std::convert::{TryFrom, TryInto}; use std::ffi::{c_void, CString}; use std::io::{self}; use std::ptr::null_mut; use std::slice; use std::sync::Mutex; use ts_binding::*; use ts_protobuf::GetOpcode; #[allow( non_snake_case, non_camel_case_types, non_upper_case_globals, clippy::unseparated_literal_suffix, // There is an issue where long double become u128 in extern blocks. Check this issue: // https://github.com/rust-lang/rust-bindgen/issues/1549 improper_ctypes, missing_debug_implementations, trivial_casts, clippy::all, unused, unused_qualifications )] pub mod ts_binding { #![allow(deref_nullptr)] include!(concat!(env!("OUT_DIR"), "/ts_bindings.rs")); } mod asym_encryption; mod asym_sign; pub mod error; mod generate_random; mod key_management; mod ts_protobuf; /// Context for interacting with the crypto Trusted Service (TS). /// /// The context maintains the state necessary for calls to be made /// and acts as a bridge between the two encoding types: Rust native /// PSA Crypto and the IPC mechanism used by the Normal World userland /// TS endpoint. /// /// `Context` does not surface the full operation sequence demanded by the /// TS. Keys need not be opened before use - only referenced by their creation /// ID - and therefore not closed either. /// /// # Safety /// /// `Sync` and `Send` are manually implemented on this type since it /// contains pointers to the structures that perform various tasks /// and which act as the underlying client of `Context`. The use of /// these pointers is not thread-safe, so in order to allow `Context` /// to be used across threads, a `Mutex` is used to lock down all calls. /// /// Upon being dropped, all the resources are released and no prior cleanup /// is required from the caller. #[derive(Debug)] pub struct Context { rpc_caller: *mut rpc_caller, service_context: *mut service_context, rpc_session_handle: *mut c_void, call_mutex: Mutex<()>, } impl Context { /// Establish a connection to the Trusted Service to obtain a working context. pub fn connect() -> anyhow::Result { // Initialise service locator. Can be called multiple times, // but *must* be called at least once. unsafe { service_locator_init() }; info!("Obtaining a crypto Trusted Service context."); let mut status = 0; let service_name = CString::new("sn:trustedfirmware.org:crypto:0").unwrap(); let service_context = unsafe { service_locator_query(service_name.as_ptr(), &mut status) }; if service_context.is_null() { error!("Locating crypto Trusted Service failed, status: {}", status); return Err(io::Error::new( io::ErrorKind::Other, "Failed to obtain a Trusted Service context", ) .into()); } else if status != 0 { return Err(io::Error::new( io::ErrorKind::Other, format!( "Failed to connect to Trusted Service; status code: {}", status ), ) .into()); } info!("Starting crypto Trusted Service context"); let mut rpc_caller = null_mut(); let rpc_session_handle = unsafe { service_context_open(service_context, TS_RPC_ENCODING_PROTOBUF, &mut rpc_caller) }; if rpc_caller.is_null() || rpc_session_handle.is_null() { return Err(io::Error::new( io::ErrorKind::Other, "Failed to start Trusted Service context", ) .into()); } let ctx = Context { rpc_caller, service_context, rpc_session_handle, call_mutex: Mutex::new(()), }; Ok(ctx) } // Serialize and send a request and deserialize the response back to // the caller. The caller is responsible for explicitly declaring // the response type if its contents are of interest. fn send_request( &self, req: &(impl Message + GetOpcode), ) -> Result { let _mutex_guard = self.call_mutex.lock().expect("Call mutex poisoned"); trace!("Beginning call to Trusted Service"); let mut buf_out = null_mut(); let call_handle = unsafe { rpc_caller_begin(self.rpc_caller, &mut buf_out, req.encoded_len()) }; if call_handle.is_null() { error!("Call handle was null"); return Err(WrapperError::CallHandleNull.into()); } else if buf_out.is_null() { error!("Call buffer was null"); return Err(WrapperError::CallBufferNull.into()); } let mut buf_out = unsafe { slice::from_raw_parts_mut(buf_out, req.encoded_len()) }; req.encode(&mut buf_out).map_err(|e| { unsafe { rpc_caller_end(self.rpc_caller, call_handle) }; format_error!("Failed to serialize Protobuf request", e); WrapperError::FailedPbConversion })?; trace!("Invoking RPC call"); let mut opstatus = 0; let mut resp = T::default(); let mut resp_buf = null_mut(); let mut resp_buf_size = 0; let status = unsafe { rpc_caller_invoke( self.rpc_caller, call_handle, i32::from(req.opcode()).try_into().unwrap(), &mut opstatus, &mut resp_buf, &mut resp_buf_size, ) }; Error::from_status_opstatus( status, i32::try_from(opstatus).map_err(|_| Error::Wrapper(WrapperError::InvalidOpStatus))?, ) .map_err(|e| { unsafe { rpc_caller_end(self.rpc_caller, call_handle) }; e })?; let resp_buf = unsafe { slice::from_raw_parts_mut(resp_buf, resp_buf_size) }; resp.merge(&*resp_buf).map_err(|e| { unsafe { rpc_caller_end(self.rpc_caller, call_handle) }; format_error!("Failed to serialize Protobuf request", e); WrapperError::FailedPbConversion })?; unsafe { rpc_caller_end(self.rpc_caller, call_handle) }; Ok(resp) } } impl Drop for Context { fn drop(&mut self) { unsafe { service_context_close(self.service_context, self.rpc_session_handle) }; unsafe { service_context_relinquish(self.service_context) }; } } unsafe impl Sync for Context {} unsafe impl Send for Context {} parsec-service-1.3.0/src/providers/trusted_service/context/ts_protobuf.rs000064400000000000000000000043511046102023000251610ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 #![allow( non_snake_case, non_camel_case_types, non_upper_case_globals, clippy::unseparated_literal_suffix, // There is an issue where long double become u128 in extern blocks. Check this issue: // https://github.com/rust-lang/rust-bindgen/issues/1549 improper_ctypes, missing_debug_implementations, trivial_casts, clippy::all, unused, unused_qualifications )] use zeroize::Zeroize; include!(concat!(env!("OUT_DIR"), "/ts_crypto.rs")); /// Trait for associating an Opcode with each operation type /// and obtaining it in a generic way. pub trait GetOpcode { fn opcode(&self) -> Opcode; } macro_rules! opcode_impl { ($type:ty, $opcode:ident) => { impl GetOpcode for $type { fn opcode(&self) -> Opcode { Opcode::$opcode } } }; ($type_in:ty, $type_out:ty, $opcode:ident) => { impl GetOpcode for $type_in { fn opcode(&self) -> Opcode { Opcode::$opcode } } impl GetOpcode for $type_out { fn opcode(&self) -> Opcode { Opcode::$opcode } } }; } opcode_impl!(GenerateKeyIn, GenerateKeyOut, GenerateKey); opcode_impl!(DestroyKeyIn, DestroyKeyOut, DestroyKey); opcode_impl!(SignHashIn, SignHashOut, SignHash); opcode_impl!(VerifyHashIn, VerifyHashOut, VerifyHash); opcode_impl!(ImportKeyIn, ImportKeyOut, ImportKey); opcode_impl!(ExportPublicKeyIn, ExportPublicKeyOut, ExportPublicKey); opcode_impl!(ExportKeyIn, ExportKeyOut, ExportKey); opcode_impl!(GenerateRandomIn, GenerateRandomOut, GenerateRandom); opcode_impl!(AsymmetricDecryptIn, AsymmetricDecryptOut, AsymmetricDecrypt); opcode_impl!(AsymmetricEncryptIn, AsymmetricEncryptOut, AsymmetricEncrypt); impl Drop for ImportKeyIn { fn drop(&mut self) { self.data.zeroize(); } } impl Drop for ExportKeyOut { fn drop(&mut self) { self.data.zeroize(); } } impl Drop for SignHashIn { fn drop(&mut self) { self.hash.zeroize(); } } impl Drop for VerifyHashIn { fn drop(&mut self) { self.hash.zeroize(); self.signature.zeroize(); } } parsec-service-1.3.0/src/providers/trusted_service/error.rs000064400000000000000000000030051046102023000222530ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::context::error::{Error, RpcCallerError, WrapperError}; use parsec_interface::requests::ResponseStatus; impl From for ResponseStatus { fn from(error: RpcCallerError) -> Self { match error { RpcCallerError::EndpointDoesNotExist | RpcCallerError::InvalidOpcode | RpcCallerError::SerializationNotSupported | RpcCallerError::ResourceFailure | RpcCallerError::NotReady | RpcCallerError::InvalidTransaction | RpcCallerError::Internal | RpcCallerError::InvalidResponseBody | RpcCallerError::InvalidParameter => ResponseStatus::PsaErrorCommunicationFailure, RpcCallerError::InvalidRequestBody => ResponseStatus::PsaErrorInvalidArgument, } } } impl From for ResponseStatus { fn from(error: WrapperError) -> Self { match error { WrapperError::CallBufferNull | WrapperError::CallHandleNull | WrapperError::FailedPbConversion | WrapperError::InvalidParam | WrapperError::InvalidOpStatus => ResponseStatus::PsaErrorCommunicationFailure, } } } impl From for ResponseStatus { fn from(error: Error) -> Self { match error { Error::PsaCrypto(e) => e.into(), Error::RpcCaller(e) => e.into(), Error::Wrapper(e) => e.into(), } } } parsec-service-1.3.0/src/providers/trusted_service/generate_random.rs000064400000000000000000000013601046102023000242560ustar 00000000000000// Copyright 2021 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::Provider; use parsec_interface::operations::psa_generate_random; use parsec_interface::requests::Result; impl Provider { pub(super) fn psa_generate_random_internal( &self, op: psa_generate_random::Operation, ) -> Result { let size = op.size; match self.context.generate_random(size) { Ok(random_bytes) => Ok(psa_generate_random::Result { random_bytes: random_bytes.into(), }), Err(error) => { format_error!("Generate random status: ", error); Err(error.into()) } } } } parsec-service-1.3.0/src/providers/trusted_service/key_management.rs000064400000000000000000000167121046102023000241170ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use super::Provider; use crate::authenticators::ApplicationIdentity; use crate::key_info_managers::KeyIdentity; use log::error; use parsec_interface::operations::psa_key_attributes::{Attributes, Type}; use parsec_interface::operations::utils_deprecated_primitives::CheckDeprecated; use parsec_interface::operations::{ psa_destroy_key, psa_export_key, psa_export_public_key, psa_generate_key, psa_import_key, }; use parsec_interface::requests::{ResponseStatus, Result}; use parsec_interface::secrecy::ExposeSecret; use parsec_interface::secrecy::Secret; use psa_crypto::types::key::PSA_KEY_ID_USER_MAX; use std::sync::atomic::{AtomicU32, Ordering::Relaxed}; /// Creates a new PSA Key ID pub fn create_key_id(max_current_id: &AtomicU32) -> Result { // fetch_add adds 1 to the old value and returns the old value, so add 1 to local value for new ID let new_key_id = max_current_id.fetch_add(1, Relaxed) + 1; if new_key_id > PSA_KEY_ID_USER_MAX { // If storing key failed and no other keys were created in the mean time, it is safe to // decrement the key counter. max_current_id.store(PSA_KEY_ID_USER_MAX, Relaxed); error!("PSA max key ID limit of {} reached", PSA_KEY_ID_USER_MAX); return Err(ResponseStatus::PsaErrorInsufficientMemory); } Ok(new_key_id) } impl Provider { pub(super) fn psa_generate_key_internal( &self, application_identity: &ApplicationIdentity, op: psa_generate_key::Operation, ) -> Result { return_on_deprecated!(op, "The key requested to generate is deprecated"); let key_name = op.key_name; let key_attributes = Provider::check_key_size(op.attributes, false)?; let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), key_name, ); self.key_info_store.does_not_exist(&key_identity)?; let key_id = create_key_id(&self.id_counter)?; match self.context.generate_key(key_attributes, key_id) { Ok(_) => { if let Err(e) = self.key_info_store .insert_key_info(key_identity, &key_id, key_attributes) { if self.context.destroy_key(key_id).is_err() { error!("Failed to destroy the previously generated key."); } Err(e) } else { Ok(psa_generate_key::Result {}) } } Err(error) => { let error = ResponseStatus::from(error); format_error!("Generate key error", error); Err(error) } } } pub(super) fn psa_import_key_internal( &self, application_identity: &ApplicationIdentity, op: psa_import_key::Operation, ) -> Result { warn_on_deprecated!(op, "The key requested to import is deprecated"); let key_name = op.key_name; let key_attributes = Provider::check_key_size(op.attributes, true)?; let key_data = op.data; let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), key_name, ); self.key_info_store.does_not_exist(&key_identity)?; let key_id = create_key_id(&self.id_counter)?; match self .context .import_key(key_attributes, key_id, key_data.expose_secret()) { Ok(_) => { if let Err(e) = self.key_info_store .insert_key_info(key_identity, &key_id, key_attributes) { if self.context.destroy_key(key_id).is_err() { error!("Failed to destroy the previously generated key."); } Err(e) } else { Ok(psa_import_key::Result {}) } } Err(error) => { format_error!("Import key status: ", error); Err(error.into()) } } } /// Check if key size is correct for the key type pub fn check_key_size(attributes: Attributes, is_import: bool) -> Result { // For some operations like import 0 size is permitted if is_import && attributes.bits == 0 { return Ok(attributes); } match attributes.key_type { Type::RsaKeyPair | Type::RsaPublicKey => match attributes.bits { 1024 | 2048 | 4096 => Ok(attributes), _ => { error!( "Requested RSA key size is not supported ({})", attributes.bits ); Err(ResponseStatus::PsaErrorInvalidArgument) } }, _ => { // We don't (yet?) implement checks for other key types Ok(attributes) } } } pub(super) fn psa_export_public_key_internal( &self, application_identity: &ApplicationIdentity, op: psa_export_public_key::Operation, ) -> Result { let key_name = op.key_name; let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), key_name, ); let key_id = self.key_info_store.get_key_id(&key_identity)?; match self.context.export_public_key(key_id) { Ok(pub_key) => Ok(psa_export_public_key::Result { data: pub_key.into(), }), Err(error) => { format_error!("Export public key status: ", error); Err(error.into()) } } } pub(super) fn psa_export_key_internal( &self, application_identity: &ApplicationIdentity, op: psa_export_key::Operation, ) -> Result { let key_name = op.key_name; let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), key_name, ); let key_id = self.key_info_store.get_key_id(&key_identity)?; match self.context.export_key(key_id) { Ok(key) => Ok(psa_export_key::Result { data: Secret::new(key), }), Err(error) => { format_error!("Export key status: ", error); Err(error.into()) } } } pub(super) fn psa_destroy_key_internal( &self, application_identity: &ApplicationIdentity, op: psa_destroy_key::Operation, ) -> Result { let key_name = op.key_name; let key_identity = KeyIdentity::new( application_identity.clone(), self.provider_identity.clone(), key_name, ); let key_id = self.key_info_store.get_key_id(&key_identity)?; self.key_info_store.remove_key_info(&key_identity)?; match self.context.destroy_key(key_id) { Ok(()) => Ok(psa_destroy_key::Result {}), Err(error) => { format_error!("Destroy key status: ", error); Err(error.into()) } } } } parsec-service-1.3.0/src/providers/trusted_service/mod.rs000064400000000000000000000255511046102023000217130ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Trusted Service provider //! //! This provider is backed by a crypto Trusted Service deployed in TrustZone use crate::authenticators::ApplicationIdentity; use crate::key_info_managers::{KeyIdentity, KeyInfoManagerClient}; use crate::providers::crypto_capability::CanDoCrypto; use crate::providers::{Provide, ProviderIdentity}; use context::Context; use derivative::Derivative; use log::{error, trace}; use parsec_interface::operations::list_providers::ProviderInfo; use parsec_interface::operations::list_providers::Uuid; use parsec_interface::operations::{ can_do_crypto, list_clients, list_keys, psa_asymmetric_decrypt, psa_asymmetric_encrypt, psa_destroy_key, psa_export_key, psa_export_public_key, psa_generate_key, psa_generate_random, psa_import_key, psa_sign_hash, psa_verify_hash, }; use parsec_interface::requests::{Opcode, ProviderId, Result}; use psa_crypto::types::key; use std::collections::HashSet; use std::sync::atomic::{AtomicU32, Ordering}; mod asym_encryption; mod asym_sign; mod capability_discovery; mod context; mod error; mod generate_random; mod key_management; const SUPPORTED_OPCODES: [Opcode; 11] = [ Opcode::PsaDestroyKey, Opcode::PsaGenerateKey, Opcode::PsaSignHash, Opcode::PsaVerifyHash, Opcode::PsaImportKey, Opcode::PsaExportPublicKey, Opcode::PsaExportKey, Opcode::PsaGenerateRandom, Opcode::CanDoCrypto, Opcode::PsaAsymmetricEncrypt, Opcode::PsaAsymmetricDecrypt, ]; /// Trusted Service provider structure /// /// Operations for this provider are serviced through an IPC interface that leads /// to a Secure World implementation of PSA Crypto. #[derive(Derivative)] #[derivative(Debug)] pub struct Provider { // The identity of the provider including uuid & name. provider_identity: ProviderIdentity, context: Context, // When calling write on a reference of key_info_store, a type // std::sync::RwLockWriteGuard is returned. We need to use the // dereference operator (*) to access the inner type dyn ManageKeyInfo + Send + Sync and then // reference it to match with the method prototypes. #[derivative(Debug = "ignore")] key_info_store: KeyInfoManagerClient, // Holds the highest ID of all keys (including destroyed keys). New keys will receive an ID of // id_counter + 1. Once id_counter reaches the highest allowed ID, no more keys can be created. id_counter: AtomicU32, } impl Provider { /// The default provider name for trusted service provider pub const DEFAULT_PROVIDER_NAME: &'static str = "trusted-service-provider"; /// The UUID for this provider pub const PROVIDER_UUID: &'static str = "71129441-508a-4da6-b6e8-7b98a777e4c0"; /// Creates and initialises a new instance of Provider. fn new( provider_name: String, key_info_store: KeyInfoManagerClient, ) -> anyhow::Result { let ts_provider = Provider { provider_identity: ProviderIdentity { name: provider_name, uuid: String::from(Self::PROVIDER_UUID), }, key_info_store, context: Context::connect()?, id_counter: AtomicU32::new(key::PSA_KEY_ID_USER_MIN), }; let mut max_key_id: key::psa_key_id_t = key::PSA_KEY_ID_USER_MIN; { let mut to_remove: Vec = Vec::new(); // Go through all TrustedServiceProvider key identities to key info mappings and check if they are still // present. // Delete those who are not present and add to the local_store the ones present. match ts_provider.key_info_store.get_all() { Ok(key_identities) => { for key_identity in key_identities.iter() { let key_id = match ts_provider.key_info_store.get_key_id(key_identity) { Ok(key_id) => key_id, Err(response_status) => { error!("Error getting the Key ID for KeyIdentity:\n{}\n(error: {}), continuing...", key_identity, response_status); to_remove.push(key_identity.clone()); continue; } }; if key_id > max_key_id { max_key_id = key_id; } } } Err(string) => { error!("Key Info Manager error when obtaining handles: {}", string); return Err(std::io::Error::new(std::io::ErrorKind::Other, string).into()); } }; for key_identity in to_remove.iter() { if let Err(string) = ts_provider.key_info_store.remove_key_info(key_identity) { return Err(std::io::Error::new(std::io::ErrorKind::Other, string).into()); } } } ts_provider.id_counter.store(max_key_id, Ordering::Relaxed); Ok(ts_provider) } } impl Provide for Provider { fn describe(&self) -> Result<(ProviderInfo, HashSet)> { trace!("describe ingress"); Ok((ProviderInfo { // Assigned UUID for this provider: 71129441-508a-4da6-b6e8-7b98a777e4c0 uuid: Uuid::parse_str(Provider::PROVIDER_UUID)?, description: String::from("Provider exposing functionality provided by the Crypto Trusted Service running in a Trusted Execution Environment"), vendor: String::from("Arm"), version_maj: 0, version_min: 1, version_rev: 0, id: ProviderId::TrustedService, }, SUPPORTED_OPCODES.iter().copied().collect())) } fn list_keys( &self, application_identity: &ApplicationIdentity, _op: list_keys::Operation, ) -> Result { trace!("list_keys ingress"); Ok(list_keys::Result { keys: self.key_info_store.list_keys(application_identity)?, }) } fn list_clients(&self, _op: list_clients::Operation) -> Result { trace!("list_clients ingress"); Ok(list_clients::Result { clients: self .key_info_store .list_clients()? .into_iter() .map(|application_identity| application_identity.name().clone()) .collect(), }) } fn psa_generate_key( &self, application_identity: &ApplicationIdentity, op: psa_generate_key::Operation, ) -> Result { trace!("psa_generate_key ingress"); self.psa_generate_key_internal(application_identity, op) } fn psa_destroy_key( &self, application_identity: &ApplicationIdentity, op: psa_destroy_key::Operation, ) -> Result { trace!("psa_destroy_key ingress"); self.psa_destroy_key_internal(application_identity, op) } fn psa_import_key( &self, application_identity: &ApplicationIdentity, op: psa_import_key::Operation, ) -> Result { trace!("psa_import_key ingress"); self.psa_import_key_internal(application_identity, op) } fn psa_export_public_key( &self, application_identity: &ApplicationIdentity, op: psa_export_public_key::Operation, ) -> Result { trace!("psa_export_public_key ingress"); self.psa_export_public_key_internal(application_identity, op) } fn psa_export_key( &self, application_identity: &ApplicationIdentity, op: psa_export_key::Operation, ) -> Result { trace!("psa_export_key ingress"); self.psa_export_key_internal(application_identity, op) } fn psa_generate_random( &self, op: psa_generate_random::Operation, ) -> Result { trace!("psa_generate_random ingress"); self.psa_generate_random_internal(op) } fn psa_sign_hash( &self, application_identity: &ApplicationIdentity, op: psa_sign_hash::Operation, ) -> Result { trace!("psa_sign_hash ingress"); self.psa_sign_hash_internal(application_identity, op) } fn psa_verify_hash( &self, application_identity: &ApplicationIdentity, op: psa_verify_hash::Operation, ) -> Result { trace!("psa_verify_hash ingress"); self.psa_verify_hash_internal(application_identity, op) } fn can_do_crypto( &self, application_identity: &ApplicationIdentity, op: can_do_crypto::Operation, ) -> Result { trace!("can_do_crypto ingress"); self.can_do_crypto_main(application_identity, op) } fn psa_asymmetric_encrypt( &self, application_identity: &ApplicationIdentity, op: psa_asymmetric_encrypt::Operation, ) -> Result { trace!("psa_asymmetric_encrypt ingress"); self.psa_asymmetric_encrypt_internal(application_identity, op) } fn psa_asymmetric_decrypt( &self, application_identity: &ApplicationIdentity, op: psa_asymmetric_decrypt::Operation, ) -> Result { trace!("psa_asymmetric_decrypt ingress"); self.psa_asymmetric_decrypt_internal(application_identity, op) } } /// Trusted Service provider builder #[derive(Default, Derivative)] #[derivative(Debug)] pub struct ProviderBuilder { provider_name: Option, #[derivative(Debug = "ignore")] key_info_store: Option, } impl ProviderBuilder { /// Create a new provider builder pub fn new() -> ProviderBuilder { ProviderBuilder { provider_name: None, key_info_store: None, } } /// Add a provider name pub fn with_provider_name(mut self, provider_name: String) -> ProviderBuilder { self.provider_name = Some(provider_name); self } /// Add a KeyInfo manager pub fn with_key_info_store(mut self, key_info_store: KeyInfoManagerClient) -> ProviderBuilder { self.key_info_store = Some(key_info_store); self } /// Build into a TrustedService pub fn build(self) -> anyhow::Result { Provider::new( self.provider_name.ok_or_else(|| { std::io::Error::new(std::io::ErrorKind::InvalidData, "missing provider name") })?, self.key_info_store.ok_or_else(|| { std::io::Error::new(std::io::ErrorKind::InvalidData, "missing key info store") })?, ) } } parsec-service-1.3.0/src/utils/cli.rs000064400000000000000000000020011046102023000155750ustar 00000000000000// Copyright 2021 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Command Line Interface configuration // WARNING: This file should be only updated in a non-breaking way. CLI flags should not be // removed, new flags should be tested. // See https://github.com/parallaxsecond/parsec/issues/392 for details. use structopt::StructOpt; /// Parsec is the Platform AbstRaction for SECurity, a new open-source initiative to provide a /// common API to secure services in a platform-agnostic way. /// /// Parsec documentation is available at: /// https://parallaxsecond.github.io/parsec-book/index.html /// /// Most of Parsec configuration comes from its configuration file. /// Please check the documentation to find more about configuration: /// https://parallaxsecond.github.io/parsec-book/user_guides/configuration.html #[derive(StructOpt, Debug)] pub struct Opts { /// Sets the configuration file path #[structopt(short, long, default_value = "config.toml")] pub config: String, } parsec-service-1.3.0/src/utils/config.rs000064400000000000000000000243711046102023000163110ustar 00000000000000// Copyright 2021 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Structures for the Parsec configuration file #[cfg(feature = "cryptoauthlib-provider")] use crate::providers::cryptoauthlib::Provider as CryptoAuthLibProvider; #[cfg(feature = "mbed-crypto-provider")] use crate::providers::mbed_crypto::Provider as MbedCryptoProvider; #[cfg(feature = "pkcs11-provider")] use crate::providers::pkcs11::Provider as Pkcs11Provider; #[cfg(feature = "tpm-provider")] use crate::providers::tpm::Provider as TpmProvider; #[cfg(feature = "trusted-service-provider")] use crate::providers::trusted_service::Provider as TrustedServiceProvider; #[cfg(not(all( feature = "mbed-crypto-provider", feature = "pkcs11-provider", feature = "tpm-provider", feature = "cryptoauthlib-provider", feature = "trusted-service-provider" )))] use log::error; use log::LevelFilter; use parsec_interface::requests::ProviderId; use serde::Deserialize; use std::io::Error; #[cfg(not(all( feature = "mbed-crypto-provider", feature = "pkcs11-provider", feature = "tpm-provider", feature = "cryptoauthlib-provider", feature = "trusted-service-provider" )))] use std::io::ErrorKind; use zeroize::Zeroize; /// Core settings /// /// See the config.toml file for a description of each field. #[derive(Copy, Clone, Deserialize, Debug)] #[allow(missing_docs)] pub struct CoreSettings { pub thread_pool_size: Option, pub idle_listener_sleep_duration: Option, pub log_level: Option, pub log_timestamp: Option, pub body_len_limit: Option, pub log_error_details: Option, pub allow_root: Option, pub buffer_size_limit: Option, pub allow_deprecated: Option, } /// Type of the Listener used #[derive(Copy, Clone, Deserialize, Debug)] pub enum ListenerType { /// Listener using Unix Domain Socket DomainSocket, } /// Configuration of the Listener #[derive(Clone, Deserialize, Debug)] pub struct ListenerConfig { /// Type of the Listener pub listener_type: ListenerType, /// Timeout of the Listener before the connection errors out (in milliseconds) pub timeout: u64, /// Path of the Unix Domain socket pub socket_path: Option, } /// Authenticator configuration structure #[derive(Deserialize, Debug, Zeroize)] #[zeroize(drop)] #[serde(tag = "auth_type")] pub enum AuthenticatorConfig { /// Direct authentication Direct { /// List of service admins admins: Option>, }, /// Unix Peer Credentials authentication UnixPeerCredentials { /// List of service admins admins: Option>, }, /// JWT-SVID JwtSvid { /// Path to the Workload API socket workload_endpoint: String, /// List of service admins admins: Option>, }, } /// Structure defining the properties of a service admin #[derive(Deserialize, Debug, Zeroize, Clone)] #[zeroize(drop)] pub struct Admin { name: String, } impl Admin { /// Give the application name of the admin pub fn name(&self) -> &str { &self.name } } /// Type of the KeyInfoManager #[derive(Copy, Clone, Deserialize, Debug)] pub enum KeyInfoManagerType { /// KeyInfoManager storing the mappings on disk OnDisk, /// KeyInfoManager for storing mappings within a SQLite database on disk. SQLite, } /// KeyInfoManager configuration #[derive(Deserialize, Debug)] pub struct KeyInfoManagerConfig { /// Name of the KeyInfoManager pub name: String, /// Type of the KeyInfoManager pub manager_type: KeyInfoManagerType, /// Path used to store the OnDiskKeyInfoManager mappings pub store_path: Option, /// File path where the SQLite database should be stored when using SQLiteKeyInfoManager pub sqlite_db_path: Option, } /// Provider configuration structure /// For providers configs in Parsec config.toml we use a format similar /// to the one described in the Internally Tagged Enum representation /// where "provider_type" is the tag field. For details see: /// https://serde.rs/enum-representations.html #[derive(Deserialize, Debug, Zeroize)] #[zeroize(drop)] #[serde(tag = "provider_type")] pub enum ProviderConfig { /// Mbed Crypto provider configuration MbedCrypto { /// The name of the provider name: Option, /// Name of the Key Info Manager to use key_info_manager: String, }, /// PKCS 11 provider configuration Pkcs11 { /// The name of the provider name: Option, /// Name of the Key Info Manager to use key_info_manager: String, /// Path of the PKCS 11 library library_path: String, /// Slot number to use slot_number: Option, /// Token serial number to use serial_number: Option, /// User Pin user_pin: Option, /// Control whether public key operations are performed in software software_public_operations: Option, /// Control whether it is allowed for a key to be exportable allow_export: Option, }, /// TPM provider configuration Tpm { /// The name of the provider name: Option, /// Name of the Key Info Manager to use key_info_manager: String, /// TCTI to use with the provider tcti: String, /// Owner Hierarchy Authentication owner_hierarchy_auth: String, /// Endorsement Hierarchy Authentication Value endorsement_hierarchy_auth: Option, /// Allows the service to still start without this provider if there is no TPM on the /// system. The priority list of providers will be as if this provider was commented out. skip_if_no_tpm: Option, }, /// Microchip CryptoAuthentication Library provider configuration CryptoAuthLib { /// The name of the provider name: Option, /// Name of the Key Info Manager to use key_info_manager: String, /// ATECC Device type device_type: String, /// Interface type iface_type: String, /// Wake delay wake_delay: Option, /// Number of rx retries rx_retries: Option, /// I2C slave address slave_address: Option, /// I2C bus bus: Option, /// I2C baud rate baud: Option, /// Access key configuration file name access_key_file_name: Option, }, /// Trusted Service provider configuration TrustedService { /// The name of the provider name: Option, /// Name of Key Info Manager to use key_info_manager: String, }, } impl ProviderConfig { /// Get the name of the Key Info Manager in the provider configuration pub fn key_info_manager(&self) -> &String { match *self { ProviderConfig::MbedCrypto { ref key_info_manager, .. } => key_info_manager, ProviderConfig::Pkcs11 { ref key_info_manager, .. } => key_info_manager, ProviderConfig::Tpm { ref key_info_manager, .. } => key_info_manager, ProviderConfig::CryptoAuthLib { ref key_info_manager, .. } => key_info_manager, ProviderConfig::TrustedService { ref key_info_manager, .. } => key_info_manager, } } /// Get the Provider ID of the provider pub fn provider_id(&self) -> ProviderId { match *self { ProviderConfig::MbedCrypto { .. } => ProviderId::MbedCrypto, ProviderConfig::Pkcs11 { .. } => ProviderId::Pkcs11, ProviderConfig::Tpm { .. } => ProviderId::Tpm, ProviderConfig::CryptoAuthLib { .. } => ProviderId::CryptoAuthLib, ProviderConfig::TrustedService { .. } => ProviderId::TrustedService, } } /// Get the name of the Provider /// If there is not one set, use the default. pub fn provider_name(&self) -> Result { match *self { #[cfg(feature = "mbed-crypto-provider")] ProviderConfig::MbedCrypto { ref name, .. } => Ok(name .clone() .unwrap_or_else(|| String::from(MbedCryptoProvider::DEFAULT_PROVIDER_NAME))), #[cfg(feature = "pkcs11-provider")] ProviderConfig::Pkcs11 { ref name, .. } => Ok(name .clone() .unwrap_or_else(|| String::from(Pkcs11Provider::DEFAULT_PROVIDER_NAME))), #[cfg(feature = "tpm-provider")] ProviderConfig::Tpm { ref name, .. } => Ok(name .clone() .unwrap_or_else(|| String::from(TpmProvider::DEFAULT_PROVIDER_NAME))), #[cfg(feature = "cryptoauthlib-provider")] ProviderConfig::CryptoAuthLib { ref name, .. } => Ok(name .clone() .unwrap_or_else(|| String::from(CryptoAuthLibProvider::DEFAULT_PROVIDER_NAME))), #[cfg(feature = "trusted-service-provider")] ProviderConfig::TrustedService { ref name, .. } => Ok(name .clone() .unwrap_or_else(|| String::from(TrustedServiceProvider::DEFAULT_PROVIDER_NAME))), #[cfg(not(all( feature = "mbed-crypto-provider", feature = "pkcs11-provider", feature = "tpm-provider", feature = "cryptoauthlib-provider", feature = "trusted-service-provider" )))] _ => { error!("Provider ({:?}) chosen in the configuration was not compiled in Parsec binary.", self); Err(Error::new(ErrorKind::InvalidData, "provider not compiled")) } } } } /// Configuration of Parsec /// /// See the config.toml file for a description of each field. #[derive(Deserialize, Debug)] #[allow(missing_docs)] pub struct ServiceConfig { pub core_settings: CoreSettings, pub listener: ListenerConfig, pub authenticator: AuthenticatorConfig, pub key_manager: Option>, pub provider: Option>, } parsec-service-1.3.0/src/utils/global_config.rs000064400000000000000000000052171046102023000176270ustar 00000000000000// Copyright 2020 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 use crate::utils::service_builder::DEFAULT_BUFFER_SIZE_LIMIT; use std::sync::atomic::Ordering; use std::sync::atomic::{AtomicBool, AtomicUsize}; /// Configuration values that affect most or all the /// components of the service. #[derive(Default, Debug)] pub struct GlobalConfig { log_error_details: AtomicBool, buffer_size_limit: AtomicUsize, allow_deprecated: AtomicBool, } impl GlobalConfig { const fn new() -> Self { GlobalConfig { log_error_details: AtomicBool::new(false), buffer_size_limit: AtomicUsize::new(DEFAULT_BUFFER_SIZE_LIMIT), // 1 MB allow_deprecated: AtomicBool::new(false), } } /// Determine whether error logs should include detailed /// information about the error pub fn log_error_details() -> bool { GLOBAL_CONFIG.log_error_details.load(Ordering::Relaxed) } /// Fetch the size limit for buffers within responses (in bytes). /// information about the error pub fn buffer_size_limit() -> usize { GLOBAL_CONFIG.buffer_size_limit.load(Ordering::Relaxed) } /// Determine whether deprecated algorithms and key types are allowed /// during key generation pub fn allow_deprecated() -> bool { GLOBAL_CONFIG.allow_deprecated.load(Ordering::Relaxed) } } static GLOBAL_CONFIG: GlobalConfig = GlobalConfig::new(); pub(super) struct GlobalConfigBuilder { log_error_details: bool, buffer_size_limit: Option, allow_deprecated: bool, } impl GlobalConfigBuilder { pub fn new() -> Self { GlobalConfigBuilder { log_error_details: false, buffer_size_limit: None, allow_deprecated: false, } } pub fn with_log_error_details(mut self, log_error_details: bool) -> Self { self.log_error_details = log_error_details; self } pub fn with_buffer_size_limit(mut self, buffer_size_limit: usize) -> Self { self.buffer_size_limit = Some(buffer_size_limit); self } pub fn with_allow_deprecated(mut self, allow_deprecated: bool) -> Self { self.allow_deprecated = allow_deprecated; self } pub fn build(self) { GLOBAL_CONFIG .log_error_details .store(self.log_error_details, Ordering::Relaxed); GLOBAL_CONFIG.buffer_size_limit.store( self.buffer_size_limit.unwrap_or(DEFAULT_BUFFER_SIZE_LIMIT), Ordering::Relaxed, ); GLOBAL_CONFIG .allow_deprecated .store(self.allow_deprecated, Ordering::Relaxed); } } parsec-service-1.3.0/src/utils/mod.rs000064400000000000000000000010121046102023000156060ustar 00000000000000// Copyright 2019 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Service utilities pub mod cli; pub mod config; mod global_config; mod service_builder; #[cfg(all( feature = "mbed-crypto-provider", feature = "pkcs11-provider", feature = "tpm-provider", feature = "cryptoauthlib-provider", feature = "trusted-service-provider", feature = "direct-authenticator" ))] #[cfg(test)] mod tests; pub use global_config::GlobalConfig; pub use service_builder::ServiceBuilder; parsec-service-1.3.0/src/utils/service_builder.rs000064400000000000000000000522431046102023000202110ustar 00000000000000// Copyright 2019 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Assemble the service from a user-defined config //! //! The service builder is required to bootstrap all the components based on a //! provided configuration. use super::global_config::GlobalConfigBuilder; use crate::authenticators::Authenticate; use crate::back::{ backend_handler::{BackEndHandler, BackEndHandlerBuilder}, dispatcher::DispatcherBuilder, }; use crate::front::{ domain_socket::DomainSocketListenerBuilder, front_end::FrontEndHandler, front_end::FrontEndHandlerBuilder, listener::Listen, }; use crate::key_info_managers::KeyInfoManagerFactory; use crate::providers::{core::ProviderBuilder as CoreProviderBuilder, Provide}; use crate::utils::config::{ AuthenticatorConfig, KeyInfoManagerConfig, ListenerConfig, ListenerType, ProviderConfig, ServiceConfig, }; use anyhow::Result; use log::{error, warn}; use parsec_interface::operations_protobuf::ProtobufConverter; use parsec_interface::requests::AuthType; use parsec_interface::requests::{BodyType, ProviderId}; use std::collections::HashMap; use std::collections::HashSet; use std::io::{Error, ErrorKind}; use std::sync::Arc; use std::time::Duration; use threadpool::{Builder as ThreadPoolBuilder, ThreadPool}; #[cfg(feature = "direct-authenticator")] use crate::authenticators::direct_authenticator::DirectAuthenticator; #[cfg(feature = "jwt-svid-authenticator")] use crate::authenticators::jwt_svid_authenticator::JwtSvidAuthenticator; #[cfg(feature = "unix-peer-credentials-authenticator")] use crate::authenticators::unix_peer_credentials_authenticator::UnixPeerCredentialsAuthenticator; #[cfg(feature = "cryptoauthlib-provider")] use crate::providers::cryptoauthlib::ProviderBuilder as CryptoAuthLibProviderBuilder; #[cfg(feature = "mbed-crypto-provider")] use crate::providers::mbed_crypto::ProviderBuilder as MbedCryptoProviderBuilder; #[cfg(feature = "pkcs11-provider")] use crate::providers::pkcs11::ProviderBuilder as Pkcs11ProviderBuilder; #[cfg(feature = "tpm-provider")] use crate::providers::tpm::ProviderBuilder as TpmProviderBuilder; #[cfg(feature = "trusted-service-provider")] use crate::providers::trusted_service::ProviderBuilder as TrustedServiceProviderBuilder; #[cfg(feature = "cryptoauthlib-provider")] use crate::providers::cryptoauthlib::Provider as CryptoAuthLibProvider; #[cfg(feature = "mbed-crypto-provider")] use crate::providers::mbed_crypto::Provider as MbedCryptoProvider; #[cfg(feature = "pkcs11-provider")] use crate::providers::pkcs11::Provider as Pkcs11Provider; #[cfg(feature = "tpm-provider")] use crate::providers::tpm::Provider as TpmProvider; #[cfg(feature = "trusted-service-provider")] use crate::providers::trusted_service::Provider as TrustedServiceProvider; #[cfg(any( feature = "mbed-crypto-provider", feature = "pkcs11-provider", feature = "tpm-provider", feature = "cryptoauthlib-provider", feature = "trusted-service-provider" ))] use crate::providers::ProviderIdentity; #[cfg(any( feature = "mbed-crypto-provider", feature = "pkcs11-provider", feature = "tpm-provider", feature = "cryptoauthlib-provider", feature = "trusted-service-provider" ))] use log::info; const WIRE_PROTOCOL_VERSION_MINOR: u8 = 0; const WIRE_PROTOCOL_VERSION_MAJOR: u8 = 1; /// Default value for the limit on the request body size (in bytes) - equal to 1MB const DEFAULT_BODY_LEN_LIMIT: usize = 1 << 20; /// Default value for the limit on the buffer size for response (in bytes) - equal to 1MB pub const DEFAULT_BUFFER_SIZE_LIMIT: usize = 1 << 20; type Provider = Arc; type Authenticator = Box; /// Service component builder and assembler /// /// Entity responsible for converting a Parsec service configuration into a fully formed service. /// Each component is independently created after which its ownership can be passed to the previous /// component in the ownership chain. The service's ownership is then passed in the form of /// ownership of a `FrontEndHandler` instance. #[derive(Copy, Clone, Debug)] pub struct ServiceBuilder; impl ServiceBuilder { /// Evaluate the provided configuration and assemble a service based on it. If the configuration contains /// any errors or inconsistencies, an `Err` is returned. /// /// # Errors /// * if any of the fields specified in the configuration are inconsistent (e.g. key info manager with name 'X' /// requested for a certain provider does not exist) or if required fields are missing, an error of kind /// `InvalidData` is returned with a string describing the cause more accurately. pub fn build_service(config: &ServiceConfig) -> Result { GlobalConfigBuilder::new() .with_log_error_details(config.core_settings.log_error_details.unwrap_or(false)) .with_buffer_size_limit( config .core_settings .buffer_size_limit .unwrap_or(DEFAULT_BUFFER_SIZE_LIMIT), ) .with_allow_deprecated(config.core_settings.allow_deprecated.unwrap_or(false)) .build(); let authenticators = build_authenticators(&config.authenticator)?; if authenticators[0].0 == AuthType::Direct { warn!("Direct authenticator has been set as the default one. It is only secure under specific requirements. Please make sure to read the Recommendations on a Secure Parsec Deployment at https://parallaxsecond.github.io/parsec-book/parsec_security/secure_deployment.html"); } let key_info_manager_builders = get_key_info_manager_builders( config.key_manager.as_ref().unwrap_or(&Vec::new()), authenticators[0].0, )?; let providers = build_providers( config.provider.as_ref().unwrap_or(&Vec::new()), key_info_manager_builders, )?; if providers.is_empty() { error!("Parsec needs at least one provider to start. No valid provider could be created from the configuration."); return Err(Error::new(ErrorKind::InvalidData, "need one provider").into()); } let backend_handlers = build_backend_handlers(providers, &authenticators)?; let dispatcher = DispatcherBuilder::new() .with_backends(backend_handlers) .build()?; let mut front_end_handler_builder = FrontEndHandlerBuilder::new(); for (auth_type, authenticator) in authenticators { front_end_handler_builder = front_end_handler_builder.with_authenticator(auth_type, authenticator); } front_end_handler_builder = front_end_handler_builder .with_dispatcher(dispatcher) .with_body_len_limit( config .core_settings .body_len_limit .unwrap_or(DEFAULT_BODY_LEN_LIMIT), ); Ok(front_end_handler_builder.build()?) } /// Construct the service IPC front component and return ownership to it. pub fn start_listener(config: ListenerConfig) -> Result> { let listener = match config.listener_type { ListenerType::DomainSocket => DomainSocketListenerBuilder::new() .with_timeout(Duration::from_millis(config.timeout)) .with_socket_path(config.socket_path.map(|s| s.into())) .build(), }?; Ok(Box::new(listener)) } /// Construct the thread pool that will be used to process all service requests. pub fn build_threadpool(num_threads: Option) -> ThreadPool { let mut threadpool_builder = ThreadPoolBuilder::new(); if let Some(num_threads) = num_threads { threadpool_builder = threadpool_builder.num_threads(num_threads); } threadpool_builder.build() } } fn build_backend_handlers( mut providers: Vec<(ProviderId, Provider)>, authenticators: &[(AuthType, Authenticator)], ) -> Result> { let mut map = HashMap::new(); let mut core_provider_builder = CoreProviderBuilder::new() .with_wire_protocol_version(WIRE_PROTOCOL_VERSION_MINOR, WIRE_PROTOCOL_VERSION_MAJOR); for (_auth_type, authenticator) in authenticators { let authenticator_info = authenticator .describe() .map_err(|_| Error::new(ErrorKind::Other, "Failed to describe authenticator"))?; core_provider_builder = core_provider_builder.with_authenticator_info(authenticator_info); } for (provider_id, provider) in providers.drain(..) { core_provider_builder = core_provider_builder.with_provider(provider.clone()); let backend_handler = BackEndHandlerBuilder::new() .with_provider(provider) .with_converter(Box::from(ProtobufConverter {})) .with_provider_id(provider_id) .with_content_type(BodyType::Protobuf) .with_accept_type(BodyType::Protobuf) .build()?; let _ = map.insert(provider_id, backend_handler); } let core_provider_backend = BackEndHandlerBuilder::new() .with_provider(Arc::new(core_provider_builder.build()?)) .with_converter(Box::from(ProtobufConverter {})) .with_provider_id(ProviderId::Core) .with_content_type(BodyType::Protobuf) .with_accept_type(BodyType::Protobuf) .build()?; let _ = map.insert(ProviderId::Core, core_provider_backend); Ok(map) } fn build_providers( configs: &[ProviderConfig], kim_factorys: HashMap, ) -> Result> { let mut providers = Vec::new(); let mut provider_names = HashSet::new(); for config in configs { // Check for duplicate providers. let provider_id = config.provider_id(); // Check for duplicate provider names. let provider_name = config.provider_name()?; if provider_names.contains(&provider_name) { error!("Duplicate provider names found.\n{} was found twice.\nThe \'[[provider]] name config option can be used to differentiate between providers of the same type.\nPlease check your config.toml file.", provider_name); return Err( Error::new(ErrorKind::InvalidData, "duplicate provider names found").into(), ); } let _ = provider_names.insert(provider_name.clone()); let kim_factory = match kim_factorys.get(config.key_info_manager()) { Some(kim_factory) => kim_factory, None => { format_error!( "Key info manager builder with specified name was not found", config.key_info_manager() ); return Err(Error::new( ErrorKind::InvalidData, "key info manager builder not found", ) .into()); } }; // The safety is checked by the fact that only one instance per provider type is enforced. let provider = match unsafe { get_provider(config, kim_factory) } { Ok(None) => { warn!("Provider {} is skipped.", provider_id); continue; } Ok(Some(provider)) => provider, Err(e) => { format_error!( &format!("Provider with ID {} cannot be created", provider_id), e ); return Err(Error::new(ErrorKind::Other, "failed to create provider").into()); } }; providers.push((provider_id, provider)); } Ok(providers) } // This cfg_attr is used to allow the fact that key_info_manager is not used when there is no // providers. #[cfg_attr( not(all( feature = "mbed-crypto-provider", feature = "pkcs11-provider", feature = "tpm-provider", feature = "cryptoauthlib-provider", feature = "trusted-service-provider" )), allow(unused_variables), allow(clippy::match_single_binding) )] // Ok(None) is returned when the provider is skipped by configuration. unsafe fn get_provider( config: &ProviderConfig, kim_factory: &KeyInfoManagerFactory, ) -> Result> { match config { #[cfg(feature = "mbed-crypto-provider")] ProviderConfig::MbedCrypto { .. } => { info!("Creating a Mbed Crypto Provider."); let provider_identity = ProviderIdentity::new( MbedCryptoProvider::PROVIDER_UUID.to_string(), config.provider_name()?, ); Ok(Some(Arc::new( MbedCryptoProviderBuilder::new() .with_key_info_store(kim_factory.build_client(provider_identity)) .with_provider_name(config.provider_name()?) .build()?, ))) } #[cfg(feature = "pkcs11-provider")] ProviderConfig::Pkcs11 { library_path, slot_number, serial_number, user_pin, software_public_operations, allow_export, .. } => { info!("Creating a PKCS 11 Provider."); let provider_identity = ProviderIdentity::new( Pkcs11Provider::PROVIDER_UUID.to_string(), config.provider_name()?, ); Ok(Some(Arc::new( Pkcs11ProviderBuilder::new() .with_key_info_store(kim_factory.build_client(provider_identity)) .with_provider_name(config.provider_name()?) .with_pkcs11_library_path(library_path.clone()) .with_slot_number(*slot_number) .with_serial_number(serial_number.clone()) .with_user_pin(user_pin.clone()) .with_software_public_operations(*software_public_operations) .with_allow_export(*allow_export) .build()?, ))) } #[cfg(feature = "tpm-provider")] ProviderConfig::Tpm { tcti, owner_hierarchy_auth, endorsement_hierarchy_auth, skip_if_no_tpm, .. } => { use std::str::FromStr; use tss_esapi::tcti_ldr::{TctiContext, TctiNameConf}; info!("Creating a TPM Provider."); let provider_identity = ProviderIdentity::new( TpmProvider::PROVIDER_UUID.to_string(), config.provider_name()?, ); let tcti_name_conf = TctiNameConf::from_str(tcti).map_err(|_| { std::io::Error::new(ErrorKind::InvalidData, "Invalid TCTI configuration string") })?; if *skip_if_no_tpm == Some(true) { // TODO: When the TPM Provider uses the new TctiContext, pass it directly to the // builder. let _tcti_context = match TctiContext::initialize(tcti_name_conf) { Ok(tcti_context) => tcti_context, Err(e) => { format_error!("Error creating a TCTI context", e); // We make the assumption that the TCTI Name Configuration is correct // and that if we failed creating a TCTI Contecxt it means that there // is no TPM support on the platform. return Ok(None); } }; } let mut builder = TpmProviderBuilder::new() .with_key_info_store(kim_factory.build_client(provider_identity)) .with_tcti(tcti) .with_provider_name(config.provider_name()?) .with_owner_hierarchy_auth(owner_hierarchy_auth.clone()); if endorsement_hierarchy_auth.is_some() { builder = builder.with_endorsement_hierarchy_auth( endorsement_hierarchy_auth.as_ref().unwrap().clone(), ); } Ok(Some(Arc::new(builder.build()?))) } #[cfg(feature = "cryptoauthlib-provider")] ProviderConfig::CryptoAuthLib { device_type, iface_type, wake_delay, rx_retries, slave_address, bus, baud, access_key_file_name, .. } => { info!("Creating a CryptoAuthentication Library Provider."); let provider_identity = ProviderIdentity::new( CryptoAuthLibProvider::PROVIDER_UUID.to_string(), config.provider_name()?, ); Ok(Some(Arc::new( CryptoAuthLibProviderBuilder::new() .with_key_info_store(kim_factory.build_client(provider_identity)) .with_provider_name(config.provider_name()?) .with_device_type(device_type.to_string()) .with_iface_type(iface_type.to_string()) .with_wake_delay(*wake_delay) .with_rx_retries(*rx_retries) .with_slave_address(*slave_address) .with_bus(*bus) .with_baud(*baud) .with_access_key_file(access_key_file_name.clone()) .build()?, ))) } #[cfg(feature = "trusted-service-provider")] ProviderConfig::TrustedService { .. } => { info!("Creating a Trusted Service Provider."); let provider_identity = ProviderIdentity::new( TrustedServiceProvider::PROVIDER_UUID.to_string(), config.provider_name()?, ); Ok(Some(Arc::new( TrustedServiceProviderBuilder::new() .with_key_info_store(kim_factory.build_client(provider_identity)) .with_provider_name(config.provider_name()?) .build()?, ))) } #[cfg(not(all( feature = "mbed-crypto-provider", feature = "pkcs11-provider", feature = "tpm-provider", feature = "cryptoauthlib-provider", feature = "trusted-service-provider" )))] _ => { error!( "Provider \"{:?}\" chosen in the configuration was not compiled in Parsec binary.", config ); Err(Error::new(ErrorKind::InvalidData, "provider not compiled").into()) } } } fn get_key_info_manager_builders( configs: &[KeyInfoManagerConfig], default_auth_type: AuthType, ) -> Result> { let mut map = HashMap::new(); for config in configs { let _ = map.insert( config.name.clone(), KeyInfoManagerFactory::new(config, default_auth_type)?, ); } Ok(map) } // Allowed to simplify the cfg blocks #[allow(clippy::unnecessary_wraps)] fn build_authenticators(config: &AuthenticatorConfig) -> Result> { // The authenticators supported by the Parsec service. // NOTE: order here is important. The order in which the elements are added here is the // order in which they will be returned to any client requesting them! // Currently only one authenticator is allowed by the Parsec service // See parallaxsecond/parsec#271 let mut authenticators: Vec<(AuthType, Authenticator)> = Vec::new(); match config { #[cfg(feature = "direct-authenticator")] AuthenticatorConfig::Direct { admins } => authenticators.push(( AuthType::Direct, Box::from(DirectAuthenticator::new( admins.as_ref().cloned().unwrap_or_default(), )), )), #[cfg(feature = "unix-peer-credentials-authenticator")] AuthenticatorConfig::UnixPeerCredentials { admins } => authenticators.push(( AuthType::UnixPeerCredentials, Box::from(UnixPeerCredentialsAuthenticator::new( admins.as_ref().cloned().unwrap_or_default(), )), )), #[cfg(feature = "jwt-svid-authenticator")] AuthenticatorConfig::JwtSvid { workload_endpoint, admins, } => { let jwt_svid_authenticator = match JwtSvidAuthenticator::new( workload_endpoint.to_string(), admins.as_ref().cloned().unwrap_or_default(), ) { Some(authenticator) => authenticator, None => { return Err(Error::new( ErrorKind::Other, "can not create a SPIFFE Workload API client", ) .into()) } }; authenticators.push((AuthType::JwtSvid, Box::from(jwt_svid_authenticator))) } #[cfg(not(all( feature = "direct-authenticator", feature = "unix-peer-credentials-authenticator", feature = "jwt-svid-authenticator", )))] _ => { error!( "Authenticator \"{:?}\" chosen in the configuration was not compiled in Parsec binary.", config ); return Err(Error::new(ErrorKind::InvalidData, "authenticator not compiled").into()); } }; Ok(authenticators) } parsec-service-1.3.0/src/utils/tests/config/providers_different_type.toml000064400000000000000000000007661046102023000251100ustar 00000000000000[core_settings] # The CI already timestamps the logs log_timestamp = false log_error_details = true [listener] listener_type = "DomainSocket" timeout = 200 # in milliseconds socket_path = "/tmp/parsec.sock" [authenticator] auth_type = "Direct" [[key_manager]] name = "on-disk-manager" manager_type = "OnDisk" store_path = "./mappings" [[provider]] provider_type = "MbedCrypto" key_info_manager = "on-disk-manager" [[provider]] provider_type = "TrustedService" key_info_manager = "on-disk-manager" parsec-service-1.3.0/src/utils/tests/config/providers_different_type_same_name.toml000064400000000000000000000010221046102023000270770ustar 00000000000000[core_settings] # The CI already timestamps the logs log_timestamp = false log_error_details = true [listener] listener_type = "DomainSocket" timeout = 200 # in milliseconds socket_path = "/tmp/parsec.sock" [authenticator] auth_type = "Direct" [[key_manager]] name = "on-disk-manager" manager_type = "OnDisk" store_path = "./mappings" [[provider]] name="a-name" provider_type = "MbedCrypto" key_info_manager = "on-disk-manager" [[provider]] name="a-name" provider_type = "TrustedService" key_info_manager = "on-disk-manager" parsec-service-1.3.0/src/utils/tests/config/providers_same_type_default_name.toml000064400000000000000000000007721046102023000265700ustar 00000000000000[core_settings] # The CI already timestamps the logs log_timestamp = false log_error_details = true [listener] listener_type = "DomainSocket" timeout = 200 # in milliseconds socket_path = "/tmp/parsec.sock" [authenticator] auth_type = "Direct" [[key_manager]] name = "on-disk-manager" manager_type = "OnDisk" store_path = "./mappings" [[provider]] provider_type = "TrustedService" key_info_manager = "on-disk-manager" [[provider]] provider_type = "TrustedService" key_info_manager = "on-disk-manager" parsec-service-1.3.0/src/utils/tests/config/providers_same_type_different_name.toml000064400000000000000000000010761046102023000271100ustar 00000000000000[core_settings] # The CI already timestamps the logs log_timestamp = false log_error_details = true [listener] listener_type = "DomainSocket" timeout = 200 # in milliseconds socket_path = "/tmp/parsec.sock" [authenticator] auth_type = "Direct" [[key_manager]] name = "on-disk-manager" manager_type = "OnDisk" store_path = "./mappings" [[provider]] name="trusted-service-provider-0" provider_type = "TrustedService" key_info_manager = "on-disk-manager" [[provider]] name="trusted-service-provider-1" provider_type = "TrustedService" key_info_manager = "on-disk-manager" parsec-service-1.3.0/src/utils/tests/config/providers_same_type_same_name.toml000064400000000000000000000010261046102023000260620ustar 00000000000000[core_settings] # The CI already timestamps the logs log_timestamp = false log_error_details = true [listener] listener_type = "DomainSocket" timeout = 200 # in milliseconds socket_path = "/tmp/parsec.sock" [authenticator] auth_type = "Direct" [[key_manager]] name = "on-disk-manager" manager_type = "OnDisk" store_path = "./mappings" [[provider]] name="a-name" provider_type = "TrustedService" key_info_manager = "on-disk-manager" [[provider]] name="a-name" provider_type = "TrustedService" key_info_manager = "on-disk-manager" parsec-service-1.3.0/src/utils/tests/mod.rs000064400000000000000000000071531046102023000167640ustar 00000000000000// Copyright 2021 Contributors to the Parsec project. // SPDX-License-Identifier: Apache-2.0 //! Static config tests to see if the service starts with different configurations. use crate::utils::config::ServiceConfig; use crate::utils::ServiceBuilder; use anyhow::anyhow; use log::error; use std::env; use std::io::Error; use std::io::ErrorKind; const CONFIG_TOMLS_FOLDER: &str = "src/utils/tests/config"; fn config_to_toml(file_name: String) -> ServiceConfig { let mut new_config_path = env::current_dir() // this is the root of the crate for tests .unwrap(); new_config_path.push(CONFIG_TOMLS_FOLDER); new_config_path.push(file_name.clone()); if !new_config_path.exists() { error!("Configuration file {} does not exist", file_name); panic!(); } let config_file = ::std::fs::read_to_string(new_config_path.clone()) .map_err(|e| { error!( "Failed to read config file from path: {:#?}\nError: {:#?}", new_config_path, e ); panic!(); }) .unwrap(); toml::from_str(&config_file) .map_err(|e| { error!("Failed to parse service configuration ({})", e); panic!(); }) .unwrap() } /// Check that the service throws an error when two providers of the same type are started, /// without setting a name (therefore they have the same default name). #[test] fn providers_same_type_default_name() { let config_path: String = "providers_same_type_default_name.toml".to_string(); let config = config_to_toml(config_path); let expected_error = anyhow!(Error::new( ErrorKind::InvalidData, "duplicate provider names found" )); let err = ServiceBuilder::build_service(&config).unwrap_err(); assert_eq!(format!("{:#?}", err), format!("{:#?}", expected_error)); } /// Check that the service starts when two providers of the same type have different names. #[test] fn providers_same_type_different_name() { let config_path: String = "providers_same_type_different_name.toml".to_string(); let config = config_to_toml(config_path); let _ = ServiceBuilder::build_service(&config).unwrap(); } /// Check that the service throws an error when two providers of the same type explicitly /// set the same name. #[test] fn providers_same_type_same_name() { let config_path: String = "providers_same_type_same_name.toml".to_string(); let config = config_to_toml(config_path); let expected_error = anyhow!(Error::new( ErrorKind::InvalidData, "duplicate provider names found" )); let err = ServiceBuilder::build_service(&config).unwrap_err(); assert_eq!(format!("{:#?}", err), format!("{:#?}", expected_error)); } /// Check that the service throws an error when two providers of different types explicitly /// set the same name. #[test] fn providers_different_type_same_name() { let config_path: String = "providers_different_type_same_name.toml".to_string(); let config = config_to_toml(config_path); let expected_error = anyhow!(Error::new( ErrorKind::InvalidData, "duplicate provider names found" )); let err = ServiceBuilder::build_service(&config).unwrap_err(); assert_eq!(format!("{:#?}", err), format!("{:#?}", expected_error)); } /// Check that the service starts when two providers of different types are declared. /// (Different default provider names) #[test] fn providers_different_type() { let config_path: String = "providers_different_type.toml".to_string(); let config = config_to_toml(config_path); let _ = ServiceBuilder::build_service(&config).unwrap(); } parsec-service-1.3.0/systemd-daemon/parsec.service000064400000000000000000000007421046102023000203330ustar 00000000000000[Unit] Description=Parsec Service Documentation=https://parallaxsecond.github.io/parsec-book/parsec_service/install_parsec_linux.html [Service] WorkingDirectory=/home/parsec/ ExecStart=/usr/libexec/parsec/parsec --config /etc/parsec/config.toml # Systemd hardening ProtectSystem=full ProtectHome=true ProtectHostname=true ProtectKernelTunables=true ProtectKernelModules=true ProtectKernelLogs=true ProtectControlGroups=true RestrictRealtime=true [Install] WantedBy=default.target parsec-service-1.3.0/test/cross-compile.sh000075500000000000000000000032201046102023000166320ustar 00000000000000#!/usr/bin/env bash # Copyright 2021 Contributors to the Parsec project. # SPDX-License-Identifier: Apache-2.0 set -xeuf -o pipefail # The "jwt-svid-authenticator" feature is not included yet because of a cross compilation # problem of BoringSSL. See https://github.com/tikv/grpc-rs/issues/536. Once resolved, # "all-authenticators" will be used again. # Allow the `pkg-config` crate to cross-compile export PKG_CONFIG_ALLOW_CROSS=1 # Make the `pkg-config` crate use our wrapper export PKG_CONFIG=$(pwd)/test/pkg-config # Set the SYSROOT used by pkg-config export SYSROOT=/tmp/arm-linux-gnueabihf # Add the correct libcrypto to the linking process export RUSTFLAGS="-lcrypto -L/tmp/arm-linux-gnueabihf/lib" cargo build --features "pkcs11-provider, mbed-crypto-provider, tpm-provider, unix-peer-credentials-authenticator, direct-authenticator" --target armv7-unknown-linux-gnueabihf export SYSROOT=/tmp/aarch64-linux-gnu export RUSTFLAGS="-lcrypto -L/tmp/aarch64-linux-gnu/lib" # Pull in the TS code git submodule update --init cargo build --features "pkcs11-provider, mbed-crypto-provider, tpm-provider, trusted-service-provider, unix-peer-credentials-authenticator, direct-authenticator" --target aarch64-unknown-linux-gnu # This is needed because for some reason the i686/i386 libs aren't picked up if we don't toss them around just before... apt install -y libc6-dev-i386-amd64-cross export SYSROOT=/tmp/i686-linux-gnu export RUSTFLAGS="-lcrypto -L/tmp/i686-linux-gnu/lib" cargo build --features "pkcs11-provider, mbed-crypto-provider, tpm-provider, unix-peer-credentials-authenticator, direct-authenticator, tss-esapi/generate-bindings" --target i686-unknown-linux-gnu parsec-service-1.3.0/test/pkg-config000075500000000000000000000003631046102023000154730ustar 00000000000000#!/bin/sh export PKG_CONFIG_PATH= export PKG_CONFIG_LIBDIR=$(SYSROOT)/lib/pkgconfig:${SYSROOT}/usr/lib/pkgconfig:${SYSROOT}/usr/share/pkgconfig:$(SYSROOT)/usr/local/lib/pkgconfig export PKG_CONFIG_SYSROOT_DIR=${SYSROOT} exec pkg-config "$@"