rusty_paseto-0.7.1/.DS_Store000064400000000000000000000200041046102023000140510ustar 00000000000000Bud1 tsIlocblob  @€ @€ @€ @assetsIlocblobA.˙˙˙˙˙˙assetsbwspblob¸bplist00Ö ]ShowStatusBar[ShowToolbar[ShowTabView_ContainerShowSidebar\WindowBounds[ShowSidebar  _{{514, 392}, {920, 436}} #/;R_klmnoŠ ‹assetsvSrnlong Cargo.lockIlocblobŻ.˙˙˙˙˙˙ Cargo.tomlIlocblob.˙˙˙˙˙˙ Changelog.mdIlocblob‹.˙˙˙˙˙˙CODE_OF_CONDUCT.mdIlocblobů.˙˙˙˙˙˙CONTRIBUTING.mdIlocblobg.˙˙˙˙˙˙examplesIlocblobAž˙˙˙˙˙˙ LICENSE.mdIlocblobŻž˙˙˙˙˙˙ readme.mdIlocblob‹ž˙˙˙˙˙˙readme_crates_io.mdIlocblobž˙˙˙˙˙˙ rustfmt.tomlIlocblobůž˙˙˙˙˙˙ SECURITY.mdIlocblobgž˙˙˙˙˙˙srcIlocblobA˙˙˙˙˙˙targetIlocblobŻ˙˙˙˙˙˙testsIlocblob˙˙˙˙˙˙ @€ @€ @€ @ E DSDB `€ @€ @€ @rusty_paseto-0.7.1/.cargo_vcs_info.json0000644000000001360000000000100136020ustar { "git": { "sha1": "e24c9b2431690c3c533dc4f7dd4b77aa9ea1974a" }, "path_in_vcs": "" }rusty_paseto-0.7.1/.github/ISSUE_TEMPLATE/bug_report.md000064400000000000000000000015021046102023000206050ustar 00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] - Version [e.g. 22] **Smartphone (please complete the following information):** - Device: [e.g. iPhone6] - OS: [e.g. iOS8.1] - Browser [e.g. stock browser, safari] - Version [e.g. 22] **Additional context** Add any other context about the problem here. rusty_paseto-0.7.1/.github/ISSUE_TEMPLATE/feature_request.md000064400000000000000000000011231046102023000216370ustar 00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. rusty_paseto-0.7.1/.github/pull_request_template.md000064400000000000000000000017121046102023000206740ustar 00000000000000**IMPORTANT: Please do not create a Pull Request without creating an issue first.** *Any change needs to be discussed before proceeding. Failure to do so may result in the rejection of the pull request.* Please provide enough information so that others can review your pull request: Explain the **details** for making this change. What existing problem does the pull request solve? **Test plan (required)** Demonstrate the code is solid. Example: The exact commands you ran and their output, screenshots / videos if the pull request changes UI. **Code formatting** **Closing issues** Put `closes #XXXX` in your comment to auto-close the issue that your PR fixes (if such). rusty_paseto-0.7.1/.github/workflows/rust.yml000064400000000000000000000037361046102023000175200ustar 00000000000000on: [push, pull_request] name: Continuous integration jobs: check: name: Check runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - uses: actions-rs/cargo@v1 with: command: check test: name: Test Suite runs-on: ubuntu-latest strategy: matrix: feature: - v1_local - v1_public - v2_local - v2_public - v3_local - v3_public - v4_local - v4_public steps: - uses: actions/checkout@v2 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - uses: actions-rs/cargo@v1 with: command: test args: --no-default-features --features ${{ matrix.feature }} clippy: name: Clippy runs-on: ubuntu-latest strategy: matrix: feature: - v1_local - v1_public - v2_local - v2_public - v3_local - v3_public - v4_local - v4_public steps: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - run: rustup component add clippy - uses: actions-rs/cargo@v1 with: command: clippy args: --no-default-features --features ${{ matrix.feature }} -- -D warnings audit: name: Security Audit runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: actions-rs/toolchain@v1 with: profile: minimal toolchain: stable override: true - run: cargo install cargo-audit - uses: actions-rs/audit-check@v1.2.0 with: token: ${{ secrets.GITHUB_TOKEN }} - run: cargo audit rusty_paseto-0.7.1/.gitignore000064400000000000000000000000321046102023000143550ustar 00000000000000/.idea /target Cargo.lock rusty_paseto-0.7.1/CODE_OF_CONDUCT.md000064400000000000000000000121531046102023000151730ustar 00000000000000# Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at rolandrodriguez@gmail.com. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. rusty_paseto-0.7.1/CONTRIBUTING.md000064400000000000000000000004561046102023000146300ustar 00000000000000# Contributing When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change. Please note we have a code of conduct, please follow it in all your interactions with the project. rusty_paseto-0.7.1/Cargo.lock0000644000001476150000000000100115730ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "actix-codec" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f7b0a21988c1bf877cf4759ef5ddaac04c1c9fe808c9142ecb78ba97d97a28a" dependencies = [ "bitflags", "bytes", "futures-core", "futures-sink", "memchr", "pin-project-lite", "tokio", "tokio-util", "tracing", ] [[package]] name = "actix-http" version = "3.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4eb9843d84c775696c37d9a418bbb01b932629d01870722c0f13eb3f95e2536d" dependencies = [ "actix-codec", "actix-rt", "actix-service", "actix-utils", "ahash", "base64 0.22.1", "bitflags", "brotli", "bytes", "bytestring", "derive_more", "encoding_rs", "flate2", "futures-core", "h2", "http", "httparse", "httpdate", "itoa", "language-tags", "local-channel", "mime", "percent-encoding", "pin-project-lite", "rand", "sha1", "smallvec", "tokio", "tokio-util", "tracing", "zstd", ] [[package]] name = "actix-identity" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "171fe3ed055b2dd50c61967911d253d47e76e1d4308acfbf99fc7affe5ec42aa" dependencies = [ "actix-service", "actix-utils", "actix-web", "futures-util", "serde", "serde_json", "time", ] [[package]] name = "actix-macros" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01ed3140b2f8d422c68afa1ed2e85d996ea619c988ac834d255db32138655cb" dependencies = [ "quote", "syn 2.0.66", ] [[package]] name = "actix-router" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13d324164c51f63867b57e73ba5936ea151b8a41a1d23d1031eeb9f70d0236f8" dependencies = [ "bytestring", "cfg-if", "http", "regex", "regex-lite", "serde", "tracing", ] [[package]] name = "actix-rt" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28f32d40287d3f402ae0028a9d54bef51af15c8769492826a69d28f81893151d" dependencies = [ "futures-core", "tokio", ] [[package]] name = "actix-server" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eb13e7eef0423ea6eab0e59f6c72e7cb46d33691ad56a726b3cd07ddec2c2d4" dependencies = [ "actix-rt", "actix-service", "actix-utils", "futures-core", "futures-util", "mio", "socket2", "tokio", "tracing", ] [[package]] name = "actix-service" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" dependencies = [ "futures-core", "paste", "pin-project-lite", ] [[package]] name = "actix-utils" version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" dependencies = [ "local-waker", "pin-project-lite", ] [[package]] name = "actix-web" version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1cf67dadb19d7c95e5a299e2dda24193b89d5d4f33a3b9800888ede9e19aa32" dependencies = [ "actix-codec", "actix-http", "actix-macros", "actix-router", "actix-rt", "actix-server", "actix-service", "actix-utils", "actix-web-codegen", "ahash", "bytes", "bytestring", "cfg-if", "cookie", "derive_more", "encoding_rs", "futures-core", "futures-util", "itoa", "language-tags", "log", "mime", "once_cell", "pin-project-lite", "regex", "regex-lite", "serde", "serde_json", "serde_urlencoded", "smallvec", "socket2", "time", "url", ] [[package]] name = "actix-web-codegen" version = "4.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb1f50ebbb30eca122b188319a4398b3f7bb4a8cdf50ecfb73bfc6a3c3ce54f5" dependencies = [ "actix-router", "proc-macro2", "quote", "syn 2.0.66", ] [[package]] name = "addr2line" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" dependencies = [ "gimli", ] [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aead" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ "crypto-common", "generic-array", ] [[package]] name = "aes" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if", "cipher 0.3.0", "cpufeatures", "ctr 0.8.0", "opaque-debug", ] [[package]] name = "aes" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher 0.4.4", "cpufeatures", ] [[package]] name = "aes-gcm" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" dependencies = [ "aead", "aes 0.8.4", "cipher 0.4.4", "ctr 0.9.2", "ghash", "subtle", ] [[package]] name = "ahash" version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "getrandom", "once_cell", "version_check", "zerocopy", ] [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "alloc-no-stdlib" version = "2.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" [[package]] name = "alloc-stdlib" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" dependencies = [ "alloc-no-stdlib", ] [[package]] name = "anyhow" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11" dependencies = [ "addr2line", "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", ] [[package]] name = "base16ct" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base64" version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bit-set" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[package]] name = "bitflags" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" [[package]] name = "blake2" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ "digest", ] [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "brotli" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", "brotli-decompressor", ] [[package]] name = "brotli-decompressor" version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", ] [[package]] name = "bytes" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "bytestring" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74d80203ea6b29df88012294f62733de21cfeab47f17b41af3a38bc30a03ee72" dependencies = [ "bytes", ] [[package]] name = "cc" version = "1.0.98" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" dependencies = [ "jobserver", "libc", "once_cell", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chacha20" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" dependencies = [ "cfg-if", "cipher 0.4.4", "cpufeatures", ] [[package]] name = "chacha20poly1305" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", "chacha20", "cipher 0.4.4", "poly1305", "zeroize", ] [[package]] name = "cipher" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" dependencies = [ "generic-array", ] [[package]] name = "cipher" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", "zeroize", ] [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "convert_case" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" [[package]] name = "cookie" version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" dependencies = [ "aes-gcm", "base64 0.20.0", "hkdf", "hmac", "percent-encoding", "rand", "sha2", "subtle", "time", "version_check", ] [[package]] name = "cpufeatures" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] [[package]] name = "crc32fast" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "crypto-bigint" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", "rand_core", "subtle", "zeroize", ] [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "rand_core", "typenum", ] [[package]] name = "ctr" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" dependencies = [ "cipher 0.3.0", ] [[package]] name = "ctr" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ "cipher 0.4.4", ] [[package]] name = "curve25519-dalek" version = "4.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a677b8922c94e01bdbb12126b0bc852f00447528dee1782229af9c720c3f348" dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", "digest", "fiat-crypto", "platforms", "rustc_version", "subtle", "zeroize", ] [[package]] name = "curve25519-dalek-derive" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", "syn 2.0.66", ] [[package]] name = "der" version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "pem-rfc7468", "zeroize", ] [[package]] name = "deranged" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", ] [[package]] name = "derive_more" version = "0.99.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ "convert_case", "proc-macro2", "quote", "rustc_version", "syn 1.0.109", ] [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "const-oid", "crypto-common", "subtle", ] [[package]] name = "ecdsa" version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", "digest", "elliptic-curve", "rfc6979", "signature", "spki", ] [[package]] name = "ed25519" version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ "pkcs8", "signature", ] [[package]] name = "ed25519-dalek" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ "curve25519-dalek", "ed25519", "serde", "sha2", "subtle", "zeroize", ] [[package]] name = "elliptic-curve" version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", "digest", "ff", "generic-array", "group", "hkdf", "pem-rfc7468", "pkcs8", "rand_core", "sec1", "subtle", "zeroize", ] [[package]] name = "encoding_rs" version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "erased-serde" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" dependencies = [ "serde", "typeid", ] [[package]] name = "errno" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "fastrand" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "ff" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ "rand_core", "subtle", ] [[package]] name = "fiat-crypto" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "flate2" version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "futures-core" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-sink" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-core", "futures-task", "pin-project-lite", "pin-utils", ] [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", "zeroize", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "ghash" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" dependencies = [ "opaque-debug", "polyval", ] [[package]] name = "gimli" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "group" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", "rand_core", "subtle", ] [[package]] name = "h2" version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" dependencies = [ "bytes", "fnv", "futures-core", "futures-sink", "futures-util", "http", "indexmap", "slab", "tokio", "tokio-util", "tracing", ] [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hkdf" version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ "hmac", ] [[package]] name = "hmac" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ "digest", ] [[package]] name = "http" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" dependencies = [ "bytes", "fnv", "itoa", ] [[package]] name = "httparse" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "idna" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", ] [[package]] name = "indexmap" version = "2.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "inout" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ "generic-array", ] [[package]] name = "iso8601" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "924e5d73ea28f59011fec52a0d12185d496a9b075d360657aed2a5707f701153" dependencies = [ "nom", ] [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "jobserver" version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" dependencies = [ "libc", ] [[package]] name = "language-tags" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" [[package]] name = "lazy_static" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libm" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "local-channel" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6cbc85e69b8df4b8bb8b89ec634e7189099cea8927a276b7384ce5488e53ec8" dependencies = [ "futures-core", "futures-sink", "local-waker", ] [[package]] name = "local-waker" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d873d7c67ce09b42110d801813efbc9364414e356be9935700d368351657487" [[package]] name = "lock_api" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" [[package]] name = "memchr" version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" dependencies = [ "adler", ] [[package]] name = "mio" version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", "wasi", "windows-sys 0.48.0", ] [[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-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", ] [[package]] name = "object" version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "opaque-debug" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "p384" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" dependencies = [ "ecdsa", "elliptic-curve", "primeorder", "sha2", ] [[package]] name = "parking_lot" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets 0.52.5", ] [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pem-rfc7468" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" dependencies = [ "base64ct", ] [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project-lite" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[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.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der", "spki", ] [[package]] name = "pkg-config" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "platforms" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db23d408679286588f4d4644f965003d056e3dd5abcaaa938116871d7ce2fee7" [[package]] name = "poly1305" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ "cpufeatures", "opaque-debug", "universal-hash", ] [[package]] name = "polyval" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", "cpufeatures", "opaque-debug", "universal-hash", ] [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "primeorder" version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" dependencies = [ "elliptic-curve", ] [[package]] name = "primes" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68a61082d8bceecd71a3870e9162002bb75f7ba9c7aa8b76227e887782fef9c8" [[package]] name = "proc-macro2" version = "1.0.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec96c6a92621310b51366f1e28d05ef11489516e93be030060e5fc12024a49d6" dependencies = [ "unicode-ident", ] [[package]] name = "proptest" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" dependencies = [ "bit-set", "bit-vec", "bitflags", "lazy_static", "num-traits", "rand", "rand_chacha", "rand_xorshift", "regex-syntax", "rusty-fork", "tempfile", "unarray", ] [[package]] name = "quick-error" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 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 = "rand_xorshift" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" dependencies = [ "rand_core", ] [[package]] name = "redox_syscall" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ "bitflags", ] [[package]] name = "regex" version = "1.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-lite" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30b661b2f27137bdbc16f00eda72866a92bb28af1753ffbd56744fb6e2e9cd8e" [[package]] name = "regex-syntax" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" [[package]] name = "rfc6979" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ "hmac", "subtle", ] [[package]] name = "ring" version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", "getrandom", "libc", "spin", "untrusted", "windows-sys 0.52.0", ] [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc_version" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ "semver", ] [[package]] name = "rustix" version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "rusty-fork" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" dependencies = [ "fnv", "quick-error", "tempfile", "wait-timeout", ] [[package]] name = "rusty_paseto" version = "0.7.1" dependencies = [ "actix-identity", "actix-utils", "actix-web", "aes 0.7.5", "anyhow", "base64 0.22.1", "blake2", "chacha20", "chacha20poly1305", "digest", "ed25519-dalek", "erased-serde", "hex", "hmac", "iso8601", "p384", "primes", "proptest", "rand_core", "ring", "serde", "serde_json", "sha2", "thiserror", "time", "tokio", "uuid", "zeroize", ] [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sec1" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", "der", "generic-array", "pkcs8", "subtle", "zeroize", ] [[package]] name = "semver" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", "syn 2.0.66", ] [[package]] name = "serde_json" version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "serde_urlencoded" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", "itoa", "ryu", "serde", ] [[package]] name = "sha1" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "sha2" version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "signal-hook-registry" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] [[package]] name = "signature" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", "rand_core", ] [[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.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "spki" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", ] [[package]] name = "subtle" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[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.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "tempfile" version = "3.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" dependencies = [ "cfg-if", "fastrand", "rustix", "windows-sys 0.52.0", ] [[package]] name = "thiserror" version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", "syn 2.0.66", ] [[package]] name = "time" version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", "num-conv", "powerfmt", "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.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "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 = "tokio" version = "1.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" dependencies = [ "backtrace", "bytes", "libc", "mio", "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", "windows-sys 0.48.0", ] [[package]] name = "tokio-util" version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", ] [[package]] name = "tracing" version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "log", "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", ] [[package]] name = "typeid" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "059d83cc991e7a42fc37bd50941885db0888e34209f8cfd9aab07ddec03bc9cf" [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unarray" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" [[package]] name = "unicode-bidi" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[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.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[package]] name = "universal-hash" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ "crypto-common", "subtle", ] [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] [[package]] name = "uuid" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" dependencies = [ "getrandom", ] [[package]] name = "version_check" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "wait-timeout" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" dependencies = [ "libc", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.5", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" dependencies = [ "windows_aarch64_gnullvm 0.52.5", "windows_aarch64_msvc 0.52.5", "windows_i686_gnu 0.52.5", "windows_i686_gnullvm", "windows_i686_msvc 0.52.5", "windows_x86_64_gnu 0.52.5", "windows_x86_64_gnullvm 0.52.5", "windows_x86_64_msvc 0.52.5", ] [[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_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" [[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_gnu" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" [[package]] name = "windows_i686_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" [[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_gnu" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" [[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_gnullvm" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" [[package]] name = "zerocopy" version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", "syn 2.0.66", ] [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 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.66", ] [[package]] name = "zstd" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" version = "7.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" version = "2.0.10+zstd.1.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" dependencies = [ "cc", "pkg-config", ] rusty_paseto-0.7.1/Cargo.toml0000644000000074330000000000100116070ustar # 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 = "2021" name = "rusty_paseto" version = "0.7.1" authors = ["Roland Rodriguez "] description = "A type-driven, ergonomic alternative to JWT for secure stateless PASETO tokens." documentation = "https://docs.rs/rusty_paseto/latest/rusty_paseto/" readme = "readme.md" keywords = [ "paseto", "token", "security", "api", "web", ] categories = [ "cryptography", "authentication", "encoding", "network-programming", "web-programming", ] license = "MIT OR Apache-2.0" repository = "https://github.com/rrrodzilla/rusty_paseto" [lib] doctest = true [[example]] name = "actix_identity" required-features = ["default"] [dependencies.aes] version = "0.7.5" features = ["ctr"] optional = true [dependencies.base64] version = "0.22.1" optional = false [dependencies.blake2] version = "0.10.6" optional = true [dependencies.chacha20] version = "0.9.1" optional = true [dependencies.chacha20poly1305] version = "0.10.1" optional = true [dependencies.digest] version = "0.10.7" [dependencies.ed25519-dalek] version = "2.0.0" features = ["zeroize"] optional = true [dependencies.erased-serde] version = "0.4.5" optional = true [dependencies.hex] version = "^0.4.3" optional = false [dependencies.hmac] version = "0.12.1" optional = true [dependencies.iso8601] version = "0.6.1" [dependencies.p384] version = "0.13.0" optional = true [dependencies.rand_core] version = "0.6.4" [dependencies.ring] version = "^0.17" features = ["std"] optional = false [dependencies.serde] version = "1.0.117" features = ["derive"] optional = true [dependencies.serde_json] version = "^1.0.68" optional = true [dependencies.sha2] version = "0.10.2" optional = true [dependencies.thiserror] version = "1.0.29" [dependencies.time] version = "0.3.5" features = [ "parsing", "formatting", ] [dependencies.zeroize] version = "1.4.3" features = ["zeroize_derive"] [dev-dependencies.actix-identity] version = "0.4.0" [dev-dependencies.actix-utils] version = "3.0.0" [dev-dependencies.actix-web] version = "4" [dev-dependencies.anyhow] version = "1.0.45" [dev-dependencies.erased-serde] version = "0.4.5" [dev-dependencies.primes] version = "0.3.0" [dev-dependencies.proptest] version = "1.4.0" [dev-dependencies.serde_json] version = "^1.0.68" [dev-dependencies.tokio] version = "1.17.0" [dev-dependencies.uuid] version = "1.8.0" features = ["v4"] [features] batteries_included = ["generic"] core = [] default = [ "batteries_included", "v4_local", "v4_public", ] generic = [ "core", "serde", "erased-serde", "serde_json", ] local = [] public = [] v1 = [] v1_local = [ "v1", "local", "core", "aes", "chacha20", "hmac", "sha2", "blake2", ] v1_public = [ "v1", "public", "core", "ed25519-dalek", ] v2 = [] v2_local = [ "v2", "local", "core", "blake2", "chacha20poly1305", ] v2_public = [ "v2", "public", "core", "ed25519-dalek", "ring/std", ] v3 = [] v3_local = [ "v3", "local", "core", "aes", "hmac", "sha2", "chacha20", ] v3_public = [ "v3", "public", "core", "p384", "sha2", ] v4 = [] v4_local = [ "v4", "local", "core", "blake2", "chacha20", ] v4_public = [ "v4", "public", "core", "ed25519-dalek", "ring/std", ] rusty_paseto-0.7.1/Cargo.toml.orig000064400000000000000000000052301046102023000152610ustar 00000000000000[package] name = "rusty_paseto" version = "0.7.1" edition = "2021" readme = "readme.md" authors = ["Roland Rodriguez "] description = "A type-driven, ergonomic alternative to JWT for secure stateless PASETO tokens." repository = "https://github.com/rrrodzilla/rusty_paseto" license = "MIT OR Apache-2.0" keywords = ["paseto", "token", "security", "api", "web"] categories = [ "cryptography", "authentication", "encoding", "network-programming", "web-programming", ] documentation = "https://docs.rs/rusty_paseto/latest/rusty_paseto/" [features] v1 = [] v2 = [] v3 = [] v4 = [] public = [] local = [] v1_local = ["v1", "local", "core", "aes", "chacha20", "hmac", "sha2", "blake2"] v2_local = ["v2", "local", "core", "blake2", "chacha20poly1305"] v3_local = ["v3", "local", "core", "aes", "hmac", "sha2", "chacha20"] v4_local = ["v4", "local", "core", "blake2", "chacha20"] v1_public = ["v1", "public", "core", "ed25519-dalek"] v2_public = ["v2", "public", "core", "ed25519-dalek", "ring/std"] v3_public = ["v3", "public", "core", "p384", "sha2"] v4_public = ["v4", "public", "core", "ed25519-dalek", "ring/std"] core = [] generic = ["core", "serde", "erased-serde", "serde_json"] batteries_included = ["generic"] default = [ "batteries_included", "v4_local", "v4_public", ] [lib] doctest = true [dependencies] p384 = { version = "0.13.0", optional = true } chacha20 = { version = "0.9.1", optional = true } blake2 = { version = "0.10.6", optional = true } chacha20poly1305 = { version = "0.10.1", optional = true } ring = { version = "^0.17", features = ["std"], optional = false } base64 = { version = "0.22.1", optional = false } hex = { version = "^0.4.3", optional = false } serde = { version = "1.0.117", features = ["derive"], optional = true } ed25519-dalek = { version = "2.0.0", features = ["zeroize"], optional = true } serde_json = { version = "^1.0.68", optional = true } thiserror = "1.0.29" iso8601 = "0.6.1" erased-serde = { version = "0.4.5", optional = true } aes = { version = "0.7.5", features = ["ctr"], optional = true } hmac = { version = "0.12.1", optional = true } sha2 = { version = "0.10.2", optional = true } zeroize = { version = "1.4.3", features = ["zeroize_derive"] } time = { version = "0.3.5", features = ["parsing", "formatting"] } rand_core = "0.6.4" digest = "0.10.7" [dev-dependencies] anyhow = "1.0.45" serde_json = { version = "^1.0.68" } primes = "0.3.0" actix-web = "4" actix-identity = "0.4.0" tokio = "1.17.0" actix-utils = "3.0.0" uuid = { version = "1.8.0", features = ["v4"] } proptest = "1.4.0" erased-serde = { version = "0.4.5" } [[example]] name = "actix_identity" required-features = ["default"] rusty_paseto-0.7.1/Changelog.md000064400000000000000000000103421046102023000146030ustar 00000000000000# v0.1.11 (2021-10-21) - Create initial commit - Rename structs, move mods, refactor traits - Generalize dependencies with trait bounds - Rename unit test mods, add v2localheader struct - Refactor entire project - Add all 9 shared key test vector cases - Rename some structs and complete minor edits - Add strongly typed claims - Refactor arbitrary claim to use try_from trait - Tighten up arbitrary claim api - Rename claim structs and fix lifetime issues - Refactor most structs to generics - Update readme - Update minor version in Cargo.toml - Touch cargo.toml to test git editor - Repair the readme file from a poor merge ### Notes - Not quite encrypting correctly. Working on getting the first test vector to pass. - Message struct renamed to Payload, moved around mods and refactored conversion traits. - Generalizing some dependencies by using trait bounds in several methods and trait implementations - Unit test mods were renamed for consistency - Went nuts and did a big refactor, traits, eliminated a struct or two, some other thing. :-| all good, tests passing, code looks pretty good. - All tests pass after addition. - Renamed V2LocalDecryptedString to V2LocalDecryptedToken and a couple minor edits - Still need to json serialize them properly after this commit - Refactored arbitrary claim to use try_from trait instead of custom try_new for api consistency - Tightened up arbitrary claim api and removed unused comments, small refactors - Renamed claim structs for consistency and fixed lifetime issues with borrowed strings - Major refactor to change most structs to generics using version and purpose as arguments additional struct refactors to accept generic version and purpose types - Update the project status in the readme file The PasetoBuilder and PasetoParser were incorrectly indicating that they were complete. They have not been started as of yet. - version update - Wanted to make sure nvim was opening correctly so I can start tightening up commit messages - - feature: Basic encryption and decryption - feature: Generic token building and parsing - feature: Flexible claim validation sans custom validation functions - feature: All v2.local [PASETO](https://github.com/paseto-standard/test-vectors/blob/master/v2.json) test vectors implemented and successfully passing # v0.1.10..v0.1.13 (2021-10-22) - Repair the readme file from a poor merge - Add optional closure for custom validation - Merge pull request #5 from rrrodzilla/claim_validation_issue_1 - Add chrono to Cargo and add paseto_builder (#10) ### Notes - - feature: Basic encryption and decryption - feature: Generic token building and parsing - feature: Flexible claim validation sans custom validation functions - feature: All v2.local [PASETO](https://github.com/paseto-standard/test-vectors/blob/master/v2.json) test vectors implemented and successfully passing - Add optional closure for custom validation ### Additions - Added an optional closure argument to the validate_claim method. To be used to allow the user to provide custom validation logic for a particular claim - Added logic in the parse method to run custom validation closures if one is specified. This means claim validators will verify the claim exists and verify the value matches what is expected. If a custom closure is provided, the validator first checks the claim exists and then the value is provided to the closure for further validation by the end user. - PasetoTokenParseError::InvalidClaimValueType(String) for claim values we try to convert to an invalid type - PasetoTokenParseError::CustomClaimValidation for claims which fail in user provided custom validation closures - Implement Default trait on all reserved claims so that they can be passed into custom validation closures - Implement From(&str) for CustomClaim so that they can be passed into custom validation closures which always ignore passed in values when adding the claim to the validator - Move chrono from dev dependencies to dependencies - Added PasetoTokenBuilder in preparation for adding PASETO validation logic - extend_claims method to GenericTokenBuilder : bump patch version 0.1.13 rusty_paseto-0.7.1/LICENSE.md000064400000000000000000000020531046102023000137760ustar 00000000000000MIT License Copyright (c) 2021 rrrodzilla Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. rusty_paseto-0.7.1/SECURITY.md000064400000000000000000000004621046102023000141650ustar 00000000000000# Security Policy This crate has NOT been independently audited. As with any code you find on the web that has not been audited, usage of this crate is at your own risk. ## Reporting a Vulnerability Please report any issues to the maintainer by creating a bug report. Thank you for your contribution! rusty_paseto-0.7.1/assets/RustyPasetoBatteriesIncludedArchitecture.png000064400000000000000000001204271046102023000245700ustar 00000000000000‰PNG  IHDR""ő´Ł* pHYs  ŇÝ~ü IDATxœěÝx“ő˝?ţç ƒ†śiŇŘR) ™(bŁÝ”YąQ;áł1[@/щ ^°sΗĆď9›ŰÔQˇăÔŁçXÜvńăœK‚č@Q( ا“TčÄŐT¤žŹ$Ŕhęb›Đ$Ւďĺéď´yßšď÷×ăşv %˝ó¤ď<ó~żŢŻ[…B „A002şœ+ š]‹˘"BÔi@˙:555[ŁŃ|W|\ooŻŽ˝˝Ý¤§§ť&NœčŢďěěl˝ü[×ĺ˙dQ- "„p("dłłłgôööŢ^ݎ`ŕăľZíĽoźq‚ř{‹ĹŇďĎţôz=Ěf3“ń:Nř|žA_w8Ăţţ“O>šÔŃŃ1dee5Ŕĉ?imm=ƒ+… !˘ BˆB ‚` `ƒF0œŢŃё)>F ‘ĄÁl6CŻ×3 rƒŒĎçƒÓéě÷ľEŤŐ~™””t."¨8řBĄSžŃBFBA„™]FćÜÜܒžžž^Ż7Oüó‚‚čőzX,–p¸PSČ`E &â|>ÉĘĘ:;yňä3çϟŻŕDß* BdDA„8AŔŒžŔqÇŋçEÖf‡C†ĹbŃh„Ńhîrd \.\.W8œ8NÔÖֆ˙<==Ý5eʔŁçϟ}Ĺ …ď%B˜Ł BˆDA°ŕrčččč¸EÜR1 0›Í0›Í8dPœN'œN'Ün7€ž-­VűW1œ„B!‡Źƒ%DĽ(ˆÂŔĺâQsjjęÂÔÔԏg.p%tX,–đ‰ň‰ÁDüŻNrrrŽuvvęěě<€žpâ’u „¨BĆárđ°äćć.\í(..î:ôz˝Źă$lˆ5'b8ˇu"VMvpP0!dě(ˆ1xddd,îíí˝Ăď÷§§ĽĽ…nżývV;Sd09xđ`č… ‚N§kŸ8qâťmmm{AÁ„¨P!d‚ ”eggßÝŰŰ[âőzó´ZíĽ;î¸c‚Ĺb Żz"C‰ĂáŔťďž{ŠŁŁcBVVÖى'Ö´śśž …Şĺ#!JDA„Ë.Żz”ĺććŢsţüů" ďčlYYÄđAH´ÄPR]]>Bœ››[wţüů7TÓj !}(ˆ„&‚955uľFŁů×ëÍKKK -Y˛D°X,(++Ł„ĎçCuu5vďŢşp႐••u6ěëěěÜD˝LH"Ł BŽ eŠľ°X,°Z­´ÝBâÂétÂnˇĂáp ĄĄľ%ŻĐI4DHBĂGWWׂÁ Ś  VŤeeeÔÃČĘĺrĄşşvť HJJ ¤¤¤ěŁPB˘Z>o(”DDA„¨Š ćěěěľ>Ÿď~ „gC…˝^ż˝ľľőEŞ)!jBA„pOcffćý===˙ÚŃёi0`łŮ(|ŐCIUUÜn7´Z헓'OţĎ/żür;ž!źŁ B¸%‚Őh4ţÔĺrݤÓé`ľZŠŕ”¨žXčjˇŰá÷űa4?tš\BĄ]îą2DWÄ­żß˙@ H*-- o˝’hÄ­›={ö@ŁŃu:ݟhë†đ†‚ႠÖôôôuíííFqëĹjľRŸBĐ×§Änˇ‡ˇnŇÓÓ]íííOŇ* á˘X‚ łłł-Ž~”——ĂjľR‡SBFŕp8`ˇŰąuëÖČU’ßP- Q* "DqA°äććţűůóç‹hőƒń¸Jrš˝üăĄPČ!÷؉DA„(‚ zeZ­öůŽŽŽĚâââđÉBHlÄ7ľľľĐjľ_vttüúîwă“{l„P!˛Á¨ÓéÖƒAŰäɓ'/Y˛D¨ŹŹ¤cˇ„HŔĺrĄ˛˛ťwďőôôô$%%Uůýţ´mCäDA„ČBcFFĆómmmË ŹV+l6mż>ŸUUU°ŰípťÝČČČxł­­íß(9P!q5°ţٞ˛VŤUîa’°ěv;*++ŠŽ„Čf‚Ü ‰AËÔŠS~ë[ß*Ú˝{7\.…BdfľZárš°{÷n|ë[ß*ppęÔŠNA,r$ "DR‘dîÜš„Ăá "TBŚŹŹ ‡ÄÜšs @„Ä " A,yyyM@¨!ĘfąX’źźź& $D*DS‘+ ßüć7gQ!„O‘ä›ßüć,Đ ‘ÂÄp[0@áŰP+$HKDHLA0R!DýF$F™‡F8GA„Œ‹ ĆĚĚĚ7œŚBHâ"œÎĚĚ|ƒ / "dLAĐëőúgœÖjľËślŮB„$’-[ś@ŤŐ.pZŻ×?sův „D‚‰ZVVÖ/5' ýü…^ > „p’^xĄPčçĆ“••őKšÇEřAUɨA°hľÚ7:::2×­[G­Ř !C[Ç?ůä“âÍőîĄ.­d4DȰA0feeU{˝Ţ‚ŇŇRTUUŃÍč!Łrš\°ŮlŘłg˛˛˛ź^oÝdž ‡śfČ ‘u W_}uÁÁƒQ]]M!„ŁŃˆęęjä÷íźí?}­˛˛˛ß#ÝűŢχźÎM×őűýwłŽ˙z^Ć5ĐMNňűHbp8°Z­hmm ĺ´:˘~ߐ{D:‚ 賲˛v°Ź^˝•••tF厶‚ż§ÇüĂ…ŻşpŽű œ|¨÷7Ću,Ă=ßŔŻo8ýú+Ôĺr5WazňUH›”‚šşoB79ó2Ža;X˘b1keeeŇúőëwO:Őáőz—„B!ŸÜc#Ň  ˘Rb-Ȕ)S&§í…¸˝3-) ßÖ_‡ůYץč*3f¤ćČ8:IŻ×Ăáp ŞŞ <ňČŁz˝žÄď÷ß …\rŒމÇrFc’Ýn‡Ůl–{H}+ďœ;LÁƒS-A/ŢnuŕíVpěJ0Y8m>­˜(„ÍfƒĹbŐj-š‹V?7_~Ǘ…WIV]ť”jIëFŠ‹‹oLOO˙T„%T7˘ D@KRRŇ˙Θ1Căp8¨„Łm§đ?§ŞŠöƒp+r•ä‡Ůüřš2ş[pŒĚf3Ünˇ`ąXŒÝÝÝď ‚đBĄCîq%ş r Ń ‚`ppůňĺBŘŢź÷ž÷s”ÖŮ(„ŐxťŐŇ:î}ďçŘŢź_îápMŹYž|šŔÁËs0‘­ˆČH,J]ˇn*++ĺˇÄَ8ľƒś_ˆŞ‰'nţpjţĺšĺtxœÄ~#FŁO>ů$ąĘŒ‚ˆ AĐgddüOggç2*J?ąţcۙ˝tŻ’PZ‚^ýôSş_Ě8P!¤Ogo76œ~ŰÎěĽ@2N—‹XQ\\ź4%%ež 7S‰/މŁË'c>ÍČČ¸Śśś–NƌљNžřtÔ<Œ §_§BČeb YPó0žřtÎtzäWĚf3jkk…ŒŒŒk.Ÿ¨ĄÉ9Ž(ˆÄ‰ ćɓ'˙uÌĆĎ>űŒÚľż§Ď}ţ2,ŒWĎíŁBČ0:{ťńęš}°ü1žűüeř{:ĺ7ÄNŹ3fĚ0Nž<ůŻF⇂H‚`NJJzΜ9SčdLôÄ"Ž€B˘'ސP ‰žx˘fΜ9S’’’ާ0D$vůhا˗/×8N !Q@h„ń‰Ü˛Ą@˝^§Ó)ďý”Ž÷J‚ˆ„.ż€ˇ”——ĂnˇË<>loŢO„Ć" ő!‰ŽÝnGyy9lĄ0"- "™6mÚ?ŘRQQA!$ ‡=Nýيǎý!éěíĆcÇţ€˘?[qŘCÝÍGcˇŰQQQ[.ĎéDt|WZ­vCggçę2şŁm§đ›Ď7ŁŢß(÷PI-A/účqęňńëëVQëřˆ7Ě[šrĺľZíTŔdÉPj›Nź‰ů­‚’ NvšńŁ#żRt#´ăǏÚę^´h˛˛˛˜?—F:;;×Đ˝i˘GA$ ‚ X=ĎăB’““ĂżVjń÷tâŢ÷~NÉ!áFh÷ž÷sř{:ĺÎ č÷űŹŹ,,\¸P˛çÈÇăů#Ýľ7:ÔĐl—_H[***$! ,ŔÔŠSĂˈgΜAJJ ęëëqüřqŔŒ3pŰmˇI’ćŁńÎŮĂřĹŃ)€BúŠ÷7bAÍĂxfŢZ|?ďVš‡ýćNу>8äcÝn7vďŢŹŹŹa-ŤŐ §Ó‰őë×oĄPČÓUŽ‚ČA0ŘR^^ŽŞŞ*f×=pŕŔ!äÁě÷Cłk׎~UŢ@ß2ăĄC‡°jŐ*̙3‡Ů˜FăďéÄóÇśáŐsűâöœ„žtövă_œĎŕGŢŕß掐˝+ëŔڐÂÂÂAófww7öďß?hĺ$Ö0RUUŸĎ‡­[ˇnÁ …¨oţ0hkf‚ ˜“’’Ţ///‡Ýngzí ôŤÖC€~?$CHä×7oޡ^#GŰNážĂR!„DĺŐsűpßáGe-d=tčPż9r¨ŐC‡áąÇBXőąŰí(//GRRŇű—?ؒ!P‚ ćɓ'˙uöěŮÖ!čű°Ůl0 ýBˆhඋÁ`6ĹKm{ó~*H%„Œ™XČ*G‹řĄćÇČUˇŰŞŞ*źňĘ+C~ŕ›3g ™ŒĹnˇcöěٚɓ'˙•ÂČĐhkfAôéééťg̘1Ĺáp0ťî+ŻźŇoYP #‘ĹŠ"ƒÁŻ×‹ ôűá˜Ňëëëc^>IŇĎÓ=b!ă&śˆ?âýëoúˇ¸=ďŔŐą@u¸m˜ČÇ-]ş”Y9X,–)gΜŮ- ĄPČÇô 8GA$‚ ú”””RSSg8čőz&× S˝^oż•ĄB,\¸ .ÔlgÁ‚ýşˇuŤ3Źú𡴠BaâíVNź{›oz3Rs$}ŽĄVC–.]ŠC‡ YwôE‹aÁ‚’ŒIŻ×Ăápŕú믟ŃÓÓó 7Sš‚śf"dddüĎĉżőÖ[oM`B€ĄĎłĹ`0 ŰńŻŤŤ+üëá‚L,{œXüŢZ !„ŚNvšąř˝ľ8쑶fSźM†(+++źš<0„ˆu#O=őÔ¨!$֚<˝^ˇŢzkÂĉż•‘‘ń?1]Le(ˆ\ŚŐj7´ľľ-Ť­­ĚfvŰx_źąÜ‚úřńăýŽÇzůđšĎ_ĆC=NGs !’čěíĆC=.Y{xŻ×;hŽőz˝ƒŽđFhzŠ8pO=őTĚŹfłľľľB[[Ű2ęžzmÍ Ü+d͖-[Ŕ2„Ŕ’%Kúýˆ÷<ë ×ëĹć͛ű}Ő2˘ż§|üŸp|ů“ëBČH6œ~çşżŔoĚk˜ń­ drrrx &šĺăǏc÷îÝá늫۱Ôć™Íflٲ+WŽ\#ÂÔc„VD ‚Ŕ–uëÖ1mX&2 ƒ÷XOť¸Ýn<őÔSý–‡Ş!3ÜwřQ !„¸zťŐű?Š3f×,,,6$,X°=ö.\8jéîîĆŽ]ťPUUŐ/Ü̙3‹-ŠyœVŤëÖ­€-—߃ZB݈\îňżË—/GeeĽdĎłhѢ~ű–^ŻˆjIp¨+ƒÁŔä†MGŰNáGG~E[1„Yˆu#ŻÎ˙će\ĂäšâJą¸2đôáhęëëՓˆ+)âœ-ˆee¤˛˛.— ;věř_Až›Č Ď6ˆDÓŐ°ęšęvť‡\Ľ÷##÷÷ďß?âň`ww7^yĺ•AĹ­ƒ6›-ćąnoޏǎý!ćëBH,:{ťQZgĂSs˙÷›b_mŽ„‘3fD˝rěőzńĘ+Ż Ş'WYÄšz`…XÂHUUœN§&яő&lŃét5˝˝˝VÇtĹÓ1………Xşté ô˝`Á‚~÷=čîîĆĄC‡†]INNĆ… ávťĂŞ ,ŔŇĽKc>-óħ¨K*䧙6)psÖźđ×§OÉĆôä옎˝ý–gúýţ\w+Î]l ˙ţďQŔ…ŻşĐxĄ9Śç"ü{ěŘĐč?ßŢđS&×K ݁°˙ţA§möB:„ŠS§Žű&zâą^ƒÁ`Đét5ž=Ž q.!ƒˆVŤÝ  ď˝÷žŔ2„Wn˛šŒ'X¸:ÚŞˆÁ`Ŕc=†]ťv1ëôGMĘGÚ¤”pИŁ3…ĂEÚ¤ĚI›)ůóߜy݈żÇľƒżçř…Ó¸đUW8´÷7‡ƒĘ…ŻşQ•WĎíĂ…ŻşâÖüĚívăŐW_TäşpáB,Z´¨ßÜůäˎŽŽkĽx5a‚ˆ^Ż˙8--í†Ď>űŒYűöĄˆý†kĺ.@:t¨_˝ČSO=Ĺ|U„BÄŕqsć<ĚϜ—í•X(5ˆ ÔńUŽ|y|y”‚ gä #sćĚt‚Fę>Ÿ×_ýĽ .|ęóů˘x5!‚ˆ^ŻćŇĽK?{ď˝÷&°îœ:œăǏăŔƒ^Č˘ŹŹŹ~íÚY݊loޏ§›^˘˘pi“RpWÎwű‚‡Wž+‚QŁŃě¸ďžűd !@_Ąjaaa¸ u¤@ŇÔÔséúú"^=űNL× ě姙°,ŻwM›ĎÝŞG"˜“6Ďý ŸűüýâřsËźyś†śpćŐłďŕîĹHůĆIŽ?pË\4c̸† Ż źĂáŔkŻ˝śC„ŮĄPČ×đŞWDt:ÝŠ™3g~“UÓ˛Xt†ĹÖĚW—žĆ¤ ß@ÇW]¸˙Żż¤ITf‰>Ô˛"2 %ʑŸfÂö[ž†vRJxŽci¨ÂÔHńjľÉçóÁbąŕôéÓóűýŇÉČHľADŤŐn˜0aÂęÚÚZ!^u!щxťj–!DDaDjÚv‰†ÚƒH$Úž‘Odą #nˇżűÝďú}Í`0`Ì᝼Ku˛q4N§ĹĹĹĄK—.mRkł3UšëB˘ĺőzQ__sĘî’ÂHüˆáă{9óĺJ\%R‰ô=GÂĄ„Hk¨"bF"WD"kBÄ "'ľ×‹¨.ˆ\Ž işďžű’ěvťÜÑÜh?ˆF¤3=9+g–âžź;ĚV*‰DD_u፳Á–Ó{h•D#…ë0rčĐĄ¸ŚFĂjľâľ×^ ŐՋ¨.ˆčőúFcĄRęB¤í/p,h IDAT …śuőc(‰D"Ń* [фË0ŇÝÝ­¸\Šqš\őjë/˘Ş ˘×ëŸńűý~úé§PZ]kcýÁŁ0›´I)¸'Ż+MĽŞ-< "ƒýýâŘŇźoœ­Ąţ$ă4–"’˘€UiœN'n¸áčtşgŐÔ_d‚Ü`Ełßďô…^ 2í¤lżĺi䧙$•:MOÎFĹŹpčΗđřܟP!ŁşzĘUx|îOpčΗP1ëLOΖ{H\O€IžŻ.}-Ѩ”Ál6ă…^€ßďT՟ъbED}JJĘÉoűŰSĽ¸Œ’t}}1Ś3ô´2éÉ٨¸ö,ËťSîĄ(­ˆDçÍłÁú“˘:’QŒ7„DŠuŽäĹbÁÇüŽŽŽkŐp?U‘œœœ—.^źhuťÝ‚šëBś7ďÇŤg߉ů•ÂČđ=€¸\Žđ݁<Oř÷‹eĐăG "‘ rrr ŃhÂż71Ž”OH†Ç"„ˆsۏňž/yV9ů|> †Đ”)SěçašÇ+î7Ô.Ő]š{÷nU§Fśmż˙ŻżŒéVÜŚĄ0rE"1d üŻÇăA0őű‡ "#Š­­ő1IIIČÉéťÍťNţWM–ĺ݉eywR €eiźĐ|eŽTiŃëőŘşuŤ°dɒ•‚ źĹű‘^ŽWDAĐ'%%ľ,_ž\ŁćŁşCÝE—őn˘R[W1<|>_ř×эѬ[ˇnĐ×FZyňÉ'c~N1¨äää@Ż×‡šşÂ3 $ŇÍeńşkݜŹV+věŘƒÓxޢá:ˆL:őŕ”)SnűěłĎ&¨u5d¨"˘02~j bčpš\áŔá÷ű%{>9‚Čpt:]8”FîĂI˘Šç0ľ‡ŸĎ‡ëŻżţŇŋßűÇ?ţqťÜă/nƒˆŘ=őŕÁƒc^2ćřNżˇvČ"˘026i“R°ŇTЇg–rׄĚçóÁĺr…ƒGkk|ß´”D†’&FŁ‘ť­ÚŽŻşđŇé=ŘŇź'!ŽýĆkîJ˜Œ˝ˇ˝ˆŠ9ă~%s8¸ýöŰŽťŽrDAĐk4ĎęŐŤ“ŞŞŞäŽ$ü=¸ďđŁ8ŮĺőąF˘sO^ ž˜űnH @SSS8|HšÚ Ľ‘t:]8”Ěž=››“ŽŻşđŰc˙7ÎÖČ=ÉÄ{Îş6ŀ×n}şÉŠă~>%łŮlŘ´iS0äđ¸EĂe™:uęÁ””‹ÓéäîSO´˝űĎQ……‘áÍϜ‡'Ž[ĹĹMč<šššĐÔÔ÷ŃđDĘÎÎĆěŮł1{öěpŹ’żpżý|3Ž|yTîĄ0%×\umŠűďř㸟SÉ|>Ěf3şşş?Śšé4Íi“RđÄÜUŠŻƒGSS“˘R2´ÖÖV´śś˘śśIIIáP2{ölš‡6¤9i3ń§[žĆ›g˙‚ßŰŹŠí9?0ěrŁâĂçąţŚ÷s+•^݇ÝnÇíˇßnĄŒˇ-Ž‚ČĺS2Ű׏YŁÚş'>݀ˇ[ăú^ #W‹ęęę~7”‹¸:˘´ŸO%‡ŃÉ.7ůř?%ťž\ŒF#ž|ňIÁď÷?*‚QîńŒFńA$++Ťş  6›MîĄ0÷Üç/ĂńĺG’?ÚÂHĹŹ°ˇřEŮWA\.ěv;6mڄ††YÇB⯥Ą›6m‚Ýnďw×b9\=ĺ*ě-~łu?!Däřň#<÷ů˒?OźŮl6 ++KńGyDA(óz˝jܒ9ěqbĂéřmáŠ!ŒLOÎĆžâߣâZy'[§Ó‰7bëÖ­pťĽ[Í"|pťÝŘşu+6nܧÓ)ëX*Ž}űŠéÉٲů]ܟ—ç0rWÎ|ěťíEYĺ:NTUUaϞ=ŠëzJä×Úڊ={ö ŞŞJÖ@2'm&öÝö"îʙ×çĺ5„ˆţé“ߊŽxŐbą źź:î%A”qôkŠ ":îĄPHŻĆŐЊSŁÁ[I›”‚ç̏`ăw—­9YdĄú2żß/{ ŃNJÁĆď<Žç̏ĥ•÷\)^U›ŞŞ*„B!˝N§ű…ÜcŽ"ƒHdŞRÎďł2žöíŹńFú&ˇgdkŃîrš°qăF d\Ä@˛qăFŮjH–ĺ݉íˇ<‡ŸSžCˆHlŻ&z˝^ń…ŤŠ "j-PÝŢźÜíŰYSzš+g>śßň´,[1vť[ˇnĽ-łÖÖVlÝşvť]–S6sŇfbű-OK˛UŁŚ"zťŐíÍűĺSJ/\U\Á˘ĆŐŁm§đtÓKrŁĽ†‘ŠYȲăóůP]]M›6Q*aÎívcÓŚM¨ŽŽ†Ďßۀˆ[5,OŐ¨1„ˆžnz GŰNÉ= Ś" W-re Ĺ­VűFiiŠŞ Tý=ř™óŮęBF˘¤0’6)›žó¸,§b6nÜHÇp‰ä°qăF8ޏ?wĹľ`ÓwšnDÍ!čŤů™óU5;łX,(--…VŤ}Cîą ¤¨ ’••őˎŽŽLľ­†<l›ěu!#QBëAžçJ—Ë…ŞŞ*ÔÖÖry#:§`0ˆÚÚZTUUĹ˝~ä{9ócŞQ{ěrăůcŰäSUUUčččČĚĘĘúĽÜc‰¤˜ "‚žŤŤkÝşuëy×ËńzçěaźznŸÜĂ•œad~柸׃ř|>ěŘą[ˇnĽBT"żß­[ˇbǎqÝŽëFĆzݚD !˘WĎíĂ;gË= fŒF#Ö­[‡ŽŽŽuJ:ÎŤ˜ ˘Óé~1yňäÉj*Pő÷tâG_”{Q“#ŒÜ“W‚?Ĺ8ąŐ‘#G°qăFş QŒ'N`ăƍ8räHܞS;)şĺiܓWŐă-„ˆ~qôEUmŃŘl6Lžňš‡ÁŒó*"ˆdddŸ7nDmmm\žVjkkąqăƸՎ,ËťűŠßďD …+ţřˇ×Tu¤ˇ˛˛mmm˔°*"{ÉĘĘŞ...†Őj•{(Ě(ő¨îXą#i“R°ý–gâV”*ւPS2˘˘"¤§§Ë=Œ~Z[[ăZ;ŇWÄú Ň&ĽP@<ŇŤVŤĹĹŊhr&k›—Ši5äšĎ_VôQÝąbF^+zťüW\BH ŔŽ;¸Ť™6mL&L&“âŢŐnÚ´i())ÁŞUŤPTTF#÷ÂÄڑ;v Hţ|sŇfb÷‚˙ÂkEĎRŕd—Ď}ţ˛ÜĂ`ڞ˛RMΞ!ç“geeU͝;W5ÍˎśÂ†ÓŻË= ćÄ0Ë§Ł”oLÁĚo\Íxdƒš\.ěŘąƒ›’žžŽ˘˘"̝;wЛ_ss3ęęęĐÜŹž‰\ŠJJúNŽh4˘ŽŽNć vâÄ TUUaůň咷8˜™ŰĎŞCˆhĂé׹(÷Ě˸FîĄÄĚbą ¸¸ǎŤ`–k˛­ˆ¨q5DMËvąX‘šĂáŔÖ­[š!‹/ĆÚľkQXX8ä'p“É„+V °°P†&Žüü|˜LWę˜jjjdÍȂÁ śnÝ*KWÖhŠ9„ˆÔ4×+aUDś ’••UU\\ʚŐľmÉ EŠa$ŔnˇsSZTT„ŠŠŠ~ŁĽĽ555¨ŠŠAcc˙ÓV‹/Ć´iÓâ=̄!ކ}ŤP˙ţ•¨śśvť=.[5c‘!P׍¸*’••%[KsY‚ˆÚVCÎtz°íĚ^š‡J #UUU\ËŐh4XąbJJJÂ+ xńĹąyófÔŐŐĄŽŽ;wîÄć͛ű˝ÉÜ{ď˝r [Ő¨îÝËĎĎąŰíFUU•,wôJ˘„Ńś3{qŚS÷ą’{UD– 2}úôjZ ů×OţK§d˘Ľ”0ât:ąiÓ&.śbŚM›†ŠŠŠđ@ Ŕśm۰sçN´ˇˇz|KK vîÜţ}zz:ňóóă6ŢD Ńhpë­ˇ†_WW7俅’ƒAlÚ´ N§SÖq$ZúNŃüë'˙%÷0˜WEŚOŸžQŽç{ÁrîÜšYjY ŮŢźŸŰĆeą;ŒTWWcϞ=˛<÷xL™2Ľ_ČáÇG-B¸M››+ŮřQäĘT ŔáĂüŢSdϞ=¨Ž–çf"†Q˝żۛ÷Ë= &*++qîÜšYrʊÄ=ˆ¨Š6ÄßӉ§›^’{˛‘#Œˆő q{N†Šá T‡ú>Չ°c2™úŐč>|XqőcŐĐĐ÷ş‘D!˘§›^RĹ˝h䏉kQ[mČŻjKf(béúú˘äĎ%Ţ+†‡zĄDžĆHOOę4 o[ź(** ˙şĽĽE‘ÇuÇĂívÇí^5]__LřômŃüÚ)ˎsrՊÄ5ˆ¨i5ä°Ç‰ˇ[rCÎ]lĹÁ6IŸC !Dzź;ńÄÜU’>ÔR¸ZVVƒÁ€ŒŒŒçăń|’AŒmmmËÔPr´í¨¸'Ż+MwKţš+Ąëޕ3×*k2QcxšČîŸd|L&Sż:›ÂÂBŹXąbÔśú=ó "‚5$ńŢ;$‘WCîʙ/IűvÇƒýű÷ú…‘+çĽUśői™6mVŹX{w›ú)Bˆh˙ţýƒžĆ²ź;ś^D Ť"6› @ I+ëk3"éééëĘËËÁó‘Ý3l8ýşÜÐĹôälIęBF FúŰťˇo%.Đw%"vŽX?rńâEG)CČhŤç̏`zr6óëň`Ăé×qŚ“ýßiźčőz”——#==}ëk3 "‚ ˜ŰŰŰźŻ†ü÷˙ˇ[î!ČfÓwg^P]]=bĐ 0rEss3jjjZśÚŰŰąwď^lŰś ÍÍÍ8|ř°âťŮJB"S]]ÍüďC;)E–îĚJÁű{‹ŐjE{{ťQ3Ëë2 "ŮŮŮk , ËËƕż§{ZĘ= YTĚz@’ş;v ľľuÔÇQš"ň>4DZÍÍÍŘśm›â›Ĺ+„ˆZ[[ącǎq?×pć¤ÍLŘţ"{Zr]+bąXĢվ,ŻË4ˆ¨ĄH5QkCňÓL’ô ŠŽŽ†ŰíŽúńF,Ţ!DävťQ]]=îçNĹľ$ä‘^ľÔа.ZeDÔP¤š¨'eŇ&Ľ`ÓMě—KN'Ćü}FšBŽ"jhhä$ÍŚ›OČ#˝źŸ ąZ­Ě‹V™ŁŃřÓŇŇRŽ‹Tu5¤âÚáę)W1˝ŚÇă Óď÷S!‰Nî"ÚłgóâŐŤ§\…ŠkÄôš<ŕ}UDŻ×Ł´´FŁń§ŹŽÉ$ˆ‚`tš\7ŃjćgÎĂJÓÝLŻ`ˇŰcž…’Č”BDvťyÝŇJÓݘŸ9é5y †U—ËuŤNŤL‚Hffćý:ŽëNŞďœ;œpŤ!b÷TÖvěŘSxˆDa„$"Ľ…ńzRŻ&b×ŐÎŢnźsîđčT¨˛˛2čt:dffŢĎâzL‚HOOĎżňź8ĹţLé¤Ř’q8c*N…’H”BDnˇ‡ƒé5u‹†÷÷ŤŐŠžžžeq­˜ƒˆ 掎ŽLžƒČöćýh zĺF\姙˜oɸ\.ÔÖÖ2˝ŚˆÂIJ!˘ÚÚZ¸\.Ś×\iş;áNŃ´˝ŘŢźô*”ŐjEGGG&‹ž"1ąwˆŮĚ´żI\í:÷ŽÜCˆ;Ö[2@@’eŰHFˆšńBD;vě`^/"Ĺ6ąŇńüŢc6›™ő‰9ˆř|žűyîr´íęý‰ŐFűaS)óĆeŁuNe…ˆôôz=ŒF#Ěf3, ,˸ężĘĘĘÂßo6›a4š>U'%žBˆř\Źű‹ĚI›‰‡MĽLŻŠtőţFm;%÷0ĆÍfłÁçóĹ\'"„BĄńł ”Ř}úôiĆXÇ"‹ŠŸÇŰ­š‡7i“Rpčǘśq?rä8ŔězŃŕmâV˘œœää䄃‡^݇N§Ó5*++űý7Z~ż>Ÿ.— >Ÿ'Ą˙x}-/\¸óçłť‘]ÇW]Xđ—‡qáŤ.f×Tşf[°ţŚ“{ăârš0sćLX …ƝLżË 222ĚËËă6„ř{:*„ŔsW1 !>ŸyńZ4ĕ‘X&pqe$ˆFفŃh„ŃhDNN ƒŹăŃétĐétƒĆávťáńxŕršŕršTßćžçô§Ďž=›ŮJ—vR ž˜ť ?sžŔäzîxj0`0póÍ7čťß‰ËĺBSSóIšńBÄçßącÖŹYĂěšËňîěgkpäËŁĚŽŠt›OîÂĎŽ{HîaŒ‹ŐjĹ/~ń‹Ärqoͨa[ŚčĎք:-łŻř÷LkC‡d§dĆB :+łgĎ˙/–żhwkf<‚Á šššÂ˙ă™Ú^łĹĹĹLovzüÂiü ö˙av=Ľ›–”…şťěrc\Xlό{E„÷m™wÎN¨rO^ ÓâńxBZ‰wřKRR PPPŔu(Q[úŽôΞ=›Yřœ´™¸'Żoœ­ar=Ľk zńÎŮĂř~Ţ­reĚXlόűÔ ďŰ2oœý‹ÜCˆ›´I)xbîO˜^s˙~eO´Ó4z˝‹6› ÷Ýw TBCÉ}÷ݛ͋ĹÂʼn5†ë9ቹ?I¨ŽŤ<ż'Y­Vtuu{{f\AD„˛`0¨áľĽű™N_~$÷0âfĽŠ”ů)ÖÝSYH„0b4ą|ůrTTT ¸¸x̧\ÔH§ÓĄ¸¸Xž|šbWiŐB€žBă#GŽ0ťžvR V&Đq^ǗáL§ňţ]ŁQVV†`0¨š\˛1fă "(ö~4Żšţ,÷âfzr6žÉî‡YŽS2ŃRk1›ÍXłf ĘËË1kÖ,š‡ŁXłfÍByy9ÖŹYٍ&‹j!"‡ĂŸĎÇězĎ,Ĺôd> ­Yŕő˝IܞÉČČxp<ß?Ž ŇŰŰ{ĎŰ2Őçůíf7V×>Ŕt5d˙ţý˛’‰–šÂˆŮl†ÍfCii)7'_” ;;ĽĽĽ°Ůl˛’D !@ß8YnŃh'Ľ âÚ˜]Oéx~o˛Z­číí˝c<ß;ć "‚Ůď÷§óş-“HEŞÓ“ł™×uš\8qâłëI‰÷0@hűeüt:Ź$‘BˆčĉLZ/Ëť3aVEĢU•••Áď÷§çŢ3c"ŠŠŠŤyޖ9ĐÂnSéX’`ÝŇYj<†ŁŃˆ5kÖPaL $kÖŹ‰Űܕˆ!DÄzŽH¤U^ߣÄí™ÔÔÔŐcýŢ1Fó–çĹă)‘:О^ q8đűýĚŽ/ź„˝^ŤŐŠňňrڂ‘Pvv6ĘËËaľZ%=e“Č!čkáϲ–,‘VEŢnuŔßÓ)÷0ĆĹbą@ŁŃŒůô̘‚ˆ FŻ×›Çk}Č;çř\ň–Ÿ |>ÓjřxSząX,¨¨¨˝íz"1 ¨¨¨`ڄK”č!DtäČŚ…Ť‰´*Âë{•Őj…×ëÍÁ8–ďëŠHYZZZHîâŻńâů–Ëc!ĹjˆŇ TGŁÄ0b4ałŮP\\Ěäzd슋‹ałŮ˜m×Pš" ŇŞČ8ńú^e6›‘––0Ś"Ň1‘ÜÜÜ{–,Y"Œid qŚż‡# IDATӃzŁÜÈ –Ÿ<˜]ONJ #‹-Byy9Ձ(€N§Cyy9-ZF3îëPŹĄĄé˙—DYŠ÷7rŰSdɒ%Bnnî=cůž1‘óçĎńZňżŻ“{qÁz5DiTc%wÉÉÉÁš5kÂ7u#ĘqóÍ7c͚5ăţwĽ24–sH"­ŠđúžeąXpţüů˘ą|OÔADě˜ĆëąÝÝçsŠkŹX~bpš\Šě +šÂˆĹbÁęŐŤiDÁt:VŻ^=ŚÚ !#sťÝLó&ĘŞŻďYbFK—Ő¨ƒHvvöÝ\ÜĎa 3œěRßę@´˝x†FŤŐJľ )..†ŐjuŤ†BHthUděNvššÜžŃëő(((@vvöÝŃ~OÔA¤ˇˇˇ„×Ő^—¸ĆŠeq:hmmev=%ŠGÉÉɁÍfŁ12 °Ůl#ţŰR‰Nkk+œN'łëąœë”Œ×÷ޞ˛2ôöö–Dűř¨‚ˆxl—×ú^—¸Ć"mR Ó{Ę(ů~2,IFĚf3VŻ^PwĹU›¤¤$Ź^˝zPWV !cÇrNyxfiBܙ—×÷.‹Ĺ2ŚcźŃވX´Zí%ƒH˘lËܓWÂěž2N§“Ëćeă%E)++CiiâÜ9TíJKKĂ{ßBĆÇď÷3[ŃNJÁ=yQŕćŻŰ3‹Z­öK4*ˆddd,žăŽ;Ćuƒ<šńş´5V,o—(Ť!‘X†‘ňňr0Q‚‚‚‚pWV !ăĂrna9ç)ŻďawÜqDŽŒŒŒĹŃ<6ŞpŃŰŰ{Ť!PÓúÜCÜ]9óqő”Ť˜\+ŃVC"ą #źŢ‡‰ŒÎh4R‰ËU‘Ť§\…ťrć3š–’ńúfąX˘žď¨ADŁßďOç1ˆř{:˘‰Ů2†K”<ˇrgE!d(‰BD,ç–sŸRŐűšź÷ŒĹbďĆkíąŃʈXxmëÎkżţą˜žœď1úTŕršTR&FkBŽhmmeÖWä{9óâ(/ďeíŢ-Ł=vÔ ’››ťôöŰo粭űďçrAr+餌$(ŒV(„ Ć´V„á¨Tźž—Ý~űíBnnîŇŃ7jéč踅Çm8čýPî!HîFçé=*ť¨Ć‚‰…ĄšÝnf'Źć@%ăő˝Ěbą ŁŁă–Ń7bÁŘŃёÉăśĚa˝ÝrCRwĺĚgvd7ŃkC†Ca„Œ…‘ąšs´“RT_´ÚŮۍĂv áâĹl6ŁŁŁ#s´:‘ŃVDĚĆt߼8ĐňžÜCŤB­@  š;ěJÂ+ !Łkhh@ `r­D(Zĺń=-";Œ¸š1bIMM]Čëý1>jçsO-Z,‹Ti5dtFH´(„DŐܓEŤźž§#55uáH-ˆ,ŕq5ÄßÓŠúnŞ,—"YŢBÍ<ZZZäQ¸–– !Qb9÷¨}{ćd—›ŰcźŠŠŠ Fz̈AÄăńĚĺą>„ÇŁNcĹj)˛ŠŠ)a˜UYY5+#Ł2ŕőĄńć÷űŃÔÔÄäZ‰°=S×Ę߇FłŮ Ç3w¤Ç DA°|ևđzÔ)Zůi&ĚI›ÉäZ´łŮLmŰIÔ Ý( Ő4'm&ňÓLLŽĽTZřŰF3„˜)†2ҊˆŮ`0@Ż×łU|ěSwa•ü}>Nœ8ÁäZj–““C7°#cVZZ:čŽĚd°'NŔçó1š–ÚWEx|oÓëő0 ŔŤĂ‘ÜÜÜ;xLôg:=h z冤îšĆf/”VCF§Ńh`ľZĺá”Őj…FŁ‘{ŠÇj.b57*UKĐËĺÝxÍf3rss‡˝ď̰A„×Ffu_¨űÍ5?ÍÄôwdd˗/é&g$ą%%%aůňĺr#&&“ ÓŚM“ô9XŢOíŰ3<žÇÖŘlČ "‚ž×FfjŻaľôčrš˜ŠŞńSŸĹb— 7ƒÁŔe­]zz:î˝÷^ŹXą=ô¤?ă~żŸŮýgÔž=Ăă{\Dcł!k=†[ᶑ{hcĄÔm™ÂÂBŹ]ťůůůLŻ+—œœđÚC‡(Oqq17ő"EEEý~ž5 î˝÷^IŸ—śg˘Ăă{ÜhÍ† "<~ô÷tŞş>dzr6łmVG怾ONˇŢzkżOPéééĚŽ/>GźV]4 ÷ËéDy–/_Žř•ĂÂÂBŹZľ %%ƒWL&ŠŠŠ${nVsŇŐSŽRusł– —Ë~"#ŹDx-TĺńŒőX°jŘÓÔÔÄ´Ch~~~ż Öd2aíÚľ())a6ń–”” ˘˘B҉PdąX Óé$’Xt:bW™M&VŹXĹ‹÷űŃŢގšššp+ö[o˝U˛0 ™…ľ77ăń˝n¤‚Ő!ƒČŋçńDŽů›ĺ‚¤X61cŠŽŽ›7oFss˙ż˙˘˘"&áÁd2…ĂŽÉ$m!šŃhÄÍ7ß,ésÄuóÍ7+Ş)^zz:/^Œ+V úŮŞŠŠÁć͛QWW‡ˇß~@ßjáPŤ%ŹPsłčđř^g6›qńâĹyCýِA¤˝˝Ý¨Ôä>’ŰřŰ;‹VÚ¤fMĚX Ż­őśm۰sçN´ˇˇ‡ż.N\ŤV­wˆˆœřęęębëH¨#&‘šR^cEEEXľj ű}˝ąą/žř"ęęęÂ+!hllС}ĂzëUÄjnš“6iŒîLŽD<ž×Y,´ˇˇ‡úłAAD3EĽöhŐűĺ‚dîĘů.“ë°Ţ–HœÄ"—s`Ú´iXąbî˝÷Ţ1Mb………áكőőőƒV]X˘-roŃäççšu:܇ QMMMř×Rm‘˛Ýža3g*ďubŚ3F¤ĄVDŒ‘ßċŁm§ä‚¤nÎrEk̤X J]]ÖŻ_úúú~_'Á˘˘˘Q÷š#—€¤Ť!z˝žNɐ¸)..Ž{×ęá> ěÝťwČíŐHíííá›>Ν;â­CbÂjŽb5g*oďy™Â8đφ "f'äĎ}|ýٌŐü,ž‚pe‚ŰśmŰ  N,>¸,а°0V><ä§4V”˛\NGź^s‹/v{´˝˝}Іá;v,|MКœąšŁX͙JĹă{Ţĺl1úŠHnnn …ލţÓrA2ŹşŠz<Iˇe†ÓÜ܌mŰśaďŢ˝ƒęG†+”KOO݆´ˇˇKşb4Šq‰;ƒÁ ůĘłX0>0đ777‡ĂÇ´iÓĆUż5eĘ&c( Â㉽šÚťŹňřžwůäĚ Jâo üBOOĎ ot×ÔÁß?J´x\ J}}=Ž;†ÂÂÂ~ÇM&L&ęëëQWW‡ööv,^ź8ü}‘{ÓRX´h‘¤×'d8‹-Âƍ™_×d2 :Š ô…ú˝{÷˘ššéééá€RTTUýUnn.óąĽŠŠ‰I¸ůYóĐxż&Ńŕń=OŻ×٧§gĆŔŻZńz˝y<ž˜áąx'ZźŐ‡ŒDŹőŘźys¸ _$6SZźxqřZssó Çąd6›‘­ŢćGDٲłłÁzş¨¨hPSÁ@ €ššźřâ‹áŔšŇMł˛iÓŚőëœ,ĺV)ՉŒŽÇ÷<‹ĹŻ×›7đëý‚Ż'fx+ÚŤů ~˜Z[[Œ†öövěܚۜm Ŕ}Ű5‘ËČR݆𺉺°~ Ö××÷;ąV__őëך˝Y{URR2l1™Lx衇¿onn–4ˆ´śśöű˙0^,ćN%ăí˝o¸“3ˇfô‘ćĹŮ.ţn‹­ü4´ ÎĂ+a5d(ÍÍÍŘźy3 'ŹŻŻďRX3›Ít\—ČN§ÓÁl63ť×Џú‘ŸŸššš†vîÜžŠ]II L&ŃŢގôôôpCÁHR÷óúćŹXW‹´“RŸfRíöĚŮ.će\#÷0˘‘-úŐ "–‚‚‚xŒ‡)ťĚE‹U}Ť;[J%˛~¤¤¤$<™J‰VCˆRX,Ś7˘ŹŻŻú$LKK Ţ~űmüđ‡? w/ŠxUŹ1‘šËĺb˛mĽć:‘cţf|?ďVš‡1&hhh°pˆ_ëD˛łłš,TĺąË\´ć0ŞúVzŽÔ466Âd21Yš­†%a˝*2Vâ ČřĂaĺЁEĘUĘHŹć,Vs¨ńřާ×둝ÝŻ`ľ_éíí˝‘ÇO‰ç_Č=ɰXńů|đűý Fcék0^óçŤűŚX„?óçϗ-ˆ}AcóćÍČĎχÉd ťś´´ šš9.Ť ‘ü~?|>_̍ßÔÜO„Ç÷>‹Ĺ‚cǎÝůľ~A$ NçqE¤%č•{’H›”¤Ť!ńd4é¤ QœěělFŮ^#ď+#7Ű3WOš i“RpáŤ.FŁRßűôz=‚ÁŕôČŻő;5ÓŃёÉ[33ŢŞ†Ç‚UšÜ›ŇĐjQ*zmöÇjîRóéŢŢÍf3:::2#ż"‚ ÄýţąRó‰™9:6{›,şŞ…^ŻÇŹYłä!Cš5kws°”XÍ]ŹćR%âí=P|}‹™čż"bŔźšŽÔT}b&óú˜ŻĄ´ţ!răíőM˝FŻ`×O$öšTŠx{Œx}Ĺ_ô "Z­öR<ÄšnţŠu˘5=™ÍýeČ4ÉĽŁ×h,ć0sŠRńřx9kĹß÷ "7ŢxăPwăU4Ť†ŁA…Şě͞=›ŽěĹÓét˜={śÜĂP s˜X°ŞF<ž^ÎFń÷áŕÁk‘.š‡ Vw¤‘+hr'ź ×ęŹć0ľŢ‰—Ç÷Ŕ˝DÂA¤ˇˇ÷F—;{ťĺ‚$ňŠP•9šÜ /čľzł ˘Ň‚UßÍf3z{{Ă˝D¸ŰЉÄŰąĽą`Ń 0pŐČLJłgĎFRR’ÜĂ $*IIIF.óűýL VŐÜa•÷÷Âpńz˝ꭈř{:ĺ‚dŚ'ÇŢp‹VCŽ Iđ†^łW°)XUoCŢŢ Íf3ź^ořĆvýVDxŤ9ć˙›ÜCL~Ú̘ŻAAä šÔ oč5{‹šŒĹœŞTź˝Ě~›™Šąe/ĐwbFË ÂŰçó1 ˙ŒF#mËî$%%EŢ6=Ąą˜Ë´“RT{r†ˇ÷MÍÄ#ŔßůuĎOGƒN̰EŸ, ŻčľŰ‡NΌŒˇ÷M͸.Vĺńüt4XĽv "}čS%á˝vű°šËÔş"Âű{axE„ÇŽŞjĹęžÁ`ÉuxŚŃhčNť„[ŮŮŮĐh4rCvŹć25ßs†7‘ÝUĂA„ÇŽŞő~eÜŞšľéSb㤎Ş}č%὆ű°˜ÓXĚ­JÄă{adwUîÂG"Pó1łxŁIœđŽ^ĂěĐÜŞL 55•ťŢÎM‹}LZ铓“#÷‰ ˝†ű°˜ÓÔZ#đůž(f  ŃhžkąXdĐXńŢIn$sT|Ţ=Ţ ƒÜC $&ôfGÍs+oćťmͨ­ˆĐ'I˘ôZŚ9MÍ(ˆ(ŒZϚˁ&o˘ôZf‡ćX噽˝˝:š2Vď{?“{’ "ěđÖ)˜áĐk™z‰Œ†Ç÷D1{L€ööv#o5"ddÔC„Nő ×2ÍijcąXĐŢŢnhkFqnΚ'÷Tƒ>Eľ ×2;4Ç*˘˘Ž>:w;Ž„ ‰^Ë}hnS'nƒČ‡mŸË=˘`ô ’¨ ˝ŚÉHx~Oä6ˆ¨•Z[ÇMÚDmč5ÍÍąĘ3A#@/rĽ`т80 ßčőLԆ^Ólć6jóŽ âëYcřŚ3fłYĆ!–čč.MÚD}č5Ms›šDd#mÍB!D6Dˆ*ѧG˘6ôš&j%°8 …d !„7•••ýţK!Ńn§B!„Ȇ‚!„BdCA„B!˛Ą B!„Ů|CîŒÇ"?‡Ă€~ Q^iE„B!˛Ąăť„qŁăť„ń˘ăť„B‘B!„Ȇ‚!„BdCA„B!˛áöřn{{žúękš‡Á\zz &MŠíŸĹáp śś–ŃˆřTVV†‚‚š‡A3 ¨ŽŽ–{˛*..†Ĺb‰é_}ő5ÚŰťŘ HA&MúŇÓSäƸЊQ%ŸĎ'÷aŠ^ÓD­(ˆB!D6¸ŔétĘ;ÂLNNŽÜC}z$jCŻišŰÔ$"s¸&„B!@/rĽčí˝ó54 ƒ‘đ^ĎDmč5Ífnc1ǒ؉ŻçP(䢭…éíĽˇ,ФMԆ^ÓlĐŤ<ܑɓ'Ę=˘`4iľĄ×4 Ďď‰Ü2<ŁŃ(÷Áď÷Ë=B˜ ×ršŰԉ‚ˆÂ¨ą7Š\č$Q z-łCsŹňL€ôôt—Ăáy(„Ľ¤¤$š‡ ;—Ë%÷a‚^Ë4§ŠĂá@zzş ¸D&NœČÝş_ŹÝG•ęŇ%6…Tt̍>Eő ×2ť9ŐŤ4<ž'ŠŮƒśfćëŻ{ĺ‚jx<š‡@ôZf‡ćXĺĄ ˘RTÔE“7Qz-ӜŚf źĎ[Č¤IüU %vvÜnˇÜC $&ôfGÍs+o@ŕ}ŕréěěl•wHc'‚ÜCL(ű&}zčCŸ$ ďč5܇ŜĆbnU*ßĹěA[3 D-ˆŮĄÓ„wôf‡ćVeƒˆë“O>áî_ˆÇ*áh°hAL+"}h'źŁ×psšZŰťóř^x9s¸€ˆ ŇŃŃAŤ# Áj“Î݁@­­Üí<hmmE {˛c5—ŠšF„7—3‡ ŕ|kfâDއ?,Vű˜ÔK¤}˘$ź˘×nVs™ZkDx/ ݈€Óé”o$ă0q"Ĺ9Ńřę+6Š‚HŸŚŚ&š‡@ȸĐkˇŤšŒŐÜú˙ˇ÷ţąmÝ÷˝÷űˆyDŠ")+•l´# łăâÖĘÜšsR1ƒqíĄn-{ÍćnI,Ž‹ $Ě}Šçéł\<‘[wď0*lś˙¸Ľę`užŹ6˝yxbŔpŠ9ÚM†*ŁZÄ?nW•JZYŞ%‘Eúő}ţ MQżHń{xÎ÷đó [żżśÉďyńóëk4DťšFx$"Œą( Ţô>Ť„ˁ1ĆĹÜÝn7‡ŐˆO4E&“Ń{Q™L†""ŕą—ńÚWˆh÷BŐ5T÷hXĐŘ(Vßt%đČeRDä1ôΒ zÎ>†Ç^fćúŃŽQ‘öööqŃR3 bY`%đh3#y mę„hĐsö1<ö23ˇîŠv/ŒD"hooW?şÂE4 Ź„ĺĺę_4˛,ĂĺrqXřÜ˝{—Ň3„0d2‘G¸\.Ȳ\őuxěŠFEô{aAD,ËG˘EDńrcĺÂ+ŒHQ‘ÇĐĆNˆ=WĂk3kjFÄ{`$ĹbůHý¸ "333ŸˆV#ˆo‚A3üĄÍzŽ>†:f6GÄ{`<ÇĚĚĚ'ęÇĹŠ!§ŤŠŢ?˝Œ1.9Mš°ú˜ťwď"‘Hč˝ ‚Ř”D"A"RŸ‰Ş+Śí˜ńX”(Ë2:::8ŹĆˆöü&ęzŽ>ŚŁŁƒKĄ*˝Ô¨ˆv,f‰ˆ¨CÍD K• Źň'ăŢ˝{z/ƒ ÖĺŢ˝{ÂíÁZB…Ş[#Ú=°t˜PŇžët:çDłqŃl°˛Ůe.ץ:‘Ő|đÁz/ օž›ŤáľwńÚKˆh÷ŔH$§Ó9WüšUgŰlś_Ćăńľ]VőX, ŚVŁŹVkźz‰Hww7şťťąsçN@,ĂÄÄnßž­ËzT˘Ń(fff(eEŠ™™é^ŞnŽhŃ ąŮlż,ţÜ*ąX,…Ăá}ľ]Vő44˜SD`ii‹ľŞk¸Ýn¸\ޚuŒěÜš_ýęW RLoo/îßżüÇÄýű÷k˛žőřŕƒpôčQÝŸ JŃ:ŇŰۋ‰‰ Äb1M‡.—‹Ë3KK捆44ˆ'"ápxŐ  $5#ę,ŤUŹĐT%đšXŤ¨Hoo/NŸ>˝Ž„¨ěÜš/żü2öîÝ[“5­G$ĄV^Â0$ M‹TeYĆÁƒńꍯâŔ\ @ľ†×že扪"ŢűJgˆkGź‡ÇÇÇ!˘ĺČ*A¤:‘îîn9rdŐçnßžk׎áâŋ¸ví&&&ä7ƍ˘&ĽěÜš/˝ôz{{šŽ7s˝Al­Ÿ‹,ČÇÁƒńÚkŻq=ń†ęCśFÄ{ß#ÇŽTDâ„ËSŠ˜'+—ĺĺ—üćž={8Źfs8Přł˘(¸xń"Ţ}÷]Œabbccc¸xń"nܸ /#/źđ†ďÎdYƑ#GpúôitwwŻÚLy@QÂh Ůšsçé(}m{cŒ:f D‘[ŹJ˝Źú[0Ć"%ß,"Za%đ0z­ç‰x<žUÚ÷ż˙ýBôŁ”ŃŃтŒx<<űěłkžçŔk޾ɲLQÂtÔ"˘266EQ ŤŃĆ^xGÓuTżů!捆âÝűTˇP]CeNľˇˇ*âćÜÔÔ¸ő7 Ż3´ŒŠobˇoß޲utt´ *(ü|ww7^}őŐ5яX,†‹/btt”ëş#‘fff¸^“ ĘeffFÓhČŢ˝{ o&&&píÚ5\¸pccckžďôéӆŠáľW™ő|@Ě{^8F{{ű§ĽŸ_ó7ąZ­ŸÄăńĎŐfYühj˛˜ś:š—ŐďŮł###\ŽľSSSe}ßččha“Te¤4LŹ( Ţ˙}îRĚ{g“'Ojv}‚Řˆ÷Ţ{OÓëGCŽ]ť /ő׎]ĂíۡqŕŔÂkN-híííĹčččYŠ%źDÄĚ‘Ś&ą˘!@žPŐjľ~Rúů5"255u#‰(ýźŃil+WV ËË9.óD:;;ałŮÉd8­ě1>Źřg&&&p˙ţýusŘ@>Œ|ăƍUĄd-ˆF٘œœDWW—ŚCĹLNNjš/Ž4޸qcMŰîÄÄ&&&ĐŰŰť*éńxpäČěÝťwUä˛VŘl6.UsšSׇˆxϋD"˜ššşQúůőţ&‘Zźkćhš˛JáíŃ*=s˙ţý‚0|á _(ëgz{{×ÍKOLLŕ… ¸víšć˘ …jň8Ą˘ĺsޏöJQ”MŁccc*ÔmŠtww㥗^‘#GjšŽá—–1o4óž÷Č-Öä"ב(@ŤFƒ×ĄMZ։|üńÇňp›žuwwăôéÓk6¸X,†wß}/^Źů°łx<^“´Aů Y˙MĹľĺDEÁčč(Ţzë­5“{{{ńÚkŻ­ęŠÓ~ióFCńîyEN-ýÚľsłx§\2™%.×Ůłgl6—k•R\ÇQœ›Vńx$!Äv!Ů^{cĚôiI’„uc,şŮ÷mِ,ę`3°Ůšô^‚ć<|˜ĺrÎÎNš ZIQ-$#ëÓŐŐĹí߄×hdD˝—m5ČLeK™ššşüŁýˆĎŰî#â(ÜJáů"mœż–„ź Y Ď˝ŚDDÔ{ُ~ô#655uyŤď+gD[xaaA1=#ŞEVϢUŻ×‹ŽŽ.×’‚7$#éččŕvŽL=ŠbŢË"‘$á­žwKaŒE].WLÄôŒ$IBN˘Ťž…Ző^+BBhÉHž{L=Š65‰Yď‡áršb[ՇĺED`ąXnŠ("€˜“č*…çť‚žžžşpĆCB2™ =31Ńh´ŞCë]F\.×îĚ^¤ ˆ{ ‡Ă°X,ž/SLY"2??íć͛BĆżD imŞŠ^ 1<==M2˛ xFCcu!"!OŰ ‡ĂH&“ (Ł>(SDcŃöööOEe1˛cH§)*R)ZHˆJ$Áůó獺aú’Édpţüů5ؑŒTĎ=%ć÷ĆËȈzď ‡Ăhoo˙´œú üˆ,Ë Qr5´U)ŠÂODzzzLßAŁĽ„¨LOO#¨Ç`199‰@ °é˙-ÉHytttp=W†ç^gdD˝w…B!X,–ĺ~Ů"233óăăăˆÇăŰ[™ŽÔKz&—[áú=|ř0ˇkZHˆŠ˘(ƒőÜŚzdddÁ`Š˘lú}$#ĺÁs/Q”l]´ěŠš–‰ÇăÇĚĚĚ?”ű3e˙-c!Âo-jˆŤRR)~iŻ×kĘiŤľ”bÂá0Ο?D"ąíÇ%´%‘HŕüůóŻE2˛9]]]Üć†|÷8##ę=KuŐĘĄ"ÝÚľkר¨u"˘†¸*…˘"›Ł—„¨LOOăÜšsřđáýř„6|řá‡8wîÜś˙_IFև˘!ŰCÔ{V8ĆŽ]ťF+ů™ŠDdjjęďŻ\š"d…ĹŇPĂÍžď:;;Ms2ŻŢ˘˘( Ţ{ď= StÄ$ ă˝÷ŢŰ2ł$#k١o׿K˝DCšš…LËŔ•+WŘÔÔÔßWň3•ţMC˘Ž{€ćf1 łRxGE|>Ÿđ“F"!ĹDŁQŞё‘‘nCčHFcłŮ¸vĘÔS4DÔ{UŃX÷Šj8*ľ7 Vňc†AÔP×vŕůÎÁív =ú݈RL8ĆĐĐuÖԐÉÉI UT R.$#yöďߡŰÍíző Ä˝WƒÁŠÚvU*Žý(ŠňO˘Ö‰H’Yśę˝Œš ETDÄ!gF—•x<^˜Ę:33ŁŮăÔ;333…é¨ZvÖťŒ¸\.ІlYś yś SĽ(Ę?Uús‹ČâââůńńqaĎÓun˙vŕý˘żżŸëő´F )&âÜšs¸ző*Տp$‘HŕęŐŤ8wî\ÍöŽz–Ţ{E}ECÄźGEŁQŒcqqń|Ľ?[ąˆ0Ć".—+&jŻÍÖ$lPĽđŽŠx˝^ěŢ˝›Űő´DD )&‰ T‰* @`ÍtÔZP2˛{÷nŽíşő ąX„M˄B!ő´ÝŠ_hŰş#[,–›˘Ö‰âögo‡T*ĂuňáÇ _¸*ş„S,$”˛)Ÿ™™]¤˜z’›ÍĆľ]—1VWŃ‘ďMÁ`°ěÓvKٖˆĚĎĎż-rzŚ^ęD€|T„ç4nˇŰĐçИIBЉD"8w‡qďŢ=˝—cXîÝť‡ááaœ;wNw)Ś^dÄçóq-PM§ë'ˆ{oRÓ2óóóooççˇ%"ŒąÍfSDMψţÚé4ߨČţýű 9qŐŹRL4ĹĽK—044„‘‘JŰ Ÿ~ÁĐĐ.]şdŘ7Hf—‘ŽŽ.ŽÝuůƒ<ë'"rŮ@(‚ÍfS*™ŚZĚś˙Ö‡ăŸ(=#Œ1$“ŰÔ´F›¸ZRL<G8F Ŕ;ńńńş:ĺ7“É`||ďźóÂá°ç`™YFxď ɤR'쪈|O ƒp8w˨lťŽb´źœŤ›vąŁôjZŔśŇ2@" ɞʄB!Ůď÷o÷2ş"ËMuUľ¸¨ŔívpťžĎçĂÝťwu-˘Źw )Ľř&íőzągĎx˝^ttt蟲í133ƒh4Šťwď6ĺ˛Ě$#ÜëĆůFpŽČŃ5-Ł(Ęśk5¤jB_;věřűĎ}îsżo¤˘°J`ŒáÁƒ˝—QSZ[›šDĹăqœ;wN—´IHůȲ Ż× Ż×‹ÎÎNn5>ƒƒƒŤ~Ż–ÉÉILOO#"Vuö‹ˆţśŮl8sć ×UEÉbaá!ˇë‰ŔO´ ;ÄŹ§§Ÿ~úéçććžžÝkT%"’$ő¸ň‹_üBČô ,,<ŹŤ $Ihowr}ŇđÁ¸~ý:ˇë•ƒč¸čěěDgg'Ün7ź^/ÜnwĹÓsˇ+"‰DńxŃhńxÓÓÓuý˙ ęsůĐĄCÜ Tgg“uVbEkkłŢËŘŃhO>ů$ŰnĄ*PEj0GzĆnˇÖ•ˆ¨}ů--2ˇkîßżŃh´fmĽ"oÜFbŁ›żŰí^őKý\Ľ§0 HăńřŞ_ÄcDMÓěŢ˝›űTźç‰€Ý.fË.Ŕ'-T€ÎÎÎ˙)Ëň)‘óˇąX KKËz/ŁŚ´ľľp+\ňGŰÍS4$!úńć›oŽůÜf‘łgĎjź"s!ŇsŰfłÁď÷C–ů˝ĄY^Îa~~‘ŰőD ŠŠżş˝ZăőzĄ(Ę÷ڧ§˙¤šëTÝ´<33óÖä䤥†UЍG.WďŹ,Ë8qâ×k–"ŇFM•"Rkď‰'¸JŔO‘ď=‘H“““˜™™yŤÚkU-"ŒąˆÓéœ{ڈUŘA2Űey9Ç}X×ëE__×kސ„ő€2Ň××Ç˝&0Î`y9ÇőšFÇbiv’*Ÿât:çśsśL)\îžVŤőŻDp8Ě5sĄRŠ ÷9*>ŸűÔU’˘ž0˛ŒtuuqoŐÍĺVęjŒ‚Šč÷œ`0ŤŐúW<ŽĹEDććć~H$ ęČw ?ŕLÔöŠí’Ÿ¸Ę?zâÄ nĂ´HBˆzĈ2błŮ4Iż&“ëŽ@U’$a˜ů"ŐD"šššđ¸aŒE˝^ᅧ‘$Ičęĺí’Í.sOŃȲŒŞŻCBÔ3F“‘îu!étŮl}5 ůN‘ßřƒAx˝ŢeŒEy\[aD4ýŰŤWŻ ÝšgˇŰ„~rl-R48zôhU?OBÔ;F‘‘ŁGrOóÔkJ&˙ŚWÜ´L<ÇŐŤWF˙–×5š‰c,(Ër†˘"âÁC"‘ć~ݞžžŠgO$!QŒŢ2˛oß>ôôôlűą7"‘H×]J0G4D–ĺ c,Čëš\[E\.×ßž—Ź9őY^Îiňżż˘âU’‚X‹^2ŇŐՅţţţm?ćF¤Rő×%ˆ €@ —Ëőw<ŻÉUDԙ"áp˜çekŠ$IhnŽż¨¤RŠ&›Ă‰'Ę:t­ž$äŔx饗ĐÝÝ­÷RęY–qŕŔ˝—Qľ–‘ŽŽMŠSóozĚ}†ĐFˆ ‡ĂÜf‡ĂUDcÇ9= nEĐ&\*Ë2úűű7Œz’Y–ńěłĎ˘ťť/˝ôŽ9ÇŁ÷˛LKoo/^{í5}ZřwěFeďŢ˝…ę‘#Gt^Mőh-#ZD[XxČ˝0^ěvągĆăq #‹q?ťűżŠŠVú­€LfI“ƒ;;;qřđá5ŸŤ' €X,†wß}/^Äýű÷äŁ$ÄŤŻžŠ˝{÷ęźBsqăƍŸ=)„OK9|ř°&˘(Yd2KÜŻ+fŠ á]¤Ş˘‰ž™Ąhľ^;hT’ImęEzzz m˝ő(!ĹLLLŕ… ¸ví%Ÿ3÷x™ž2ň•Ż|E Ńr/3DCBĄZ¤úgZ\_aŒEwíÚ5JQąY^Îiv"ć3Ďîíí5Mĉ—Œ<óĚ3Wő˜……‡uŮŞŤb†hH ŔŽ]ťFyMR-EłĘ™ŠŠŠ˙622‚h4ŞŐCԄzŽŠůzŁM?4›„‹ĹpńâĹUŠY–WÉ ą=FGG‹Ĺ ›%*đ‘-HĽ2u[˜#F122‚ŠŠŠ˙ŚŐch&"Œą°ÓéœÔę!j‚$Ip:ë;4žJ)†ŮLĚ,!*_ř ߸qcŐ ”Ř>׎]+üšťťŰT…ÁF“‘ü›˜ú­ §Sţěŕŕ œNçc,ŹŐchÚK”L&żuĺĘ&r+/ȲŘmW<0Bxľ$Č;SëBEY!ŞcbbbUtÉLQŔ82˘eZW,–áç†Äăq\šr…%“Éoiů8Zß]CŮl6+z­8Íz/AWô.8Ť éîî^UR\dITÇăY]2K;o1zˈŢ{…Q0Ă=# ›ÍfhÚy˘Šˆ0Ćâ6›- úL°ZŃÔÔ¨÷2tey9§ËTÄz‘ŤnŠĽm§Äö)žÓRZřűěłĎšn˛­ž2’H¤užęMSS#ŹVńď@6›-ŔÓ4­Ąyž!‘Hœ›œœ„d¤ŢkE ›]ŽiČľž$¤ˇˇwŐňâv^bűěÜšsÍäÚű÷ďŻjç5[TĐGF"›]ŽŮă3Ü+‚Á ‰‰Ä9­KsaŒEŰÚÚ~(zŃ*46Z„Ďůń@Q˛H§ľßÜęIBJg[Üž}›:e8ĐÝݍ—_~šń˜˜˜Ŕŋqá…U˘W*fĄ–2’Ng4™Č,˛lEcŁEďeTÍŕŕ ÚÚÚ~¨UËn15ŠŔœŸŸ˙–œ樂ćÁ⢢ůŚóӟţ´.$Čß‹ T)R=;wîÄ /źPřw˝qă.^źXźąąąÂˆ}ڌŠyůéOŞéc(J‹‹TËd–.Ku€ŮüüźŚEŞ*5ł 8ňO´z=Ż­;ižyć™Â8x3ăńxVECŢ˙}j×ĺŔWżúŐU2::şć{J§ŮšqpÜŃŁG5VP‡L1‡9ćNi=ŔŹ”šő¤ŞÎÂáp­R3ěv›)Bo<ˆĹRšĘˆz6M5SXNą„Äb1*PĺŔŢ˝{ “S'&&֕őkˇoß.|\Ü:-:6›MłłcT–—sˆĹRš]_$-Â/€p8ŹůłRj&"Œąp{{ű¸jE ĽĹ›Uľ0Ć‹Ľ4=Úť§§§ęsiŒJéP­ŃŃQj×ĺ@q˝GąhŹGééźfˆŠ¨çÇh)!šÜ bąTݡ骘ĺž088ˆöööq-˜•RÓ)]łłł~łDEŹÖF*\}„ĹҀ†mÑęI˝š>N­)ކLLLP4„Ĺí¸[ĽšbąŘ*9xđ ĐퟍNÔՊ†Šî=ŞČ˛Őíşj4dvvÖ_ËÇ­éłČlQ*\͇#=GMţTéęęŇüąjÁVźśQú€¨Žr¤bllĚçĐtuuŐDB€|˝œÇă¨ű4ľY T}˘!@E0WTÄLOŔíPK Q‘eءo_ÍS dYĆłĎ>[řxllŒÚu9RÜ SN[né(ý˝{÷ ×Îťoß> Դƅdč۷o˘%_řÂVɉ˘(x˙ý÷…ů˙čęę‰' ÓnŹĘˆÖ-ýFÁn7ĎŠějmȃÂz<žn9…Gľ"? ‡Ăđů|z-ƒ‡ŒLfŮÔ/@ŁIˆŠZ7˘Z˝‘)MPJF;îßżk׎­ż˝{÷Žj—Vę¤ăžž>Cî›ő"#8ĆŔj)Ú7k^˘˘›ˆ˜-*äS4óó‹z/CŒ*!Ĺř|>x˝^\ştIˇ#С˘´]wŤDu¨Ý0XS'˘( >ţřcŒŽŽ 3ÉÖfłáĉđz˝z/eCęAF̒’ô†:Š`ž¨HޒmŚKŃđĆVV˜ćĄLŻ× żßP(„{÷îiúX•"Ë2ĆĆĆ …Ş Š ˜˜˜€Çă)´ň>|řPˆbvďލţţţš¤brš44HŰ~͛YFóLÖ6B4ĐYDĚ1[І—„¨ÓWkQĐ&Ë2Nœ8>řápŘ0ŃEQ0::ŠŰˇocďŢ˝ÂÝE'‹ ů(ĆfłÁçóa˙ţý5y}ßEč."Œąh[[ŰÍ2Wxœ˘ŢRüšůůE(J–Ç2ˇÄívă̙3čëëŤÉă/úúúpć̸Ýîš<ž˘d1?ż¸JŔĚ̌.ë ŒIGGöď߯›|¨,/çH¤‘Ë­Ôüqcą”°ĺš\vÍOŻ5ápĂĂĂđ'Fj×-Ű"ŒąĐO<1î÷ű÷E"˝—ĂŤľ‡­f""KˆJ.ˇ‚ůůE8˛Žgů¨BF‡199ŠŰZýéęę‚ĎçÓ-ýRL*•áZZ)˘ĘˆĂaƒŐjčŰáśP TŔřř¸nk!jĎž}ű°˙~] PKÉĺVH¤ ńúMFdŮ ‡ĂXEÁÝťwFFŠŘľB\.W!śgĎĂ´Űnc ɤ˘ŰKZŁşŽ0Q IDAT‡Œ¨{¤YÄĚĚLFQ”czŻe;)"R4'̜˘)ˇ­—$¤|%‹Lf vť vťŐĐBäہՎ ˙ŽG•’ééiŠ˜”ĐŃсÎÎ΂|ˆ-eŒ!Î"6~7LľÔRFĚÜŚ ŹJɜ-%Ł"¤ˆS4/żüň—ň“Ÿ4ˆśé”ƒÚÖťŃaR$!•“/úËżŰt8Ä*ZsťÝŤÄDQLOOÄdzzşn˘&.—Ť=ňz˝čěě&⹊’E*•Šůů0zR QżnĆ6] ˙ćäĺ—_^ioo˙gS2*АOŃŘlśű~ż_ƒz/GTŮ(•’ęČĺV°°đŠTF8!Q‘eyU'đXNڧ§Ç ľÖfł„Ăívţ,˛tSRŒ–2˘÷ižľŔď÷ă׿ţu6“É™’QZDÔ.šááá+ýýýŚtŚ˘Ž!VgŒ„đĂ BRĚzr˘F×ý]OQQE@aÍĽż›‘zb´’§Ó¸ę<…BęÉşÂuɔ"´ˆů™Š¨7Čt:K˘f’ő(çŻĘ đ8ş˛]úúú .b˜Y26ƒd}xˈÝn5ĺkXĽdp™°)É EQ’$šÇ˙~ć™gž‡Ăz/gSfggŃŢŢžíŸgŒ‘„Ô‹ĽÁ´B“ÁÁÁUżëCRź˘˝f-LUńů|řńü •JýŚčŃ0Ec,žJĽţÓČČŒÜÎ{ýúuźńĆ˜œœÜö5HBjƒ!™›K qŚa<Ôi¨ssI,,<$ )52RÍëÍě022‚T*őŸĚ !€IDc—ËőÝ×_‘HDďĺŹ"NăíˇßĆĺ˗äŸHłłł5]IČöČĺVJ)˜MbqQĄ› ą%šÜ óϙTŠž3•ÂCFĚJ$Á믿—Ëő]ƘąntU`€x<ţm—Ë5600€xÜ8˘hˇŰW}œN§qá¤Óéš<>IHőäg<äßÝ&id2Kz/‰0™Ě‰4ćć’u1 DKHFÖ˘NOuš\cńxüŰz݇'ŚH$_żwď^Ćď÷뽔U?~]]]…'''ńöŰokţ¸$!üĄĄB‚Ş$#Ťńűý¸wď^&‘H|]ďľđĆt"‹*ŠrbxxFš-bˇŰá÷űWEGĆĆĆ é- Ń5˙ŕÁ݄ę UF™ÇůT’qX^έşIŞbŇŘŘ€ŚŚFS՗hI.ˇ‚ĽĽe,/ݐxŒ–˘rřđáU"ňÄOŕřńă¸|ůň!šsçîÜšƒŢŢ^?~źŞsĂ6˘żżńx<–L&rż¸AŠ›]1O&“?üđC͇é‘O܈ĆF ÚÚZ„ŻE¨W–—sH§3…soԙ%ŠTKK˔JŔcéHĽ2…Ůęů.ét†$DPjľwľˇˇŻÚłďÜšƒt:ď|ç;8~üřş5{cccxă7đöŰosmůőűýřđĂ3Édň YΑ)‡ş ˘(g†††¸;+ľbŁő˘[, šżŤ jc ™ĚR)ąXŞ 'ńx ‹‹ %‹ĽĽeSFŔcXZZ†˘dą¸¨ O¤#K!•RÉ,™ňď^o¨ŃÜZE>źJ8._žŒt:C‡á7Ţ@ooďş?wëÖ-|ç;ßáŇň 144EQΘé™r¨+ĆX°ĽĽĺĎâŐŇ'é{ď˝g˜>tI’ĐÖÖBÇڛľţAœÄbů´*(jE•#G–—sŮP#ŞpÎĺVËą5__Ya†–#˘vŘí6´´Č\ŻyëÖ-ÜşukËý8Nă7ŢX%-ú§şŚáŕúőëkŢl>÷Üsxńšľžx<ŽŽŽ.&IŇGńxü™m]Dpę."˘’H$Z,–IŸĎÇeňęáĂ‡× , k Y+A=ďő×_çz@^K‹ŒÖÖfn×#Äey9ßE’Í.#•R żÔČJńŻJ)ýů……‡ŤC}\’ČĎŃBBÔÉՁ@`ÓȈ:ÄŹ˜+WŽŹůžŇtMľâóů`ąX&‰Dݧ–Rˇ$ŠÇfłýŻ'NȟwîÜy˜Íf‘„Ź…""ëŔ‹dłŮßš{÷ŽRmĘ‹/žˆŽŽŽu# *étz™×*%ł˛lĽsj‚¨őź­#ŤŞ|¨§žŤ’ąŢtëŇČHqŁ€zŘ]1ęř…jŔÝťw•l6ű;őŢŚťŮĆXD’¤/ ˙€ŞÉS_,›^Ż€ľ–)™P7”T*CŁá ‚Ř-FľoţxůýľUF6ŠŒGŠKÓ9<Ŕđđ0ÔőŔ˛­ ˆČ&ďSCCCUGE6Bmë-Fď”ĚFŘlMhowŽ{AőKSS#Ú۝°Ůšô^Ę6’•'žx‚ëăƒA y r˝¸ !)ĆX°łłóżœ:uJšpášÜäącǸ?/Ôł!ZZřOE$B,ÔhŠÇcěDÜšsça&“ůc,˘÷šę‘Á‹dłŮßůä“O˘_üâW"z~—‹*$mm-Z&ˆM(>˜Ž¤2"‘žřĹ/Ž|ňÉ'Ńl6ű;$!ľƒD¤†0Ć"ąXěéůůůďëëc$#•aą4 Ľ…"$QJi„:Đ*#‰ ŻŻÍĎĎ˙{,{š$¤śĐłľĆ0ĆâŠTęˇ/?ýôÓTŔş (eCy(S=Á`O?ý4/§RŠßfŒQîźĆˆčc,>77÷uuđŮŕŕ ŢK’b!1ăĺąKZ[›I@Şdpp°0¨lnnîë$!ú@;ˇŽ<ŽsęěŮł "Öm"Idي;ňBB“Z łŇÔԈÖÖfěŘá„,[I@śI<ÇŔŔΞ= §hP™žĐŽ­3fD/]şô˙E"9Ăívë˝,a‘e+dيĺĺŇé,eC˜YśÂnˇŇT¨1wďŢUüc,Ź÷šęŠˆĆX8“É|é“O>‰vuuQ+-hmmĆO´Âá°Qچ‹Ľ‡ O<ъÖÖf’D"tuuąO>ů$ú¨=7Ź÷šĂ vÔH’ôїżüĺ*bĺƒZG˛c‡.—6[“ŢK"ˆMąŮšŕrŮącŐđ$ âË_ţňŠ$IQgŒą 1Œąx<†1vĚăĎę ž˘$„qPŁ$ĚÚ \ÇťđhĽ‚<A;ąQ‹Xϟ?ŸĄIŹüÉoúŁ$tĄ˛l]ý 9ć‹Zrţüů ¨(հгޠ0ƂŠ˘ě˙裏ćhŤvŘlM…Zę¸!jÚů˘>ç(úĄ ę¤Ô>úhNQ”ýtfŒq!10ŒąH2™ü………ŁşmQ[€=věp˘ĽEŚâ@‚´´äŁpƒZo5F­YXXřˇd2ůTblHD ŽZ7ŇĐĐđßO:EóFj€ĹŇť=^I ą]ŠĺC='‰R/Ú˘Î9uęţ;Ճˆ˝*!ŔąwŢy'ăóů@ŠšÚ°ž”Pú†ŘˆŚŚF’ˆD"đů|xçw2Ž=Ú3  Wˆ@0ĆBŠ˘ěůĹ/~ńóžž>FŠšÚ˘J‰Çă(ä÷)Ä^ߨ)=ľćĂăq|č@0D__űĹ/~ńsEQö0ĆBz݉(zľc,šH$~ceeĺ<ĽjôŁôävço@”Â1?Ří6¸Ý$¤zSœŠYYY9ŸH$~ƒ1Ő{]DePŒYP’Éä7%IşţÎ;ď\ŠD"ś`0ˆžž˝—UˇX­°Zó/'ƲŮed2ËXZZF.ˇ˘óęˆj°XĐÔÔ›-˙LÂa "‘pďŢ˝ €Éd’˘ ‚BQS5Ńhtě駟F Đ{IňŃľ-xÇgá0>YśRČ^,–†B´Ťř˙Ďfk" 1@O?ý4˘ŃčĽbć""‚ó( ůŒŰíţ‹×_ý˙ …B…Btpž°X`ąX!ËůŐˆÉňrŮlKKËú.°Îijj„ŐjAcŁ…"'Łżż###pš\߼‚Ts@oĎLÂŁäÓ?ţńtuuąPˆŢ 5bâpČđxřĚg\hkkAkk3ěvuĺhHSS#ěvZ[›ŃÖւĎ|ĆÇ‡Cڈ‡Á …Bčęęb?ţńxš$Ä<ˆ˜ĆX$•JýfsssđŘącTČ*Ȳ--ŤĺÄĺ˛ĂáČË ĽuĘG­ëp8lpšěŤ¤ŁĽE†,[аXԂÔcǎĄšš9˜JĽ~“”™ zëe2 ďůI’ţáŇĽK?¸yóŚőűß˙~ƒĎçÓ{iD…46ćÓůčÉăĎ//çË­<úţĚÓoą: I-R_˙L‚aÂá0^~ůĺ•_˙ú×YߘžžŚPŻ !1)Œą$I;>|xĺůçŸ÷˝öÚk¤ÚP,(Ľ,/簲 b˘Š ájQÔ•*Şx44H$&'cppCCChoo˙çL&sŒ&¤šóč…űź$Iýçϟż …lÁ`1/ę Zm%^Ć––r…K%›Í•ţȜبÖĹjľlř}MMŞÓ¨sÂá0033“pâÁƒ19R˝…sëI’ÜíííWfgg):B„á(‰‚„ggg) R'ˆÔ’$őŰlśŘl6Űđđ°Ôß߯÷’‚¨sBĄNž<É2yžAsAę *ĂŻ3cĄL&łSíŹéďďG4Ő{YAÔ!Ńhýýý…Ž˜L&ł“$¤ţ ˆH#I’Ďápüż‹ĽýěŮł’ßď×{IAÔ @ožů&ËĺrłŠTęca˝×DčEDęĆXxqqń3’$ýĺ믿Žžž„Ăa˝—E„‰ ‡ĂčééÁ믿I’ţrqqń3$!ő ‰ĄNe}ňWżúŐřóĎ?OƒĐ‚ŕŽ:˜ěůçŸÇŻ~őŤqOŇtT !Á‹>xđ ŔąP(ëęębtˆA<xtôD Ŕąô<:'‹ HDˆŐ0ĆB‰D˘›Ň5ATKi&‘HtS1*Q ‰ąĆXź4]CÝ5A”‹Ú Sš†Ąš ÄzˆR”Žyţć͛sO>ů$Š~„ ˆuQ‡’=ů䓸yóć€ç) ClľďeÓŢŢţ§RŠ7­VŤ•Ú} ‚(FmÇÍfłY‡ĂqvvvöĎő^!!ĘfvvöĎEéTëGź^/‚Á ŢË"BG‚Á ź^oĄDQ”N’˘HDˆŠ(ŽI&“?´D‡áóůpęÔ)$“É‚ę@ˆmB"Bl ĆXtnnîëžüřăÇŸţy‚¨TyţůçńńǏxrnnîëTBl˘*Š ZIHÂźŹ# TˆJpD„ŕc,LBBćc ë˝6ˆ\)’Ÿ˙üç÷HHBLŠäç?˙ů=€A"Bhc,ü駟îEHB(֋€|úé§{H@­ !4eŁ”M(DSž ÂH„B!JÁş@"BԄb!ůŮĎ~6zěŘ1šCB@rěŘ1üěg? Qch˛*Ą ’$yŰÚÚţÇüüüďť\.řý~řý~¸Ýn˝—FŚ'#  ‘H ­­í‡óóóߢBHD]‘$ÉëršÎd2żŐjľ;vL„×ëŐ{ia:˘Ń(qĺĘ–Ífł6›-H$Α€zB"BI’ÜúNç˙H&“;úúúŕ÷űŃß߯÷ŇBxBĄFFFŕt:ç’É䷄h *a¨F„0Œą8c,¸°°ĐŽ’:’@ @'ţD…¨'á–Ö,,,´3Ƃ$!„Q ˆaX$Iňvttü?‰DâEą>›ÍڏÝîPę…0$"„i‘$Šż­­íĹT*ő’B֓‡ĂńOóóóo3Ćč€&”ˆuI aTH>ˆz‡D„¨;T)Éĺrż›H$<űöíƒĎçŁBW˘f¨§ápăăăpš\1‹Ĺr“䃨GHDˆşF’¤ž–––WdYţĘěěěçZ[[ŮącÇ$ŸĎ‡ţţ~šSBp!# !ăʕ+laaAjoo˙TQ”Z\\Mt%*"## a||°k×ŽŃŠŠŠżGţœ—¨Ž $ƒ@"B IRGGÇ×ršÜÁŮŮŮĎ9Î•ßýÝßmPĽ„Ň8D1‘H¤ 7oŢ\I&“ EQë”r!ˆő!!ˆ2x-ńľľľQkKZ[[ŮóĎ?/ŠRB“ú"äăG?ú[XXŠj=ŽSԃ ś†D„ ś*&ťví:žL&'™Lž>‹ ՘˜ƒx<žJ—ËýV&“ůʚâ§Óšň[żő[ Ĺb˘ŠŠ™dE• U4Š?÷ŃG­$“Éő{NçœÍfűĽĹbůhffćaq‚0&M˝ZXIDAT$"! Šc żTQ€ŮŮŮ}ĽßŻ ‹úqiŃězE´Źűń|°Ç밂ööö °X,ĚĚĚÜŔ]a!Y!!!Âópđ)˘‘JĽîÇămĘkÁ(”Ż× ˇŰÍU$´F™h4Š`0Xôš•Âât:oŰlśßˆJ@T–ĺ 6Ł'b#HDBc–…ĂŔŰŐŐ՟N§ˇ…Ăáű”ŻďÜšnˇ>Ÿ//F’ ^(b˘üF111‘M{{űoŹV덛7ožD.ŠB‚BB"B5‚1ćŕEN8_XXřjanF___^2|><<Ďz—#Ę  ! ĺĺ$ bll,˙ő–––Pccă˙wóćÍ÷” ,ËŤ÷’‚ŕ‰A¨ċeéˆÇăßPśTşťťáőzáőzI84ŚPP‚Á ‚Á ŚŚŚäśxœNç/9‘e9 é`  ˆ–“G˝‡ă ‡Ăą{zzú!ŕŽtř|žüŸ„ţQÄDůS‘“­[ˇ~<77wqnnîrrŇt aHD˘–ĹĂ×ŐŐőta´ŁŻŻŻH:ÜnˇŚă$ř äœ(r˘lëDMŢ 1!ˆň!!ˆPÄŁľľuO6›}<‹ľ477ˏ=öŁh‡9)“ .ČwîÜa.—+bąXޛ=‚( ‚XĆŘ@GGǟfłŮţp8|ŸÓé\züńÇë|>_>ęA Š”ź÷Ţ{KńxźŽ˝˝ý7‹ĺüĚĚĚ;˛,j=F‚Đ#$"ąĚrÔc ŤŤëĎnŢźš ȕÎ @‘‚(EJFGGó%Ä]]]—oŢźůSŁ-!ˆ$"„ŠaŒyÇ ’$ýI8žŻššYŢťw/óů| ‚ ŃhŁŁŁ8uę”|çÎÖŢŢţ›d2ů󚚚׊— afHDÓÁhmm}NÉőŘšs'|>ü~?mˇ5! bdd@(Č-yƒśpłA"B˜E>ćçç˙$•JI;wî„ßďÇŔŔőđ 4% att###˜˜˜€ÍfKÚíöŸ“”fD„0,$„h”f„D„0Œ1oGGÇKŃhôY’Bd֒ˇŰýćĚĚĚk”SB BxcžśśśgÓéôżÇămÝÝÝ$ů ƒ"%ĂĂØšš‚ÓéźmľZ˙ŸŰˇożIŐ7„čˆÂÂó{<žż…B˙ĘĺrÁď÷SÂ)ax”Dב‘Äb1x<ž…B?–eyDëąD%ˆBĄl˝Äbą?O&“ś§žz*żőBfCŮş9}ú4$IJš\Ž˙N[7„hˆBŔóˇ´´ŽD"eëĹď÷SŸ‚@ŽOÉČČH~댼Ľ%‰D^Ś( !$"„naŒy:::ţJ‰~8p~żŸ:œÄŒŒŒŕřńă…Q’˙›rI˝B"BčĆ˜ŻŤŤëŻoŢźš‹˘Q+Ł$Ëíĺ˙/Y–Z !!tcĚ `Ŕétţ(ˇőőőĺ+_‚¨Ľâfll N§óv<˙ȝwŐzlA"Bh cĚărš^LĽRƒVŤŐşwď^644DeˇĄĄPCCC8uꔜN§Ó6›m8‹ĄmBKHDM`ŒyZ[[4;;ű­îînřý~ Ňö AԀh4ŠááaŒŒŒ`jj ­­­?›ý$$„ˆ5eeţÇĐĐü~żÖĂ"Ó222‚ĄĄ!Ę#!4ŁNëć€1ć۲eKŔ…x`׊S§ …HBBcü~?BĄN:…x`€ [śl 2Ć|Z0$"„Ş ČC=´ó… ”„J:c``@.\ŔC=´$$D !T1ćťďžűŽa…€P‚Đ7>Ÿo•Üwß}×HHľ !¸Rš˙ţűżLBbR($÷ß˙—AB%HD.ʎCBbłV„„„„ŕ ‰QŒ1 AŸ „ÄŁńĐÁ!!*‚1ćikkű)€ĎH@Â<Ź!$Ÿľľľý”„„¨˘,cnˇŰýCŸ9Îo;vŒ„ Lˆ"$ǎƒÓéü€ĎÜn÷—k ˆ’!!JŚ˝˝ý{’$M˲üŸ^yĺęBDžÉ+ŻźY–˙“$IÓíííßÓz\„8PgUbSc>§ÓůÓx<ŢvřđajĹNĚ(­ă_~ůeĺp˝?Ł.­ÄfˆëÂ󴡡†ĂáO=ő†‡‡é0:‚ 6% app§OŸF{{űD8 slˆő ­b…y _řÂv^¸pŁŁŁ$!A”„ÇăÁčč(.\¸€/|á ;Aů#ĐˆE0Ć\.פ’ )• ˆŠđů|ƒůü—Ë5ÉŁóˆ"HDEý@N ´LMMąÁÁA­‡E„ÄÔÔhpŠú…ˆXš 322BɨApĹívcdddŐvÖă"´‡DÄÄ0Ć|‡ă÷´ CD­Xš]ăp8~OíâÍ ‰ˆ aŒšˇnÝú÷.ô÷÷o™˜˜ m‚ jĘŕŕ &&&X˙śnÝú÷”ĚjN¨|×d0Ćl6ۛ6›Ívüřq60@ycAhËčč(8 §r<+Ëň¨Öc"jEDLcĚ˝e˖ N˝řâ‹Ňrâ˜ÖĂ"‚ŔŔŔŚŚŚŘ‹/ž(!—Ěz˘#ć""&€16 IŇɎŽŰČČ偘Y–ą¸˜Íꏘ)úz:]ů-€–űŞĎ ýYH$2żćuŹVKŃÇ őˇ€1ść÷ć Ŕď÷cff&•L&÷QtÄřÔoţBTcîöööS|/źđ†††¨Ćŕd2Y,-ÉČd˛eŮŹŒlv ŔjáP›őŢoőçSkžN‹Ľ c őőÔŐĺţ$Œ‰’Ě:44d{őŐWOmٲ%‡÷ʲŐzl„:ˆ%¤ąąŃzኂˆL&‹lviůO9˙wŁE7aY\\ű늘(˘˘ü$E|Ün7†‡‡100€çŸţ›6›ícŒrG ĺˆŒ‚Š˜Sűöí“>účŁ:’1Éd˛HĽ1?ŸD$2Űˇăřýďc˜C,–Ŕü| Éd‹‹ĂIH)䜘2H&ӘŸO!K`vvż˙} ˇoljĚc~>‰Tj™ĚÚŰM„žńů|ř裏ęöíŰ'8E•5Ƅ""‚1ćľŰí˙kaaĄýÔŠS dTqČd˛Ë˙-aq1[ómŁ‘Í.!›]ZőďŘĐP† ęës‘ŠžčĽÚŔŔ8ŕˇŰí{c,ËrPëą| ˆˆAXîPřá׿ţő-TŁodY.Št(QŽ;wH¤HBTdq1ƒD"…;wňѓÂȉ#K˘ TÖ|ýë_ßŕCęĘjHD‡1ćqťÝďÇbąďžňĘ+”Ş3˛Ů%$“iÜšł€Űˇăřüó;ů­’íY\Ěäˇv>˙ünߎăΝ$“é|˘/ĄÜn7^yĺÄbąďşÝî÷éĚń!˜ĺ˛Ük§÷Ă?uGŐ˛,‰-lbąR•ŸELôĂŕŕ >üđCx<ž^I’ŽŃ‰žbC""(N§óÇN}űŰßśx˝^­‡djŇé ć撘ĂçŸß!ń0Š˜(“ŮŮ9ĚÍ%‘NS4KKź^/žýíoۜZž ! ƘÇĺr}ZWW÷ÂącÇč¤\(Œz|ţůDŁóH$RTa2™,‰˘Ńů"é¤hIíQY;†şşş\.×§´U#$"ĄlĹ|ń‹_źllŒůý~­‡d*˛Ů%$)D"´9V i$’RŠ„Őżßąą1öĹ/~ń~ÚŞAXΧ­˜ŁČÇěěnߎcn.I ŚÄş,.ćśčnߎcvvޤ¤†ŹÜŞĄŞq Ń9Œ1ˇŰí~iié?ŇVLmXK>h˅(—L&KRRc ˇj–––ţărU M˜:‡šéƘ×étžonnnyçwę( ˘Jo……EŠxÜÉIINLęŃŘ؛­řS żßŻ×[÷§ú§_[ZZú”1ÖO Đô EDt cĚ/Iҕ‡~¸íŁ>" Q‰Tj1żżçÎIĄ:‹‹™˘{.•Zç0˘*ź^/>ú裺‡~¸M’¤+Œ1żÖc"ֆDD‡,—Ą{á…lÔ Œ?Ůěćçs!óX,d2­ő“˘”ßžÇü|’śn8Ł4@{á…lŽQ‰Ż>!ŃJ>cěŕącÇ0<<Źő E*ľX0éÓ~=ĄrrœĘË1EIř2<<Œcǎ1vňFôĺˆčƘˇĽĽĺT6›íţĹ/~Áh+†˛,#‘H!™\$ń „ •ZD*ľ‹Ľ’Ô€Ś&ĺ’p@Ééëë{¸ĽĽĺCĆŘ^ĘŃŃŒ1ŸÍfűŐśmۉňAŞ'™L#™Çěěĺ~†!™Lcvv‘Č<Ý×U˘äěۡOp’Xľ‡DDC”¤ÔÇS*Pş[*‡“Qĺ aT”Š›ŰˇăÔŐˇ ”~#‡(‰Us(GDcîÖÖÖż›››űÖącÇ@­Ú+CÉ˙H$hB&ĚE6ť„;wŔXMMVĘ#АĄĄ!x<|ç;ßyą­­mËěěě˙)ËrTëq™ ‘ĂsŰíöĘd2|řá‡ÔŞ˝H@"‡,˘ŸĎý.TĆr+úúúžśŰí_eŒýk’‘ÚB[35dš2ćĂÖÖÖ/QRj™dłK˜›K"Εߒ„DEHÂáܑ”œ]^ŻcccŹľľőKË549בÁóZ­Ö_nŰśÍCRË#7Éć%$ ąJ´PiFż+ĽŁtbÝśm›Çjľţ’d¤vˆÔƘ×fłýjűöíTS:Š€(‚ JG‰”ŽRQł}űöĆĺň^’‘@"˘2ËĽaîۡO ƒ$!%°R@h%ˆĘ(ܲ!!) ˇŰ`0¨”÷~Hĺ˝ęCÉŞ*˛|;pŕFFF4$“iÄă4aO “ZN ’dŐzHşG™ł?~Œ1Y–G4ĄˆˆJtvvţ[Ç:DRét&ß„$„ ÔA–ĺ|’tšúílĆČČ:ǖçtBHDTŔétţxzzúoéŕşÍÉd˛ˆDćÎSŚ?AԈlv Ńč<"‘yjż ʁyÓÓÓKĎԁśf8ÓÔÔôßžĽFe#Ë2âń$ľŤ& Y\Ě`vv’d…Ó)Q’uPćňď|ç;/655š‰ÄŸk;"cA"Â’ŇH$R”„J:"™L#•Z„ÝnCS“Mëáč’yśŠŠ $#ü áIČć¤ÓĚÍ%)L:D–eĚÍ%‘L.Âá`ľŇň°’u ;$!CŰ0!™LŃčpˆ ‡ĂEó+ôöö˘ťť›ËőIFރD¤ œNçk!!păĆ n×W“Â(AćA¤čČĘŐŚŚ&<ýôÓ\ߣPF¨kyˆ”Hgg翝››{ą€KČPm‰ľˆ&ŁѐHč÷Aä“O>YľŐýä“O˘˝˝ű{)2277÷"MS:ÔG¤–OŃýŰZHČr]:€œŒđ ňD–eDŁ JF%"ß-•ĘŔínŇ]ߑsçÎ}ÜŢގ'žxBľ÷+č3ňˇŒąÚť9$"›°,!Ç:¤ş„ěŢ˝[ślɇoܸťÝŽńńq|ňÉ'€mŰśá›ßüŚ*6_ ŠÔ"•乊ĹĹ Âá8š›ał5h=(š;ž{îš5_;55…S§NĄ˝˝}ÝהŠßďG0ÄŤŻžzŒ1’‘!ŮƘŔąp=E÷ÜšskJČsĎ=WôKóöŰoeyš0ăŋqđŕAlßžŰ˜6Cɘ×s– m‘eąXMM6Ří6ÍŁ#+sCz{{W͛‰DgϞ]9ŠVF†‡‡FqüřńcŒą ,ËÁŞ.h`(Gdc^›ÍöŤ`dd„ëľwďŢ]´ĺ˘H€˘_’•RřůŁGÖŹ×ˆűŔAč=ä]źxąhŽ\+Aőâŋřţ÷żżJBxőÁ`łŮ~ľü`KʉČ0ĆźVŤő—>ř Ä[B€Ü/Äŕŕ şťť‹$DaĺśKww÷şŻ6ÉdZó … ńP`´hżÖüX˜ :55…ááaźńĆk>đmßž˝˝˝\Ć222‚|P˛Z­ż$YښYcĚÝŇŇrjŰśm@€Űußx㍢° "#MMMŤ^ŰÝݍp8ŒÝťwýňŹ´ôńńńŞĂ‡qç΂ĄÎ™ ˘ś(-âÓé,š›köž+Ł!J‚ęzŰ0…Ż{úé§šIˆB €ĎçkźqăĆ)ĆŘ×dYŽr}Á!)€1ćśŰí˙äp8ś¸Ýn.×USĂápQdc- €'žxO<ńÄŞŠ™Ýťwu\oëŚZ˛Ů%Äb Š‚Á…d2L& —Ť ‹şřľ˘!O?ý4.^ź¸fސ'Ÿ|ťwďVeLnˇ@ř‡¸-N˙cě_“ŒÜ…D¤€ÖÖÖżËd2źóÎ;Œˇ„wëŮ7łíJvçççó_OdŞ!Î KPU A\Qšş\M°ZŐ[z”c2ÚŰŰqńâĹUŐ3@n}ňÉ'K*ç ‡ĂUU+şÝnźóÎ;u}}}´śśţ€?ŤřbƒDd§Óůăšššo}řá‡đzůmă­L(}űíˇ+ű}ňÉ'E×ă>œŸOR‡T‚ T#׃hvť vťÄýúápxUĽL8^5+˛{÷î’čΝ;‡łgϢˇˇˇŞípŻ×‹ąą1öľŻ}í[N§óÇńxü/*ž˜ AžWȋǎă*!°wďŢ"WÎ<(7‡qôčѢĎń #*ű¸ŠÔ"—ëAlÄü| ŮŹ §SâZâťYGęrä“O>ÁŠS§ň×U˘ŰŐĘČącÇđď|çEĆŘ?QŞšcĚŕŘáÇš6,Sčîî^ö+ˇÚejj ?řÁŠÂkĺTB6ť„Hdž$„ ˆš˘TäełKÜŽšQÄb÷îÝřţ÷ż'žxbS I$xűíˇ1<<\$7ۡoǓO>Yő8ý~?> Ç–× ScęˆČrݐ˙šoß> Šö>O>ůdŃže8ĆšsçJڗ\+ÁŞťť›ËMJyĺƒĄJŢHK‹őő.×T"ĹJ…áĘęĂÍ_UÖť2—D)¨&2244„P(„“'OţOĆŘ™šá™iE¤ LWâŐ5u˝ła”F:…ĽˇgϞÝ0<˜H$đĆoŹ:ŹŠťťƒƒƒU5™LăΝ…ŞŻCQ ˛,cvvÍ͍$+—k*2˛mŰś’#ÇápoźńĆŞ¤V%ʢĚŐ+Ű(T##ĂĂĂƒ’ŮËzM+".—ë|6›íćUŚŤTÇôööâ駟^eßťwď.:÷ ‘HŕâŋëFEšššđÄO`jj*Ÿhľ{÷n<ýôÓUWËĚÍ%ŠK*¨Żˇ Ž.ˇGßĐpw:°XXŐe–--ö˘łŮ%dłwŁoĘĄ‰KK2•Џsg™Ě>IŹĺäĐ)ɨ+ŤmVöY)!/^Ė-[*>DO)ëíîîîvš\ç|˝˘ Ž)EÄétţ˜1öľ_üâ\Ęt KtŮXŤ$leâęfQ‘îîn|˙űßÇŰożÍ­Ó5)3Œ144XŔC}˝%/ĘÇjS(6šWžÂśę{2™,dYÎK‹ňńâb–śM@"‘ÂҒ\łćgSSSř‡ř‡UIŽO<ńž|ňɢšy­śďJwějpťÝcßüć7żfÖJӉď ™ˇß~{ŐvJ˘Ó/~ń <÷Üsů&fJâŞŇŐoł¨‹Œđčž*Ë2ľj7(ŠpÔ×[P__‹ĽőőÍŤEVKKîÎd˛Čf—É,!“É’ ĽůYK‹]ő{¸°A¤ÂŕŕŕŞ#5֓őşc—‹×ëĹkŻ˝VgÖJSUÍ0Ćź’$9tčˇ ™'Ÿ|rÝí’p8Œááá˘ęVZöŮłgU?źNЌ! ĆlśŘíZZěhksb˖f¸Ýv8$Ɋ††z!%d3rÂUI˛ÂáŕvŰąeK3ÚڜhiąĂn—`ł5ň˙Ýl(‰ô<+jÖâšçž[ŃX™—§ś„(řý~:t’$1ۙ4Ě,OŒ1ˇÓéüôá‡năy†ŒB8ĆŮłgWEG łŽWŢŘk|Ç ŞŒ›úz ŹÖzÔ×ץĄĄ^őöŘĺ˘T›ŠYuV Ůě3Čd–NgHÂ…1Ćľ˘f= ˇ×ťsr­$¤ŸĎ‡>řŕv<˙’Y’WM#"nˇűýćććŻ}ôŃGuźÚˇŻ…ŇŮoĽU+( P+[˙ŕ?¨Ş}đZ„ˆ‡" š?őţtŻWY‰,ËH§3X\̒˜†V2˛}űöU4jKDŁQüáţáҝ;w>ŒFŁŚH^Ő×ă•J¸Ýî.--}íwŢQUB€œhÄD‰X55ŮHJtD}˝EՒ޵S żVMă˛JYîźjűěłĎÎřRMßź†vvt:?Đ322ÂĽs*”nŠ?řÁŠÎŠá]5SŤä.b5…Ű.îúyęľ%djjjĂfeźËĹívcddz–×4CbȈČňžˇŚeźQ„¤ˇˇăăăŞÜŕŠ-6[C>úA“Âí%JB§VŤÚä¤ăšçžËËHaNîŠŐŕőzńꍯ˛ĺfgçdYŐl0*a¸ňÝ埐kßţöˇmË&ij¨ŁŞzX,uhl´˘ąŃź•˘”徭,ËXXHca!­zó-3R )äâŋ¸xńbMSKĹď÷ă˙ńSÉdŇpů"†‹ˆ¸\ޟz<]ä…čŠŒđ‡˘„c,ŸOBQžÔZB€ťš|z“ŕnžH(ú) v8žĄ6;Ýn÷cąXŻžňBôĺŒT˛ŕ´ľ9ár5‘„Ť°Ůŕr5Ą­Í‰Ś&›iŁd<ĐBBô(!ŔÝ|‘X,Ök´ţ"†Ƙ7‹}÷•W^Ńe^ˆÖŒT†ĹRť]B{ť‡D‰ŠÄŚX,up8r÷ŒÝN÷Lšh)!zÇëőâ•W^ÁrĂ,t†ř aŒšívű˙ęëëĂŕŕ ÖĂQ•jrzHFJÇbŠËw;ľŰéé–(Ćěv[ž‹+ ÉćđŁĺ=Ždpp}}}°Űí˙‹1fˆĐż!rD:::ţŰÂÂBűč¨á’‰‹H&ÓH$ŇUý˘RÎČĆä" 6H’UëĄhB(Ę˙=™Lbzz:˙ąĎç+ëZ…‡KnÝş’TiĂÖ IDAT$ĺ?öx<ŽPL$É I˛"™Lc~>E‰­kŔKB"‘y45Y ý;<::ŠîîîöŽŽŽ˙ŕ˙Đz<Ő"źˆ,—ę~çÔŠS†Î QÚś@$2O2Â3ˆ"+˙œžžF*•ÚôűË‘ąąąM_cłŮ°uëVwĺdĺŸF‚„dmxJH&“ÍϕFý}vťÝ8~ü8Űťwďwcďˆ^Ň+´ˆ0ĆÜ6›íÍ}űöa`Ŕ¸íř3™,âńdŃÇ$#|0š€(QŒééiDŁŃüßK -HĽRů3–”? F•­[ˇÂívç˙^]’ťđ–…x<‰úz‹aˇ˘pŕŔœĆ؀¨[4BŠcĚ-IŇÉ^xĄě}kQeąXbÓ ‰d¤t$É §SF@’É$Ž]ť–—ŁE;x333ƒ™™LLLČEM)yđÁu1QŞlššŹˆÇ“ŞyŻ%ľÂ×Ĺb Öű|>:tŻżţúIĆŘVˇh„‘öööSvťÝfäśŇĺHÉČĆX­őp8$!ÂłÓÓÓ¸víŽ]ťfęˆbą&&&011Ó§OŁŁŁ>ř |đÁ|‚ŹaŒĄššMMVĚÍ%‘Ng´Wj)! ĘŮÚę¨ř=őĚĐĐFGGmóóóBnŃ'"ʖĚ[o˝%TčľîÜY([HFVĂƒÓ)é>Dk׎é6ŠÔ(“ąą1Řlśź”<řŕƒZmMęë-pťíH&ӈǓ†ŘŽŃBB”jšććÊß[Ż(]WEݢJD”*™_|Ѱ[2ss•‡dIFîŇÔdÓu#2’mIĽRůh‰ŢĽDÉ™ŸO!‘÷^ŃRB’É4ęę}oÓU‚˛EsäČáŞh„‘ĺĆe†Ý’É5,Ťn˘1ťŒÔ×[ĐÜܨËm˜P(„`0HňĄ3֒Ż×ŤŤ>&ŒĺOIj¨(bŞ5z…D"…úú:ÝGJ+ahhǎłšÝnĄ #"Œ1 ܏ʰ k™MFî&úŮ´JŃhÁ`Á`’M PJ\.ź^/ź^Żnćœúz Z[H$R˜ŸŁşFO˘pç΂!{ŒŹhtöY–ZŠ„ťÝţ?úűű ٸL)Ó剙dÄj­‡ÓŠŻł<Ž]ť†`0ˆëׯk=˘BbąĆĆĆ066†/ůËđz˝şŮşij˛Áfk@<ž ëdV=Jˆ‚QËzđÔSOáüůó˙Ŕ=Z§„ˇŰýC‹ĹŇnÄĆeĽ–éV‚ŃeDoQd2‰+WŽPôÀ\ż~ׯ_ĎGIyäÍˁ-–:¸ÝvÝFGô,!ĘľZÖ;<<Œ;wśťÝîFŁŃ˙Źőx6C÷"Âóřî+Żź˘Ť=[^¨˝ßkTŠŻˇŔĺjŇĹÓĚôô4Ž\š’ď_A—Â(ÉΝ;ńČ#h^ ŹDGbą„Ž~?ő,! ʖ¸ËŐ¤Ú{hÇăÁË/żĚţň/˙ňťŒą#˛,‡´ÓFč^DÚŰŰGżđ…/`ppPëĄpg~>‰TjQő÷1šŒŘíěvíŁ ĄP@`UűqÂ(š$ÝÝÝđů|š>(Y,uhmu`~>…ůůäćß "˘HˆB*ľˆůů$ěvcUŇ bddżűÝďFxľĎFčZD–{†ě|ë­ˇ´ wŇé ćçkW9aąXęŕr5iž` qĺĘj8FČÖwüřqtttŕ‘G×ŤÝœoˇŰ`łŐ#Khrnh˘0?ŸBCC=ŹV]/‰e3<<ŒÇ{l§Ţ{‹h×^ƘŰĺrýý ×3$›]B,–¨ůűŽw€^9(2Rk°ŮĐÚęĐTB‚Á †‡‡qúôi’b3338}ú4†‡‡ 5‡RYcł5Ôü}E”­äMM|>8—Ëő÷Œ1}”~­nEÄĺrýgY–ÝFLPU+9ľD“ĽÝľËŐ¤YBYĄ€P*ąąXLs!aŒÁĺjBsscM~oD—ĺýľx@T›ááaȲěvš\şMZŐĽˆ0Ć<ąXěť/żü2ÓKý>/ôЌHQ&7­…B!9r„„¨EHŽ9‚P(¤É$ÉZłßS‘%Dg?'˝ŕvťńňË/łX,öÝĺâÝĄË 1Ł&¨&“iݜ¨Š÷œ›­ĄfOs+™žžĆŮłg) •ŕÂĚĚ Ž?Žîîn<ůä“5ݞQDáΝîÉńF’…d2 ŤŐb¨ÎŤzO\՝ˆ,wP5\‚j&“E<Žm6űJô*#ZUĹDŁQ*Ă%Tajj Żżţ:vîÜ ŸĎWÓn­ĘV ĎŞ#JˆB<ž4\çՂÄUŸŢ:ŽęNDœNçOüqC%¨Ę˛Œ;wt×pЗŒ(ů ľN˛€@ €+WŽĐ0„ęLLLŕÚľkxä‘Gj>ĎŮí6Ô××U=YB€ťsś‘šů|><őÔSxď˝÷~  ]ëń˘Ť‘öööďĹăń6Ł%¨ÎϧtůËŚ ‡œebŤľ„„B! cllŒ$„¨ŠT cccŽyţˆÍÖŔĺwŐ¨˘ÉdkÚbĄ #ˇľˇˇO빢aŒšççç>|ŘPTSŠE!ŽîÖRFŹÖúš—GŁQœ|óóó‡őTÎŤqš\˙ŮjľZ” Ş„÷DA ‘$+Üîچ?Ż\š‚#GŽĐt„n¸~ý:Ž9‚+WŽÔě=cpťKŻJ3›„(ču[˝RaľZ­z*çՅˆľ\7ŐŽ_HĽÔRF Í͍żOšLOOcddçΝŁmBw¤R)œ;w###˜žžŽŮű677Âáظ˝šY%ȍ;5N=–óęBDZ[[ÔÝÝm¨rÝD"…ĹEýĎ˝ľ‘ććڞšđúëŻSI.Ą{”ęš@ Pł÷lj˛­űP`f QX\ĚąĹ^*ƒƒƒčîîFkk돴  aŒyfggż544¤őP¸a„$'ľd„1†ÖVGÍjôŁŃ(Ž9‚ąąąšźAđbll GŽŠYîˆ$YŃÚę(’ťč˝č \†††0;;ű-=DE4‘öööŃžž>řý~­‡Â Łě)ň–‘ZŸSŁä‚Đš0D)ěÚľ ---Zو™™™šćŽŠIH1˘ĺüm†ßďG__ÚŰŰ5? OÓ>"Jó2#ECć瓆řĽSŕŮgdiI†Ĺ˘žű&“IŒŽŽ —ŒÚŮىĆĆ\x<‰ ‰h<"óĐŮىţţ~<ú裸téĆÇǑL꣥’; …000IR÷¸zĺĐźş:F˛‚\´; ť]ݟA­ŇE“3ME¤˝˝}řĄ‡2Ló2#lÉŹ/ąXÔŻŒ …B8yň¤0ɨ---ŘľkzčĄU Ěää$._žŒÉÉIFgúűű’$Ąˇˇ—/_ÖxDŤš~ý:†‡‡ąoß>Ő[TűŔ`D Q˜ŸOÁfk0D×UŸĎ‡žž>|üńÇĂаőťf"bÄhˆ‘Âv+á!#j„É‘$ ýýýčíí]÷5===čééÁ™3g0>>^Ăљ‹;v §§'˙ńůóç5ÍƤR)?~}}}ş}€3˛„(Üšł€ÖV‡ÖĂŕ‚˘"š‰ˆŃ˘!FےY ˝ĘH2™Äɓ'…ŠˆŮľk}ôѢČ­[ˇđńÇşşş°cǎü×öěك›7oâÖ­[5ŤP˘!@. uőęU GSccc…BءoŸę[5ĺ` ŒľEه¨ˆ&"b´hH6ť„DB§ęލŢdDé "ÂVŒ$Ixć™gŠžžŻ^˝ŠóçĎŻĘéěěÄóĎ?Ÿ_džyćźöÚk5ŻX™ zćĚ GSSSS†ßďŻů‰žka QH$Ґ$kMňŢÔF먈&˙‚÷Ţ{ď=‡ËĹ(U2ĽÂٚ†Á`ŻżţşŇŮىC‡ĺ%$™Lâĉxë­ˇÖLJ˝uë O nii)Š’Ő#I}ôŃüǗ/_.A8•Jáő×_G0Ôtf“ŔXU4JTäŢ{ď=˘Ĺű×\Dcžßţöˇ_6J4$™L ۸Ź´–‘ŃŃQœ>}Z“÷Ž„ĆĆƢúĽK—6MB]šMĐŐŐĽÚřĚHţg’L&qéŇ%GT9§OŸĆč¨6U˜f”…ĹĹ ’IcDdž†đŰßţöËË;5Ľć"ŇŢŢ>l”hˆ,ˈÇőQâ§ZČH2™ÄČČ&&&jöžqľ§§ű÷ď/’ˇŢz‹şŞr`ešŽYÚćoÝş_ýęWU}I˛ÂáżËhľ%qu``ÝÝÝhmmýQ-ŢOuaŒyfggże„ܐL&K ŞČM:MM6ŐßÇfłéŚk$o&''‹Âţţ~ZŇéźÄúŒăŐW_-úwíééÁK/˝„]ťvćߗ‡„¤R)՚žéa[WK‰´đłj78SED:::ţĘ ĚĚ Q3á, âç?˙yŐçÇMF +eV&Y•‘L&qćĚ=ztU7ŰC‡•t˛žá%!###řůĎŽJ;xł'Ż!*˘48ëččř+5ŽŻŠˆÄbą?§hˆŘ8’*O1ĘŮ1ŔÝuIFr$“ɢrޕe§DĺÜşu 'NœŔ[o˝•Ď‘$ {öěÁÁƒ…Lć)!ĘI˝jMS_oÓiŒT%!*â÷ű‹Ĺţ\ksƘ?™LÚDďbćhˆÍÖ J‡Äééiœ={vŐçHFœ—˘"|šzőęŞ>-Řż?žyćaňGԐ…łgĎŽú$ÉjÚ|#DE‘L&mŒ1?ďks‘–––Ă€Č%ťŮě’iśłXęTÉ ŮH8HFŠ9sć €\„„NÜUĽsíĘü‘…ý÷~PSB6űZľ477ÂbŠŮĄďşb~>%tˇŰ ĽĽĺ0ďks˝#cŢH$â=˛°`Ţ-—Ť‰ű^n2™Äčč膢A2r—ÉÉIœ?~U˘%Á—H$‚3gÎŕĉ˜œœÄĽK—tßÍVm )|Íčč(÷Ƙ&ݙő‚čk‹ßďG$ń0Ćź<ŻËUD:::^ęîî†Ďçăyٚ"˲đ7KĽŘíę䅜%iő%ž×ĺ*"FHR5knHnrŕ߃`ttSSS%żžd„ VSk Q˜ššÂččhĹďšvťÍ”%˝FÉ᝴ĘMDŒ¤jÖJľÂĽÁ`eÉAÜE+ Q˜˜˜PĽ’Fm`˝‚Ćď÷sOZĺ&"ç/žzę)Ą“TÍ ąŰmÜČŚ§§óeş•~?Éav´–…Ó§OsO^ľXęt׾šˆqťÝxꊧŕńxţ‚×5šŹ>Œ1O(úW ŤľMM|'ƒd2‰‘‘‘ŞŻC2B˜˝HˆÂČČ÷źĽŚ&ŹÖzŽ×#DEBĄĐżâŐi•‹ˆ´ľľ=ërš„J- }cTc N'˙Rݓ'OV%…ŒfDo˘\OäU§Ó|]WeYF*ľ¨ő0*f``.— mmmĎň¸I§Ó˙^ähSö QcK&”•œZ $#„™ĐŁ„(LMM!p˝ŚYˇhD_sü~?Ňéôżçq­ŞW!Ƙ7ˇ‰,"ÉdZčF3•P_oáž% …066Ćőš $#„Đł„(Œ! q˝fS“ůŞh˛Ů%$“âŚřý~Äăń6=EŞĽwˆ×ËľżIMYX7DV)źť§&“IUÂś…ŒFF Q8yň$÷|ľNúÖ3"Ż=^Ż—[O‘ŞE$>+rďL&‹ĹŌÖè)j<}lÖ9•$#ę3gMaÚǧma;‡`çďpŠűł˛ŻsŠűłü÷ÚĆ´3Ž9ŤŘáhľIB”÷âÝ_D(­ŢY\Ě “Éj=ŒŠD4­:O¤ŞteĆŘIä$UłUĘ0ƸďÇ^šrׯ_çz͍Pd¤š‰[‘‘ZMÜzdś1ŮŚD^<ć­)ĚYůý>üş-źî×i+ěiśĆp¤mhM4ĄuÁœ­żE“…ëׯăʕ+xä‘G¸]Ónˇ ß}´\‰´°Ń üĺ_ţĽÄešb3­JDZ[[ŸťďžűŕńxŞšŒfȲ,ô]%8× őh4Ę=y­HFĘ#mÉbÚyӎ8f›˜qÄ5Ϝ59kzŐ8:ćœhM4aëœ[ăͰf7 Ş„(<řŕƒÜúGĺ*ů$ÜšŁ˙Ăy‘LŚšĎËľÂăń`çΝřÍo~ó€ŠE¤Ş­™ůůů?9IUäŚ2•`ľÖC’Ź\ŻÉłTˇ\h›fcŚqüó˝7đîöńćÎpĄçS|rόć˛3Ž8>šgz>ś;?ŔťŰ?Ć?ß{ÓNýŽšRD—ĺýyç†I’Őt˝ED^‹ü~?ćçç˙¤škT,"ŒąT*%ôśL2)n˘P%8|š %f§&$#ĹÜpGpŠű3źšóœ{ŕ>šgł ­‡U1ł |rĎ Î=p oîü—ş?Ă wDëaU$Daff†{T”÷\ĽwD^‹JĽ¤ĺTŠ¨XDZ[[ŸŰšs§°Ű2ŠÔ˘ŠJv%ÉĘ5AuzzZľRÝr1ťŒĘDžžOńëś0ŇqŕÖ#mÉâ×má|´DT)1’„(ŒqK}˝…{ôVĎdłKÂ68SśgZ[[ŸŤô‹ˆčŰ2"hš(űŽ<9{ö,×ëU‹ŮddΚB°ówřŮW& -ëQ(%?űĘ‚ż˘"Lj˘Ŕ{N5o˘RD^“ŞÝžŠHDDߖŮ>+ĄŠÉĆőúʕ+Üť§ňŔ 22íŒă˝ű˙?űĘG˜čźÉľĘETćŹiLtŢÄĎžňŢť˙_t›Obd r]WŻ\šÂízŒ1S•óŠĽŻv{Ś"}[ĆL•2Kššř…8ľŞ’)ŁĘȧmaźťýcœ{ŕ~ăŠj=ÝňW縆wˇŒO7(Ž5F—…@ €h”ßýŮÔdĺ~ …žumŞv{Ś˘Ÿp6›}œśeÄŔnç 9{öŹfU2Ľb$ů´-ŒŸ}e—ť?:é´ÖĚ6&pšű3üě+š ‰Y$ȍ“ç}ôŒČk“ßďG6›}ź’ď-[DcŢX,Ö"권ČáŻrąXę¸&|…BĄš6.ŤŃe¤P@hűĽrćŹiM…ÄL˘pýúuŽgŃH’y˘""§ ‹ľTröLŮ?]‡Ăń‚ČŰ2Š”yÚšó~’ŕÝŇYmD”‘igďn˙˜„3Аźťýăšĺ˜QBxĎfŠŠˆşF)Ű3‡ă…rżˇl‘$éO|>_šßŚ ĚÔI•w4$ ‹qť^­EFćŹ)œýƒk8÷Ŕ5ڂQ‘ŮĆÎ=p g˙๪U6f–ˆĹb\sÉĚI&Ĺmqďóů IRŮŐ3eýdcžp8|Ÿ¨ů!˘†ź*çD4ĺš _kô.#š2܏tÝńÔhĚ8âřŮW>B°ówÜŻmv Q¸rĺ ×ÄUsEEÄ\Ťü~?Âáđ}Œ1O9ßWŽb477Ë^oŮ[@ş@ä#—ËAhˆŢT7C22íŒăg_™ŔDçM.×#Ę'Wö;Ámť†$ä.ŠTŠ˘""ęZĺőzŃÜÜ,(+‰´ŹŸjWWןíÝťWČ3ŮěĹÜ{+žOÓÓӘ˜˜ŕv=-ыŒ¤-Yüó˝7pîk”˘ćŹiœ{ŕţůŢU5…# YÍÄÄ×˙łDE3ÂUěÝť—uuuýY9ßS–ˆÜźys—¨ů!˘†şĘ…w4DoTŤEk™mLŕÝí˙ŸÜŁí=Äj>šgďn˙ßĺ萄ŹĎ9ÄLQQ×,ŸĎ‡›7oî*ç{Jţ‰*ÓD-Űš>ťx>1„B!]vP­­d$Řů;źťýcŠ‚č˜9kďn˙¸ŹÜ’™ššâZÎk–¨ˆ¨k–âĺtY-YD:::ţtçΝpťÝ M[˛Ů%d2Ć?‡ƒ˘!ĽSKI[˛8ű×(D &:oâě\ŰtŤ†$¤4(*R>™LVČíˇŰ;w˘ŁŁăOKýž’šŮlś_Ôhˆ¨!Žrá)!Á`33ĆŢ>¨…ŒĚ6&đłŻLPEŒ€ä*k&ÖÝŞ! )™™ƒAn×3ËÉź˘Ž]ČfłýĽžž$QĘvEÍ5ÄUš˘řVʘ5eD9ĆL§â´%ťćš5$!ĺĂsNij˛šâd^Q×.ŸĎWVoŠŸÓé\QD̲-ÓŘČď3 ŮźŹRԐ‘Kݟár÷gź†HhĚĺîĎpiůçIRąXŒ[T„1†ĆFăGEDݞńů|p:K|Ľźž$immÝóř㏠š)'jhŤ\xţRš%R/9ŕ?€_Oă×::ů•ŕï۸ńő4ř„TϚŠ"ˆť†=ţřău­­­{JymIr‘Íf1ˆŰˇżlśnÉ[f‹†ÂCF$›„á?ţ/ŘŃÜĂqd„ŘŃ܃á?ţ/lRĹ×0ł„|Ł"Klś.×Ň3˘Ža>ŸŻäÓx7]˝cžX,Ö"˘ˆČ˛lŠ&f’Äď—QäVî<ŕ!#Î;ŢüĆߐŒˆÍ=xógƒ˝âk˜]BxÎ1<ç>˝˛¸˜ňěŸĎ§œĆëŮ쾼„B!ĂWʔÉQI_fff¸őá Ö3"ŽeíŢ}›˝vӟ`WW×ӏ=ö˜éÉé´9’TyaĆܐő !’ľ \‘ňu-{ěąÇXWW×ӛ˝nS‰Çăßq[Ó"˅×/áôô´!Ŕ}Ř IDATť¨VɈš! QŠŠ)n˙&fQ×2ŸĎ‡x<ţÍ^ˇĄˆ0Ć<ńxźMÄm™tZĚ}ľr°Ů¸•ěš=7d=HFĚ Iˆúđšsc†OZ•eé´xůŽ^Żńxźmł<‘Í""^ g5˘!â­\x%j%“IܰŤ$#ć‚$¤6LLL ™Lrš–’VE\Ó ÜaĂhƆ"âp8žčëëă4¤Ú"â­x&ŠR4dsIŚ*Ÿ8IFô IŚ’$!%Âkî1CŇŞ¨kZ__Ç˝f3Ů-b4D–eĂwSĺŠäy„‘™žžĆż;˙__œŻř$#ú…‡„ÄçńďÎ˙W’á9÷}{&“É ™nŕóůŕp8voôš Edzzú!óCDMě)^ĄČk׎™śYš\ęţ –&đě/żG2b0xIČłżü.,MäŰÁ‹ĹpíÚ5.עí}âőz1==ýĐFŻYWDc>@ÔücGCęë-¨ŻˇpšECJăÓśpžműŐ;“$#‚§„\˝3 ×~ĺAyÄÚđšƒx΋zEÄ.ŤŠC(NąEDźÝÝÝpťÝ|GUŒŢM•—ůGŁQ\ż~ËľŒĚlcbŐv$#Ć@ Q¸ÜýfŐŃđ\ż~Ńh”ËľŒqmsťÝčîî6HX]WDşşşq[&›]ň´ÂrŕľJѐÍI[˛8÷k‡ŽIFÄFM Q8÷אś;BË^s‘ŃóDD]ßź^/şşşÖ=wf]ľ‘™ˆĆXőőŽÜóŢý˙˛áBB2"&ľ '˛ďÝ˙/ż‡čééAgg§ŞďÁó <Łoψ¸ĆmÖŘl͍1桑™ąŸ>x…CĄ×$UIŞüDR˝ěüfńM_G2"ľ’…GÁÎßUü^ZŃŇ҂gžyű÷ďÇóĎ?Żęďx,ăvţŒŃˇgD\ă ›­™ëąŢŁľ°ĚD´ĹrĐëśLoo/^zé%ěŘąƒëuľbś1‰Î›%żždD j-! 7…É‘$ ťví*ú}–$ Ď<óŒŞďKŰ3Ľ!âˇYcłuEd9šD(dYr˙ŹT,–:nŰ2źJć€Ü“ÓŁ>ZôŐŇŇÂíúĘ{Ô*꒶dqĄ‚p:ɈžŃJB.l˛Í§z{{qđŕAô÷÷ŻúZOOvíÚĽÚ{óš“xΓz$›]˛ŸČF Ťkţ´DMTąĆşx™ţľkתjWž’;vIBOO^zé%ô÷÷s“‡ţţ~:tHՉP!Řů;ĚYÓ}/Ɉ>ŃZB`ΚÖíMOOöďߏ={ö=DD"œ?>ߊýŃGUí •Jq“ŁGED\ë6JX]SDž*˘ˆ˝›*Ď&f<š|ů2Ž=ŠÉÉâ z׎]\䥧§'/;==ę.ÎÓÎ8>šgŚŞkŒč =HˆÂ'÷Ě`ÚšyŢQ­hiiÁž={°˙ţUż[çϟÇŃŁGqůňeźűîťr[4kEKxAÍÍJCÄľÎëőbaaáŤk}mM‰D"óCDLâ)ơlpŢ"ˇn݉'đÖ[o!‰ä?ŻL\ŹX" 'žË—/W=֍¸Ü]ÝBŁ@2˘ô$! źîąjŮľk<ˆŢŢޢĎ_˝zŻ˝ö._žœ„\˝zWŻ^Űžá˝őŞŔknŞŻˇp;™\ˆ¸Öů|>D"ĎZ_[%"Œ1/xĽńâ’G˝CĘHFj‹Ţ%DAÍ{ŽI’°gϞuˇG#‘ČŞ†őřřăó×TŤÉŻ9Š×œŠWD\ó–ÝbóˆHWWWż˜‰ŞĆ-ŰĺŐMuzzZŐm™ő˜œœÄ‰'pć̙Uů#ë%Ęľ´´äŁ!‘HDŐhČ´3^Ră˛j Š ˘HktŚv⪒0žRř'''óňŃŮŮYQţVcc#—1Ž$•Jazzşęë˝ËވkŢrĺĚŞlçUŤ[:Ţ&ćAwâŮaŠX­âECÖb||G]•?˛Véŕž={ň_/ܛVƒ˙÷ŢŞ^_dD]D’ľî˝őJč#‘Nœ8'NÉ}Š9]]]ÜÇşüśgŒqÍsťÝH§ÓŰV~~•ˆ„ĂáűDʘ1y§TÄĘŮ%×ăčŃŁů,|Ľ™Ňž={ňOh“““Ť^ǓOŰÂ5íxI2˘"Jëŕűi[˜ë5wíÚľŞŠ`2™ÄůóçńÚkŻĺˇI #Ľ4+ëěě,ꜬćV)żíăFDD\ó|>Âáđ}+?_$"˘V̈¸WV<Ź>™LbfŚşţ<‰D"xë­ˇpâĉ|ŰŽ) #Ť ™Đ zd„/˘Jˆď{p||ź(â8>>ŽW_}uÍíÍÂÜŤţţţue¤§§Ď?˙|ţăÉÉIUEdffŚč˙ĄRŒÄ[űÖŤœYqžXŒÜ֝W=ź˘!k199‰ŁGâ̙3Ť&žńńń"IáͧmaŽĺşĺ@2ÂŃ%ȕóňŒŠ(я~ˇ _űÖ[oĺżŢßߏýű÷َˇ===čííÍŰP¸ĹŁv?€ĎœĹł˙’mí+p‹˘ü•"âŰšsg-ĆĂŃŹ°x=Ż“-ŐByjS" ĘdŞ&ZDC !Š#Hˆ‚Q‘•ŃĆő¸uëŢ}÷ÝźŒôôôä“Č÷ěŮłę Ë3gΨÚĎG×œe䨈ˆk߲cř ?W$"B&ŞŠŘeŽTęëůޤwî揬Ս7ZFC !Š #IŔ?*R.WŻ^ĹO~ň“ ĹĺÖ­[8zôhÉĽžŐÂkÎâ5‡ę×>ˇŰŽŽŽ˘„Ő"UĚfł‹˜¨ş´$VxŞxÔÂGŁQÄb1ŁŠ ĺô5¨”jϓá‰"#Ő,ʊŒčeaUŁIˆÂ'÷ĚŕKˇŰ5{E4vě؁žžž|˛ë­[ˇ099Y“(H!ąX ŃhŐ>🈈kŸĎçĂÇüpáçŠ~BŠTę^#"˘í“• cŒK˙˘!ľdÚŻiĽL)Œ”†Q%ČUĐL;ăŘwj:ŽÂse´& ĄÚžVKce™Ó¨ôƒˆkŸŰíF*•şˇđsEŤ\<o­™™ˆ{dĽb–üZsőžę›%ŠmÓlŒ‘%DAŻ÷ŚVPžČ戜z˝^ÄăńśÂĎĺE„1ćPuŹÖˆh„ĽÂ+ۛG—BŁ0gMá7ލÖĂX’‘ľ1ƒ„Ŕo\њŸAŁgxÍ]T9ŁÇPœ(ŽˆxTŤ5˘Ů`9đ8´IoýC´F˄ŔR!)Ć,˘ Â=Z+řő1ވˆś8†GůK‘ˆ8NąÔ @6kź}?…ş:>çËwůľ “<ÉHłI Î=Z+xĚa<ćR˝"â¸ěĺă"yřᇅűi‰–*JTĺĎ wD%ťĽbv1Ł„šRŢnőş–Š9LIX5""ŽËŽáQ>ÎŻt˘ö-,U*źÎH ˆČ]nč87d=Ě*#f•ďUľŕ5‡őÜ×Ŕ•˝Dň"’Íf-?€!K˛JTUƒßú”i61ť„âŢŤj@ Ť#âčőz‘Ífó˝D„ŰŠ)DD,ݓɤPĚÔä†;‚´EÜűĹ,2B’#mÉŇöĚ2ąXŒKÂŞ‘;ʊžć2ápx§h‘Ľ%ńL°Txä‡P4ä.Fu]FHBŠ1Â=Ë s9U݈śz˝^„ĂáüÁvE?ŃrDDˇŔŕF$š‹QBÝF•’ŐĺžĺšĚ¨[3€xkáJרÄmf&âŢX)0ƸdxGŁôDäZş‹ź-łŁÉIČÚ¤-YL;ăZCđ˜ËxÍŤzD´ľpeS3%"âÄkf&být)PĹ _n¸Œ÷di! Ů#Ţť•@•3#ÚZ¸˛Š™Đ›f"ÖO—/k'É1cЧJŃe„$dsŒzď– ŻšĚ¨Ń×Â|DDÄŽŞF…×^f*EgV¤-Yݝ´ËQe„$¤4f†ÚVŹ^s™‘óDDٰťj^DD쪺¸˜ŃzŞ`ąToíÔQ5Ç´óŽÖCPŃd„$¤<Ěp—ŸŤĆŒˆˆ¸vWN>̀‘ËĚjÍ´ĂĄmQd„$¤|Ěrך[őI8Ž­R.˘e —}LŠˆä˜m2îśĚJô.#$!•aŚ{x#xĚiFÍÄ\÷¨I’ţČçói: rY\4îž)ícňcĆdO“z•’Ę1Ű=Ź&Fž[E[}>$Iú#€śf ED`č$ՍЛŒ„TYďĺBhN3.$":ĂČĆ^kĚŇ֋Œ„đÁĚ÷2ohŽŐuÍf]Z¤\DĚ.…ş:ę!‹9ŤšË—ľ–’~˜ý^řÍiźćX˝!⚨¸GD"h9"ÄĆPP{lđ•‘r! áÝË4§ ŸĎ‡H$âhkFw44Ôk=Ă0OO‘řÉH-žG$¤ş—ůAsŹţ 1 ”Ô•cΚÖzş‡ŒÔ ’ŐĐ˝œƒć6c"ʈ¤Ób•*ľ…öÔW#‚Œ„ŹÝÓÄFˆź& +"FŨ-ˆk͜ž ×BĎ2B˛1tOóćXýQÇó€ŰíÖv$>-ˆ“É$‡‘ˆ ==Že„$dsčžć3ˇQ›w} 8c̓?tĆëőj8$‚'TşK“öfčIFHBJƒîišŰŒDsxH ¤čAFHB‚ ! U”†–2BRtOFE؂ꖖĘ{ŸĎjPGčgƒgú^Óz„Éhh¨Ç=÷×LÜĐPD„ ‚ Í !‚ B3HD‚ ‚Đ ‚ ‚ 4CŘdŐĄĄ!­‡@Ś' ßG‚ТţRD„ ‚ Í`|.ȲŹńP‚ ĺ LÔ'1‚ ´ƒ1QD„ ‚ Í !‚ B3HD‚ ‚Đ ‚ ‚ 4CŘňÝHd‹‹­‡Á–;ŞűąŒq‘˜\ęţ żn k= !ŘŃ܃7żń7p6Ôöü&:ôŽ<îżÝŽG§ž¨ő04ĽŻŻŻęs´3ˆD´;qZ-ę…=ƒ""„!q¤­ZA´’ wčݛßřěhîŠů{‹ÝӄQ!!“˘Ľ„(ŒQ Á`PۑÜŘşuŤÖCĐGÚŚőt$Dd¤4螦šÍH8G¨N–ĺDŁQÍDÜ%›]Şú’$q‰ŘФ˝>z’’‘ÍĄ{šĎÜĆcŽ%ŞGqY–C´5Ł3˛YępËGŠöÓ×B˘@2˛1tOóćXý!ʈX­­‡@čzz\ž%Ddd}čž&6Bä5QX!ÖÇăńh=]@UwABHFVC÷ršŰŒ ‰ˆÎ0bo­°Ó$>_,żďB%ߣ@2R ÝËü 9VÔ@KKK(h<‚'6M\[ăN­‡ 9ź$äŮ_~Żěď{ö—ß#áÝË4§@ €–––°,"‹%Śéˆ* ÚîŁzei‰O"•šŃž:O ФűéŐ;“$#œ0ű˝ đ›ÓxÍązCÄ5QqښŃ™LVë!†ÖD“ÖCĐ ­%Dd„fž—yCsŹţ 1(”Ô´.˜sň֋„(ŒTYďĺBhN3.uL&%ZŽHCƒ¸ĽJ›AĆΏŽ9sí­ëMBHF*Çl÷°šynmM H&“ż–EdnnnFŰ!•cLë!¨†,Wż‡IO9ĚŇÖŤ„(ŒT†™îáŕ1§ń˜[őŠˆk˘â´5ŁC¨1?śšäiRď˘@2R>fš‡kÍ­úD‘Đ| ÜOHÄ,áRŕт˜""9śĆ›ľ‚ęˆ"! $#ĺa†{¸xĚiFmď.âZ¸ě! @Dâń8EGtŻ}LŞťŹY‹ĄýD“’‘Ňh]h‚5+ÖŢżđšËŒœ#"ËÎߚąX„ţşđÚǤ^"9: Ú JT Q ŮŁŢťĺÂk.3jŽˆčka>"Á`Pť‘T€Ĺ"^rN),.ňąv‘Űb-Z;˘KˆÉČĆńŢ­^sŻšUoˆś¸FXY–CFľSň˜%\ ˛,s1wˇŰÍa4âł5î4TxŰ(˘@2˛6ÖŹ…Zť/Ăc.ă5ŻęŃÖBĹ5÷¨[닢P_oœĹe%<ö2)"r—ű˘Ćx˛4š„(ŒŹĆ(÷,xĚeFÎm-\éyiooŸmkŚŽN, ,ef$"wŮ?:dT Q )Ć÷,/xĚeF.Ým- ƒhooŸP>:ĂE4 ,‡LŚú_I’ŕrš8ŒF|śE[„Ţž1ş„(Œä°f-ŘF€Ëĺ‚$IU_‡ÇœŞWD_ ó"bąX>-"ˆˇ7V*źÂˆš‹¨ĄnłHˆɈ¸÷ŞđšĂŒş5#â aąX>P>΋ČĚĚĚ ŃrDńMp=¨r†?"†şÍ&! f—ďUľ Š™q ŒF٘™™šĄ|\¸5#dwUŃë§×C–e.{šÔaő.ۢ-p¤­ZŁdĚ*! f•GÚJŰ2đ騺d؊×ŔÂŽŞŔ ąťŞhőÓĺ°´D Ťźš˙vťÖC( łKˆ‚eD”{´Vđ˜ĂxĚĽzEÄ5°°Ť*°BDńšš‰–*•tşúP˘$Ičččŕ0cđ%&y’bĚ&#"ÜŁľ˘ŁŁƒK˘*šT݈śŽlfˆˆ¨MÍD K• %ŹňǑśá>ᅮ„ŹYd䞘Ž4Ľ@‰Ş›#Ú¸˛™°˘|×étŢŚˆˆ~H§3\ŽCy"Ĺěř˝>Ō$dcĚ #z˝7ľ‚×ÜĹk.Ő#˘­Á`N§óváçŠÎśŮlżFŁmľVőX,u†lVŁ$ŹVkźZ‰HOOzzzĐŮŮ ˆD"˜œœÄŐŤW5ÂÖ¸­ M˜mLh:ŽBHBJC‘‘jţ­ŃŰżUëBľt_%ŞnŒhŃ ąŮlż-ü\‘ˆX,–ŔÎÚŤzęęŒ)"°¸˜ĹR]Ľ‡Űí†ËĺB,ă4ލéěěÄżů7˙&/ …ôööâÖ­[x÷ÝwqëÖ­šŒg-ś˙ž—ť?Óěý ! )ŁĘČöߍ›ËŐŰۋÉÉID"U߇.—‹Ë3‹‹Ć†ÔՉ'"@ ¨‡°bkFÔ^"VŤXĄŠrŕŐ °VQ‘ŢŢ^3‰$pđHÎůœßŰóâ‹Řšs'—PĽáuÎ2ňDU=^űŠgˆËGźGFF 7ô–#+=Չtvvb˙ţýKžwëÖ-\šrçϟǕ+W06–ťXŠ˘¸jÔ¤˜G}GEww7×ővM~šëńʅ$¤2Œ$#Jż÷ěٓ—={öŕÔŠSÜ?OźĄúőŃăľoŃ1‚…ß+‘„ĂáŞ,ˆz̓•J&“ĺ’ßÜşu+‡ŐŹÍΝ;ó–$ çϟÇoźááaŒaxxçϟǵk×ädäđáëޝ‰˘ˆýű÷ăĉčěě\r2做Q’>AF”ކ<účŁË¤ŁřłĽExœłcÔ1Ł! ÜbIęeÉ߂1*z°.ĐŁ–ŁWzžˆËĺZrBűńœ~344”——Ë…]ťv-{ĚΝ;—Ýľ‰˘hˆ¨I_ô.#Ո†Č C’¤ü×r´ńđáĂpš´3͕ßüăFCý]űdˇ]Cf™NšÝÁ`Uœ††úő¤Sx푠dT¤đ$vëÖ­u Q‡††ň˘˛sçÎüó;;;ńâ‹/.‹~ĚÎÎâüůóâşî'ŚÝh™ˇr=ćZ„(ƒ^e¤eŢŞh4dűöíů„ąą1\šrçΝĂđđđ˛Ç8qB3ő#źÎUFÝ_Đç5/ ÂívTüýe"b6›uY°ÚĐ /3,^V_ô LLL”ô¸BŠŘšs'Ž=ŠŁG.‘I’píÚ5źňĘ+ŤFX*ĺ˙úř1EŽ[ Iˆ˛čQF”~ďFCŽ\š 'őrÝVágJ.h=qâ„ęő#źÎUFŽˆčńš‰D`6›?,ţţ2™˜˜¸ŚˇĄfP_ŻŻ\Y9d2Y.íÉmmm°X”™Ú8??_ösĆĆĆň‘“îîîešęááaœ={–{¤˜ś˜­qeç7„T=ÉHkÜŽčܐÂHăľkזľíŽĺ‹Č Ó5.— ű÷ďÇŃŁGUŠąX,\&Şfł †ŽŃă5/ abbâZń÷Wú›„nܸQ…%ńEoš˛ráŐ ŻTTdrr22{ňÉ'KzNww÷Šyéąą1œ;wnŮ RIv…WěŘ$!ŐE/2˘ä{ްöJ’¤eИBdá—ëśd:;;qôčQěßżżŞé~iăFC}^óÝbY¤c% T°Ş5xmÚ¤dzć˝÷ސ+€[Ťđ­łł'NœXv‚›ĹoźóçĎW}ŘYSڂŽÉÍ܏K˘Z—‘ŽÉ͊î)SXëqíÚľu…^’$ á•W^Y6ů¸ťť§NZҧ$üŇ2ƍ†úťć8E¸řgËDDŻ3€>‹wJ%•zŔĺ8[ˇnU,=S˜B)ĚM˸\.>|G]2?¤°DÍńďžÉOsmç% Q­ĘHSÚ ‚2ůÉÉÉ5Ł!ĹŹv3P8MÉA„‹…›ˆđ:gj=^ëVë˜VŽˆŔĺr…őŘ9ŁÇ)sĽÂł^ЍČěělţ¤ˇ}űöü KEěÜšsœŘđđ0Ν;§xHŠěçsÁ! ŃZ”^ďąU_š(Nˇ”Šœ-ŽŚŹv3Á ^ç&^󗴊ŻuÁ`.—+źŇĎV‘ĆĆĆ_ęł`U˙9ĺ Iü˘"JQxâzöŮgąsçNœ8qbY„¤°PNK{_´ĹěďůA˘-´$#Ű>iU|cťąą1H’„[ˇnUÜi644´bÁ¸œ^坮áunâuŽÔ*zźÖ…B!466ţrĽŸ­("×ő("fłţÂU堇ôŒ$Ixë­ˇ< ç֋ŹÖ:¨%*Iѐ„h-ȈŇ)Y6 )fľzYvxAi™ŇŃăľnącćúJ?[­˙'4>>Žŕ’”Aݍź-‡lvŰ.ĂJFE ‡•ríÚľ‡)i sքÝüNŮĎ# Ń6jËČî~ćluîd%Iâi”‡ žńĆ˜Ĺ[o˝Ĺő5x“xž'ľˆÉTAÔ^FŮ,:ŊŽUEČĺtô†‹xʁ—é{<.ÇY7Ţx#Ÿ˘šuë^yĺ U­ˇRZć­euѐ„čľd¤krsU'ř*‰üyć}CÁëœdôhˆŻq.Qşˆ0Ć"vť}ZŸéýĺÎʁWهƒËąVB’$üřÇ?^rĽ7<“Ÿ.iĐIˆž¨śŒ´ĆíUIÉTž7‡ƒŰnťFŻŃă5. ÁnˇO3ĆVŰžjĂnˇ˙ED´Ż)Ť€ňQ‘ÉÉIÍց”ĘW× §“„č“jɈ9kÂW7ćŤ5x‹Œ>MĐç5. Ânˇ˙Ój?_UDôZ°j2ŐşNĐOzĆ˜ł&ěýőĘšk’}S Ůűë­UŤ Ń3”–) ˝^ßÖ*TÖ,Źęs<ýc9đ =:Nlٲ…ËąŒLËź;ǗŽă& 1JĘČÎńÇ S˘$[ślÓéär,٧eôxm‹D"kŞkˆc,čł`ŐbŃßV9d2YnáGŠŠ”ĆÓn|nqťv’cĄ„Œ|nڍ'ß/ÄÚđ:ńždšwďŢߏŒŒ ‰lle*R+é™lvëtßž}܎Ľ5Ş!!2ćŹ ű~˝]“›7üZDuéšÜŒ}żŢşn4‹d¤4xžK$)]-ťzMËD"ŒŒŒŕŢ˝{_ęsJţ[2Ć@Îtôˆ^C\ĺ’Hlü„XLGG‡!§­VSB ńL~ώ>‰Ś´yĂŻK(KSڌgGŸ„gňÓ%?‡ddmÚŰŰšÍ řžă´Œ^ŻY˛#ČÎP eéÖć͛‡ôZ'˘×WšPTdmԒ™–y+žý7Řö‰ąÓ^zdŰ'­xvôߏšŠY ’‘ŐĄhČĆĐë5+ bóćÍCĺ<§,™˜˜řťK—.é˛BČdŞŤ‰áfß;†śś6ĂěĚŤś„Č˜ł&|ńăǰ÷ý­ŃMi3öžż_üřąŠ ‹IF–ÓŐŐĹőďR+ѐ††z]ŚeŕŇĽKlbbâďĘyNšӀ^Ç˝@cŁ> ł\xGEź^oEo-  Y˛ŚXŽ’jGÔŁkr3žńŤ.´Ĺř“Œ<Äbąp픩Ľhˆ^ŻUcÝËŞá(KDä6^żß_ÎÓ4ƒ^C]çƒÓéÔőčw-JH!žÉOăżúUčun˙Fŕ}ŃŰŰËőxJŁ )¤-fÇłŁObçřăT?Â‘Ś´;ÇÇłŁOrKĂŹG-ËďsEmECôy ‡ĂA<ľÜç–-"ŒąĂá˜ŐkŻĹŇ Ű" ráéččŔ–-[¸OIô(!…<1íĆ7~ŐEBR!˛€|ăW]ËŚŁVƒZ”‘-[śpm×­ĽhˆÉT§Ű´L wŰ-ťˆtCWd“Ét]Żu"€~űł7B"‘â: yßž}š/\Őť„R($”˛)–yŤŞRH-ɈĹbáÚŽËŤŠhˆžŻM~żżäÝv‹ŮˆĚĚĚźŚçôL­Ô‰š¨Ď=hœN§Ś÷Ą1’„"ď[ł÷ý­řl”_ Ńřlԉ˝ďo]q5Šńz˝\ T“Éډ†ú˝6Éi™™™™×6ňü ‰c,`ąX$˝ŚgôţÚÉ$ߨȎ;49qŐ¨RH[̎Ż~đ;řĆŻ>ŽÉ͔śA.ý’kĂý<žúÁďT­¤\Œ.#ííí\ťëryÖN4DĎe@‹E*gšj!ţ[Űlś ôŒ>`Œ!“¸SkWkAB iJ[Ű~ť°{ě |nÚ]Sťüšł&|nڍÝcOŕżę‚gňӊvÁđÂČ2Âűœ‹I5ąĂތžŻI~ż6›­ěn™ ‹ˆŢÓ3zśĎ Iid2YnÇkkkCOOˇăUB­IH1E\Ř5ţ8ž5ňCKIĄ||kä Ř5ţ8‹¸Ô^VŮQFzzz¸Ž%“ÉÖĚť€žŁô•Śe€ DDďé@ßşâqžQŻ×‹ÖVu÷LŠu )ŚPJöžżŰ>iŐu‘kËźŰ>iĹŢ÷ˇęZ>Š1’Œ´śśrŻă}ŽŇ:zžUš–*@˙éŤUűĄ\ž¤ÓîwGŽQ­‹†$dmÚbv|ńăÇđěč“ůhÉśOZ5=Áľ5nÇśOZóQgGŸÄ?~Lłu•`ąX,8rä×cJRét†ë1ľŽžŻE•Śe ˘É)333ŻÍĚĚ|#síŻň¤ŐZ Ćb,–n“űä.šŤWŻr9^А„”‡9kÂcגhÂLc3Ö$âćîÚcH˜Sˆ›ŤóYhJ›aK[ĐłŁ)mAKŇŞëČÍF‘e¤’÷˛,#jź—ywÉ(QĎŚuô´Ě\ü'~?nN!nIç~7§żWţgäsÓî|WOSڒű•2뢨´ščUFślŮÂ}*Ţsô€ŐŞßÎ79-#IRE5Wk:ÎŸ TzŐ¨Ż7ĄĄAŸ#u7J2™âZ¸ äF:W#EC˘>Ž{÷î˝Réą*ĆXČnˇOëšhUÍ5ŐĘ (s÷ŃŃŃĄXK/IQ čAFzzz¸×*ĽŐ:&Sn'Šš"UťÝ>˝‘˝eŠárő5›ÍŠg›­öBƉDŠűřdŻ×Ë}ę*IQKhYFÚŰŰšˇęfł 5ľŸŒŒŢŻ9~żfłů/y‹‹ˆLOO˙$BĎ3Exv’č…\…:˙p(ϖ^’˘ѢŒ(ŃŞ ąŘ|ͨ ‚ Űf@ŽH5bzzşâú€“ˆ0ĆÂ˙Źç¨ˆ şŽ^Ţ(ét†{ŠFEôőőU|’˘–ŃšŒôőőqŻ I&S573ČuĘčůĆ×ď÷ŁŁŁăŸcaÇăV‡˙ćňĺˈD"źYuŹV‹ŽßE‰M[[8PŃóIBˆZG+2rŕŔîižZMÉänző›–‰D"¸|ů2Âáđßđ:&7aŒůEQLQTD0ƍ&š×ăń ŤŤŤě瑄ÄCԖ‘ŽŽ.x<ž żöjDŁÉšKÉƈ†ˆ˘˜bŒůy“kŤˆĂář[=Ďj7*’Édš;éíí-Ťx•$„ –Ł–Œ´ˇˇsŸ䢰ľÖ%č?äf‡8ŽżĺyLŽ""Ď ƒ<[UA@ccíEE ‘999r¤¤ÍńjIBvî܉ŁG˘łłSíĽÔ˘(bçΝj/Ł"Ş-#­­­Š§ćnzjkŒťŒŢŁ!Á`ŰěB¸Šc,äršÂzNϨY” —Š˘¸îäŐZ’Qąk×.tvvâčŃŁŘż?\.ýď(ŤUşťťqęÔ)ěŮłG÷âW-‘'§ň.NU* Źä˝ÍôŒßď‡Ëĺ ó˜R÷)^łłłgu]´j2ŐéžÇ{Łdł ŠL8\K4jIB ąącccůŻťťťqâÄ Ýßąk•íۡç/¨ű÷ďWy5•Ł´Œ(9mnnž{aź^°Ző=83‰`ppłłłgx›űżŠŠVÚ­€Tę"śľľaßž}ËžWKłłłxă7pţüyLNćv›E{öěÁ‹/žˆíۡŤźBcqíÚľüŸ].—!„OI١oŸ""Ii¤R¸WĽ6„w‘ŞŒ"zf„˘ŐZí ‘‰Ĺ”Šńx<ůśŢZ”BĆĆĆpîÜ9\šr’”Ë™ť\.>|GĹŁ>Şň Áää$†‡‡ó_ďÚľ‹{ĘA ”‘(Ň!“Éd‹Őf] ˙Ú —–á]¤*وˆÜťwďOÇÇÇu=i¨í¨c ssĘL<ôx<řÚמVÓRČđđ0Ξ=ťäνłł'NœŔţýű qŃT›k׎ĺeOŽ>ž2ňľŻ}M Qň\˘Œ r‘ęŸ*q|ED„1ŢźyóEEôM&“UlĢŸ~š$¤I’044„W^yeYýČŠS§ĐÝÝ­âęô$Ixë­ˇň_www&âÄKFž~úiŽŤzČÜÜ|MśęĘ!200€Í›7ńš¤ZŒb•3˙ůƍ‡ĂJ˝DU¨ĺ¨ŤŃÚôCŁIH!łłł8ţü’T‚(ŠKä„ŘCCC˜Ím”¨ŔGF” ‘HŐl]`ŒhH8ƍ7011ńŸ•z ĹD„1´ŰíÓýýýJ˝DUv{m‡Ć I3'#KˆŒËĺ“O>™˙úÚľkK. ÄĆšrĺJţϝ†* ֚Œänbjˇ.ěvQ÷7˛ýýý°Űíӌą RŻĄh/Q,űîĽK—˜ž[y@őÝvĹ-„WkAB€Ü°3š.D’¤%Ѣ2ĆĆƖD—Œ´##JŚuő‚ÉT§űš!‘H—.]bąXěťJžŽŇW×@:Në˝VěöFľ— *jœŐŠ„tvv.Š),˛$*Çĺr-‰.ĽˇľeDís…V0Â5c``ét: @ŃÎEE„1ąX,zŸ)fs=ęŐ^†Şd2YUŚ"֊„XrQ,n;%6N᜖âÂß]ťvn˛­š2&UžŞMCC=Ěfý_/`ąXcŠŚ5Ď7DŁŃŽĂ2Rëľ"NgŞr­% éîî^2‚ź°—Ř8>úč˛Éľ“““KÚyԑ‘ššy¤Ó™Ş˝žV1ÂľÂď÷#"ţPé×R\Dcᖖ–Ÿę˝hęëMşĎůń@’ŇH&•?šŐ’„Ďś¸uëuĘp łłĎ?˙|>â166†óçĎăÜšsKDŻXB5e$™L)2‘Yoˆ˘őő&ľ—Q1ýýýhiiůŠR-ť…TĽsffćťFpŁ šń¸¤řI痿üeMHť¨R4¤r}ôQ>|8˙ďzíÚ5œ?>/xĂĂĂůű r2ňË_ţRŃא¤4âqŞe2J—Ľ<ŔlffFŃ"U™ŞˆˆQœš7Z­nˆWŒŇ4O?ýt~ź‘qš\K˘!o˝őľërŕŮgŸ]"!CCCËS<Íֈƒă8 Ř°2€:d ąŮŒ1wJéfĹT­'Up Ťő’ŠaľZ zăÁělBQ‘÷ŚŠd ŤÖ)”ŮŮY*PĺŔöíŰó“SÇĆĆV”ůgˇnÝĘ]Ř:­w,‹b{ÇČd2YĚÎ&;žž¨Ż7é~xƒAĹ˜S5aŒÝn÷ˆjE ŠÉ'ŤJaŒav6ĄčÖޏ§â}i´JńP­ĄĄ!j×ĺ@a˝GĄhŹDńîźFˆŠČűÇ()!Ůěfg5ߌ+c”kB?Ün÷ˆ’̊Šę”ŽŠŠ)ŸQ˘"fs=Ž.b2ŐĄŽNŮp¤źSokkŤ˘ŻSm Ł!ccc áDa;îziŽŮŮŮ%2˛gĎ]ˇóśśś.ŮQW)ęꄚô(#ŠfC´ëĘŃŠŠ)_5_ˇŞď"ŁEE¨p5ŽtšlUůwe¤˝˝]ńת;wî\˛ńÚjé˘2J‘ŠááaCěCÓŢŢ^ rőr.—­ćÓÔF)Pԉ†UŔXQ#˝7B5%DFEôőőĄŤŤŤjŻŠ˘(b׎]ů݇‡‡Š]—#…Ý0Ľ´ĺŇßž}ťîÚyťşşĐ××WŐ’ăܐŞ TĆXđ3ŸůĚŁDEDŃ\“WՐBz{{uÝQłk×.j×U‰‰‰üŸK‘ŠÎÎÎ%b(IćçőÓ rŕŔôööŞňÚľ,# ĆIŃ÷÷÷ă3Ÿů̝jGCD>ţřă“F‰Š@ssŁ!Œ¸TԖÇƒ^xAwEŹĹű›źőÖ[T Ę™[ˇn-Iľ>|xILĆĺráđáĂ8zôh^ ÇĆĆpöěŮ%Q­bąXđ /(Z”Z ľ(#‚  šY˙űÉŁ!üńI5^_•[yĆXđ‘Géďďď2‚Œ˜Lu°ZÍH$´ąýś’hEBdÚÚÚŕóůpáÂŒŤ˝œ’ŘżţĎłłłT˘oźńNœ8 — ;qânÝş•–<ůä“KäD’$źőÖ[şů˙hooǑ#G4Ón,ˈŇ-ýZÁj5ÎŽěrmČýű÷ƒjźžj9…ĹZ‘7ƒÁ ź^ŻZËŕ†Í&"•Ęú¨5 ‘‘ëFdŤ×2ĹiJÉ(Çää$Ž\š˛Düśoßž¤]ZfxxXW;÷ôôhňźY+2R_o‚ÍŚ Ź”‚ófŐkCdTŁEE€\Šff&Žö2AŤRˆ×ëEGG.\¸ ÚčëQÜŽťŢŒ ˘2än˜;w.Ť‘$ ď˝÷†††t3ÉÖbąŕȑ#čččP{)ŤR 2b””  ~4PQDăEEr–l1\І‡„0ư°ŔevttŔçó!ŕΝ;ŠžVšˆ˘ˆááá|Ą*ECŞĂŘŘĆĆĆŕršň­źóóóş¨)d˖-číí­J*&›]@]°áĎź‘eÄf3Îdm-DC•EĈQŁĽhxIˆ<}ľm˘(âȑ#xűíˇ 5‘$ CCC¸uëśoߎť ĄŢ™ŐM䣋ů׋;vTĺőäąí&S]EŸ}#ʈ‘R2€6˘!€J]3…LMMő޸q~ż_íĽpĂ(]4<%$“É.ůs5ŘącNž<ŠšiŹT J”Jkk+Nž_Շ“ĽR¸|V.#őő&45§U|>ěvűôÔÔÔŸŠ˝–B4%"‹ĹžyůňeĂlˆÉUkřY18yň$8@­DŮ88p'OžTm“:IJWísjŠŻ7.%ŁĹvÝb4)"@ŽW„ˆŃR4ŕp¨7_D/RřZssóޤjd<|> Q˛€ř|>x<UÖ §bćććŤňš1‚Œ‚‡Ă8óBd|>AˆhŠ]ˇ͎ţdŒEAřăÁÁÁK}}}†ŘWĆdރĂaE$’¨ęëęMB IĽ`f& ‡ĂŞÚ‰ĘăńŔăń  áíˇßĆ˝{÷TYĄMZ[[ącÇŐäC&“É"M"›]¨úëÎÎ&tťQžĂaU|wđj 188ŹĽvÝb4+"Ŕ <ňČ##>ŸŻ+ Š˝ޘÍő°Ů,UëŃł„Čdł ˜™‰ĂfUÝËG’p8Œ`0ˆńńqŐÖB¨O{{;ź^Żjé—B‰ׂÔrŃŤŒŘl˜Íšžnš@őţýűľ×˛š˙—ŸššęššúÍŔŔŒ–ŚąŮDd2 ŠĎÎ0‚„’HHHĽ¨~ÓŃсžž>Ü˝{ożý6FFFT[ Q}şşş°cÇU P‹Éf&5ńůԛŒˆ˘6›ś:y ¨Đdj!šĆXŘétţŕĺ—_ţż{{{-Üu𤚚łł Š}،&!2™Lv1:bQ}rm[[z{{ąoß>źýöŰ…BˆFŁŞŽ‰P‡ĂÇƒ;v@ľqńJ&SH$RŞŐP­„^dD‹cx‡ĺŐżˆD"aľ×łšˆD"ßkjjúcŸĎ÷H  éSŮČRĹŰoóŔ¨"ĂC<.!ÎŔnoT=ż+Š"ź^/ź^/nßžP(„;w&‚[ślÇăÁÖ­[Ő^JžlvąŘ|ŮcÚŤ…ÖeD>÷jqĐdĽř|>dłŮŠx<ţ=ľ×R şH$půňĺ7z{5i* “Š.— 33qnÇ4ş„’Ng4‘Ůşu+śn݊H$‚P(DQ"G?<OՐ­‡Ł +ĄeqšlŞßź(A Ŕĺ˗ŕÔ^KŠčFDcÁśśś;vŹo||\ĐډĄRäţőššy.ÇŞ ‘‘Ł#’ô@łZdœNg>J‡ …pűöm¤RŐcOŹĹbÁÖ­[áńx4Q|ZL&“ĹÜÜźn>—€6eDKçžD";vŒľśśúďŢ˝T{=Ľ˘€{÷îýG‹Ĺň­ţţ~ŃhSW\ŃT&ł€drăŠZ”BäÚŤŐ›Í˘Š°kGGGţâvűöíü/’u‘ĺCţĽEcH$RÔDK2bľZ 79UŚżżŠT*577÷Ő^K9čJDg‹|ëěŮł—z{{ 5[DŚŠIÄÂŰĐ4ŃZ—B’ÉćçÓ°ŰEMžt /z$%ŐGň!#IiÄb’ćÓ0ëĄEłávԕ ƒ8{ö,|KË3CVBW"äg‹űúúźĄPHsš[477"“É–őA# YŽ<•U’ ŠIť›X^ ďŢ˝›—˜Ć—ÖÖÖüżľZn×#“É拱‚š2bÄńí2‘H}}}pťÝA­Ď Y ݉LMMŒÇăwűűű-FLŃ(ëƒF˛6r1Ť(šaˇ‹šJ×ÓÖֆśś6x˝^H’„ۡo##Sąk™8Ž|:lëÖ­šiˇ]Ćb1Iľ=–”F ‘Ď‘FĽżż÷îÝKI’tPíľl]ŠČbŠćˆ‘S4Ľśő’„”Ž$Ľ‘J=€ŐjŐjÖ´šv`škČÝőČRr÷î]Š˜ŃÚڊśśśź|č-ZĘC2™F2Šýn˜JŠŚŒšMX’’9˘ˇ”ŒŒ.Ex˜˘yţůçżň‹_ü˘No'RŰzWŰLŠ$¤|rEšťM›M_EkN§s‰˜H’„ťwďćĹäîÝť55q8ůčQGGÚÚÚtńX IJ#‘HU}5ІŒČ?7b›.ť9yţůçÜn÷?ę1%#Ł[r)‹Ĺ2éóůDżßŻörA–b! ЌlvssóH$RşQ—tâĺäîÝťˆD"ů?ëľÖbąä…Ăétć˙Źgé(¤¤%eDíÝ|ŤĎçĂ'Ÿ|’NĽRşLÉČčZDä.šÁÁÁK˝˝˝†t&#!–gĚLƒť!IDATŒ„đĂBRČJr"‡Wü]MQ‘E@~ÍĹż‘ZB”’ť]ťę<ňÎşşë’)F×"äR4Ft&#_ “É4IˆMHV˘” ź,'ŔĂčĘFéééÉ˙š8ŠadÉX •á-#VŤŮŸa™˘ÁeşMÉČF(ŠÁiłŮ~ýôÓO? Ő^ΚLMMÁívořůŒ1’*`2ŐVHxŇßßżäwbeH@JƒW´×¨…Š2^ŻďźóÎýD"ńťz†€!*xc‘D"ńďnܸ-ˇó^˝z§OŸĆřřř†ARäÉôtL{zÚCž†:=ĂÜÜoF—ܸq‰DâßABƒˆ0ĆB‡ă/˝ôBĄÚËYB2™ÄkŻ˝†‹/Č˝‘ŚŚŚŞş’‘Í. ‘05C<.ŃńX—lvńxî=“HĐ{Ś\xȈQ …Bx饗ŕp8~ŔÓօŽ #"‰Džçp8†űúú‰hG­V뒯“É$Ν;‡d2Y•×' М܌‡ÜÝm4šD*ő@í%#•z€h4‰ééXMĚQ’‘ĺČÓSÇp$ůžÚëቡD˘Ńč7ďÜš“ňů|j/e ‡B{{{ţëńńqźöÚkŠż.Ič‚CȐ *ÉČR|>îÜš“ŠFŁßT{-ź1œˆ0Ć’$„–f‹X­Vř|ž%ёááá|şF HB”EÁßż?GĄC–Ńű÷ç(e§ $#9ü~?!IŇĆXXíőđĆp"äZz›šš~xęÔ)ŚĽz‘•däęŐŤ¸yó&÷×" Š.…Q’x\˘w"oBGяęR-Žzí^)„B!œ:uŠ555ý1ŚűVݕ0¤ˆ@,ű€1­Ő‹´ˇˇăĐĄCKž÷ÚkŻUÔIS Iˆzdł H&S˜™‰cf&Žd’Z6ő ýjĽeäć͛8wîNŸ>ŤWŻV­~o=äşc‹×4Cbˆ9"Ť!B‡(Ў˙đ˙ĐR­4Í͛71::Šńńń%vmľZ—ŐˆžŮĺhIác6Iˆ6ŠŻ7A`ą4jß #ÎÉfJ=€$= Ď‘Ćŕ1g¤˜›7o.Ť×łZ­Řˇožyć™eÍŐ¤ŻŻŻżţzJ’¤­FLÉČZD@„^—~ôŁÉfŠSSS8wî\E‘öööeŠ› Iéü8xB{˜Lu°X Š şAmÉd˛¤HĽPÄCĂ477r2¸’„âvťóBRmü~?Ž?š’‘1έŮ*0Ƈă/žřâ‚Rő"ÉdőWUqze||œK˝ˆ(šŃÜÜhřÁ>zĽ0Ü˙ţććć!IişV‘lv!/ě÷ďĎQÚEă‚Ŕ]BŕąÇ[óçSSSxíľ×púôi s}íľ…BxńĹç…ZBě5S ‘Hä{N§sĎ׿ţő§~ń‹_Ôńޏćć͛ˊœśmۆƒޚjI&“ËÄejjŠ›y‹˘őő&Ş8×8Œ1HR’”űşžŢłš šßI&ůŔC:ÁƒY¤ÓJšč%wŃmooÇ3Ď<łäĐív/;ŸËďmŰśaď޽؜m÷ľČD"|ýë__¨ŤŤűŁÍ YšˆFŁ{ţľˇˇwďýhFGG—|˝mŰ6Ź7ÇÄjľ*úfćSIFôC&“]r‘”Ťžž ő†Ş/Q’lvdÉ,xč%%Dfßž}KDä‘GÁĄC‡pńâĹeB2::ŠŃŃQtwwăĐĄCíś˝˝˝ˆD"łąXl÷ƒk”š9Ť1Ć"ąXlĎĎţsŇŠ‘O\úzZZšt_‹PŤd2Y$“Šüž7ň̒D"…2”JŔCéH$RůŮňţ.ÉdŠ$D§TëÜĺvť—œłGGG‘L&ńýď‡Ząfoxx§OŸĆkŻ˝Ćľĺ×çóáç?˙y*‹í1Ę>2ĽP3"äöŁ‘$éäŮłgš;+śb­ő˘›LuŠßUՁ1†Tę łł‰źœD" Äă$)2†Œ€1ĆđŕA’”F<.!IäĽcv6DBB*őŔ÷ZCŽćV+¸oßž%ÂqńâE$“IěÝť§OŸFww÷ŠĎťyó&ž˙ýďsiůőűý8{ö,$I:i¤}dJĄŚDcţŚŚŚň,^-~“ţěg?ÓLşŒ hii˘mí ˆ\˙ GNfgshYPäŠ,)ZŽd2ŮźlČY8îߟĂěl"éH§)\ľŽ(šŃŇŇÄľ>j˝óąÜSřřŸýěgůŸ8q>ŸoĹTL2™ÄŋóßrqęâĐ2˙†¤S ßžťN§óĆŘĆÇÇĹŤ/^ÄŐŤWó_ójĹ~ˆxőłÇă’É—cúŚžŢ„şşÜ żĄáaɘÉ$,ť-üšĚZíťd–|Í. ›eË~ž°Ŕ4-GDő°Z-hjšóć͛¸yóćşçăd2‰Ó§O/‘–?ů“?YÖppőęŐe7›Ď<ó ž{îš ­/‰ ˝˝ ‚đn$yzCŃ95‘‰FŁ{L&Ó¸×ëĺ2yußž}Ë– ,+d-ycź—^z‰ëyMM"š›šĐ/™LŽ‹$Î ‘ňżäČJáŻr)~ţÜÜü’א_—$„r3B”yrőŔŔŔš‘yˆY!—.]Zö¸âtMĽâőza2™ĆŁŃhͧSłÁcąXţϑ#GD5#ÉdËÚrˇmۆçž{Žä ë›7obxxx™ÄěÝťwŮxřJ  ĽˆrřÔ§ËžˇVDä“O˘ ݈0JuĆ++ezőJQ‘çž{nŐ„ńńńŠŚa÷őőá… R*•úR­Ő…Rł WźšJĽ~ppËdHů^řŚ-UBŚŚŚpőęŐ|ôcĽH ď ň¨Ł† 5Qę´ ‘W‘ WW˘ éďďÇŕŕ RŠÔďײ„54Gd5cAAŽŸ9sćGˇZ­xîšç°oß>ŒŽŽŽŰĘ;::Šááá’ăć͛\ۃ厚X,×qAQ DŃ ť]ä>´oŁ"łwď^üă?ţcžűQ.\ĺöűý8sć gŒšX§ÔtDDFî¤9~ü8xuŇ÷Ś’L&qóćMœ>}+JHń&y2ôGÄe}…Čă“m6 ÷cAcłYنbĽ˝c:Tvä˘8*rőęUn;¤‡B!?~ľÚ!ł$"‹Äbąď´´´ü´§§‡)ľ'ÍÔÔ.^ź¸ć ŤŐŠC‡ÁçóáţýűK~ś‘T9Řl"œNž;[AČ‚§Ó›oQ*°úv/^,["žyć™eéô• WË% Ą§§‡ľ´´ü4‹}§â„šOÍ233óďm6Űżýú׿ţĎ=iäÔËZ4Ĺť<WxˇˇˇcďŢ˝\Öłfs=ZZš&Š› nÔכŕpXRśÖ.şrA9é wăwîÜšüוŽs—÷Éfł˙:77÷ď+:˜Á ˆHŒąH"‘ř˝x<ţ!Żś^ WY˝š„¸Ýn<÷Üsřţ÷żŸ—ŤWŻ.{ź)™Ő0™ęhřAܐ‡”UCBä:˝Â™!Ťu4Žô|™îîîü~`•´čŰtăńř‡‰Dâ÷ji|{)PD¤ĆXD„ƒ‰D⟼^o#4ÍĄC‡055ľdéâˆĚčč(.^ź¸ěůJŚdVŁššfł ą˜D-žA” °ŰEEojşťťqóćMŒ/)L}ěąÇ–D–WŠŒČłšdŠecďŢ˝ů›ĹJđz˝O§ÓIB–C‘`Œ…Ňéô—oßž-UÚE#óÜsĎĄ˝˝}ĹˆL2™\fćŐJÉŹ†(šiŸ‚ ĘFŢ/FéČŞ,ňŽç˛dŹ4Ýş82RŘ( ovWˆ<~Ąúúúpűöm)NšÖŰtWƒ""ŤŔ ‚đĽÁÁÁPń&yň‡e­Ă+°V3%łň %‘HŃhx‚ ÖE‰Qíkż^îüZŒ,#ŤEF ŁÔĹéôőőappjz`ŮzPDd ß8ÇW|“—ËZoryšj!jĽdVB45‰p8ŹÔUCĊ‚‡ĂZU Yľ"#˛œ¸Ýî|=/|>Ÿ,!ÇIBֆDdűźŸ={śâ¨ČjČm˝…¨’Y ‹Ľnˇ}Ĺ Đ‚¨]ęávŰaą4¨˝”eŹ<ň××óűý8{ö,“?׃‘`ŒůŰÚÚţĂńăÇ‘‘sçÎ-ËMüđĂđb{nPí5$"šAî¨áÝŻ|ĺ+ TÄĘšŽdÓ&;+,–ľ—Dkbą4Ŕá°bÓ&Ş˙ŕ‰ßďÇWžň•AŢĽÎmA"˘!c‘H$ň4cěm˜ÇŸĽ'xŠ’ÚAŽ~0+ƒźqcěÜâ9– ň4‰5ˆ\ÄúꍯŚh+r'ý‡QڌP Q4/‰~óEŽyőŐWS ˘TÍBďzÂóK’´ăÝwߝŚIŹĘaą4äkI¨ă†¨rç‹üžŁč‡2ȓRß}÷ÝiI’vО1څDDĂ0ĆBąX쉹ššĄşe‘[€].6m˛ŁŠI¤â@‚őő&45ĺ˘p.—ZoFŽ™››ű—X,öՃh#׍ÔŐŐýˇăǏӼ‘*`2ŐÁjÍí×ARBl”Bů÷I˘Ô‹˛ČóAŽ?Žşşş˙Fő ú€>:!‰|ŔÁ×_=ĺőzAŠšę°’”Pú†X††z’•…Bđz˝xýő×S.ž3 @ŸÁ H’´ő7żůÍ===ŒR5ŐE–—Ë–ĎďSˆ˝ś‘Szr͇Ëe#ůPżßžžö›ßüćI’ś2Ćj݉(ú´č ĆX8>ą°°đ*ĽjÔŁřätć.@”Â1>őő&X­8$¤jS˜ŠYXXx5>Á Ť˝.˘<(ĆŹSbąŘwA¸úúëŻ_…BżßÇŁö˛jłšfsîăÄC:A*•Áƒdł *ŻŽ¨“Š ő°Xr˙Ç$Ú  ĄŻŻwîÜI8‹Ĺ( ˘S("˘cäTM8~ꊧ000 ö’ä˘%r[đŚMöüf|˘hڐ˝0™ęňŃŽÂ˙?‹Ľ$D# ੧žB8ŚTŒţĄˆˆÎY C>ít:˙üĽ—^úmœ§!LŚ:˜Lfˆbîk9b’Éd‘NgńŕAFÝÖ8 ő0›M¨Ż7QÄCăD"ôööâƍp8? ‚Tc@ˇgańůÔ;ďźsż˝˝tƒ U䈉Í&Âĺ˛áSŸr ĽĽ Í͍°Z-Ô•Ł ő°Z-hnnDKK>ő)\.l6‘"' ˝˝˝óÎ;÷EA ŽPkáÜZE§Űíž455EŃ‚ 4GQ$855EQD¤ĆĄ×bąüÄbąX…ŢŢ^ľ—DDpěŘ1–Ęń-š R[P~Á ¤RŠGĺΚŢŢ^„Ăaľ—ED ‡ŃŰۛďˆIĽR’„ÔŠaAđÚlś˙i2™ÜgΜ|>ŸÚK"˘FŔË/żĚ˛ŮěT"‘řĆXPí5ę@‘†1ŒÇăŸá/^zé%x<ƒAľ—E„ ƒđxřŕ Ač“Bůŕƒî€„PBcÁ>úh+(BBşbĽČG}´•„P BQVKŮ4ĺ™ ´D   Ą $"DU(’÷ßčŕÁƒ4‡„ 4€<äŕÁƒx˙ý÷‡@BTšŹJ¨‚ ---˙}ffć‡>Ÿ>ŸN§SíĽ„á‰D"ŔŔŔ˘Ń(ZZZ~:33ó]ę€!ԀD„PA:ÇÉT*ĺ3›Íćƒ ýýýčččP{ia8Âá0úűűqéŇ%–N§Ó‹e ţ„PB‚ŕĐkˇŰ˙{,ŰÔÓÓŸĎ‡ŢŢ^ľ—Fş'```7n܀ÝnŸŽĹbß )¨„ B0Ć"Œ1˙ÜܜEu$´ă/A”‰źnqýÇÜܜ›1ć' !´EDÍ"BGkkë˙Fż-I’ĺŘącčëëƒ×ëU{iĄY‚Á ü~?!ŠbĘápüí˝{÷ţ”Ň/„V!!t }.—ëĺŮŮَöövř|>ôőőQq+A ýđűýŔřř8\.Wxvvö cĚŻöÚb=HD]!‚§ľľőE9JrŕŔôőőQ- Q“řý~\ž|š0úń c,¤öÚ˘THDÝ"B_GGÇwÂáđúúúĐ×׏ǣöŇB1BĄü~?ü~?˘Ń(:::ţ9˙ E?˝B"BčA:6mÚô­t:ýŸbąŘ&9uÓŰŰKmŔ„!‡ĂůΗńńqŘíöiłŮü—ÓÓÓ?ĄÚB†BNÝD"‘oĽR)ąŤŤ+Ÿş!)!ô„,~ż###°X,’Óéü Ľ^ŁA"BAz[ZZžK$_#)!ôŔJňałŮţaffć5ĆmĐD˘& )!´ ÉQ됈5‡,%ŮlöŤŃhÔŐŐŐŻ×K…ŽDՐ NƒÁ FFFŕp8fM&Óu’˘!!jA/aUHD„ VA„ŢÖÖÖŻgłŮ=SSSŸľŰí _ýęWëd)Ą4QH(ĘËÇőë×bąX]AÔă*Ľ\beHD˘Ł%ޖ––ýrmIss3Ű˝{ˇ K ELj‹`0˜—7ß|“ÍÍÍ ľW)ęAëC"B@“Í›7ŠĹb_ŽĹb› §§…bB5&Ć ‰,7něvű´Ýn˙§‰‰‰‹ ń ˆ A"BXOSSÓŢŚŚŚgîŢ˝ű$´ˇˇçĽ„˘&úANą„B!„B!ŒÚÚÚދÇă7ăńřU!‚¨‚PAź<›7oţjaÔD–YL:::h–‰J„Ăa„Ăἣ Úq9骺X‚0($"Q%Apđ`QNćçç˙íěěl‡üóžžx<8NÎ G$A(ʧWŔĺr…)KrâQmÁQCˆ„Ę‚ŕЁœ ěI§ÓMMM}VţyWWW^NœNg^V¨kg)ĄP(/rMG$ɡ΀ŰíţČl6811q 9áÓ ‚P‚Đ(‹‚âŕmmm},›Í~!•J}FNń€Ýn_řžPW(&˛¨IVdšEŁđ{ďžűîB,Ť“kˇŰ§-ËÇ&“éÝ{÷î}  BÂAڄD„ tČbqlţ—,*055ŐUüxYX䯋‹fW*˘ĺ)2˛4 WýşX0dÜn÷ˆFXţEĹŁĄ?HD Č đPZĐÔÔÔ*Šâ—äÇełYGa­J5qš\a“É•ż–$é˙Äăń{‹_†$aXHD‚X‘"‘Š ‚ Vä˙}ˇĚş“é€IENDŽB`‚rusty_paseto-0.7.1/assets/RustyPasetoGenericArchitecture.png000064400000000000000000001212051046102023000225450ustar 00000000000000‰PNG  IHDR""ő´Ł* pHYs  ŇÝ~ü IDATxœě˝p“ç™ďý˝m„mY’ľq‚%œĐđcśRpvÓ]C,2Ě gBŠI6˛b坝ŔvćwĎöÇöœĹÝ´çôœí'3;M˜ĚÚ Ůmrh€ŚaJv˜bphwß7“68+ŠM–mɲ$űąŢ?äGHţЎô<÷Łë3“Űň­{ÂŁűů<×}]×Íâń8‚ fĂł°sΏǽœĆ"BC0‚Đ&łD"ů÷ŞŞŞZƒÁđ§ňë$I2;ŔbąxËËËGĺŻ#‘ČŻCĄĐ­™/˝3˙$2ĄYHDB@R$ĂŔ^[[Ű IŇfđűýŽŮŻ7Ó›7o.“żvš\i?Ÿý5˜Íf8N.óíëëĂČČȜďwww/řőŋ§ƒÁ`faľZ/@yyůĹ[ˇn]Ç]a!Y!!!•Âs0pɢFď ƒ+ĺ×Ȃ‘* N§fł™ŤH(,2###čëëKűŢla1wôzýQé0Çű”™=A‹A"B 3#vÎúúúíąXŹÁď÷Ż‘îp8`6›árš’rĄ%Éŕ…,&ňŸÝÝÝÁĺ˗“ŻąZ­Ÿëtşë7oŢ< ‰( A(‰A Ƙ€ áxtbbâRs3ZZZ’’árš`ˇŰaˇŰŽČŻ× Ż×›”“žž>œ;w.ůs‹Ĺâ]ąbĹonŢźůK$Ľ/ĎÝK"‚;$"Q c.ĚHG0ü3yKĹfłÁétÂét’p(LŞ ôőőĄŻŻ>Ÿ@b‹Çh4ţJ–“x<Ţ­čd Bِˆf’GUUU;ŞŞŞś mîJ‡ËĺJţI¨YLä?e9ŠŤŤű$ ő„BĄŻ˘% @"B90#Žúúú'RŁ---iŇa6›'Á9çD–y['%jr@7‰ Ad‰Ad€,555ť$IztttÔR]]ßśmŁhGi’*&gϞ1“É(//˙ĺđđđű 1!ˆŒ !ˆ`ŒľÖÖÖ~M’¤í~żŃhœ~ôŃGË\.W2ęA2˛”tww㗿üĺt0,łZ­Ÿ———ŸšuëÖ{ńxü¤Ňs$5B"B3ĚD=Zëëë˙üć͛Í@˘tśľľ˛|DŚČRrňäÉd q}}ý…›7oţŔIŠ–D˘¤aŒ9ŤŞŞö †Çü~˙šęęęřž={˜ËĺBkk+ĺx\Áɓ'ŃÝݍ'NÄÇĆƘŐjý<‰œ …BŻQ/˘”!!JĆXkMMÍłrއĂá€Ëĺ‚ŰíŚí˘(ôőőĄŤŤ ÝÝݸ|ů2RrKޢ-˘Ô !JY>ĆÇÇ‹FŁ‡ĂˇŰÖÖVęáA(Š×ëĹɓ'ŃŐՅ˗/CŻ×G*++O‘”Ľ‰ĄYH>Ń )!JBS0Üľľľ/ŽŒŒÍ KIGG|>ŒFăN÷wîÜů UߢC"B cĚmˇŰ˙Ęëőţ‰Éd‚Ű팄SBóȉŽ]]]…Ýn˙˝^ďăńx—Ňs#ˆ\ !„BŢzý‹H$˘ß˝{wrë… J yëćg?ű CÔd2ý+mݢA"BcĚmąXťźőâvťŠĎA ѧ¤ŤŤ+šucąXź@ŕť%!D€D„P-Œ1{mmíßÉяśś6¸ÝnępJ‹ĐÝÝŽŽ.źńĆŠQ’ż§\B­ˆރ1檯Ż˙Ţ͛7›)úAš1;J2Ó^ţżĹăńnĽçFАˆŞ€1fĐj4 Wś´´$+_‚ČšâćÜšs0w‚ÁŕE⼛ĽçF$"„˘0Ćě&“é@4őčt:ݞ={X{{;•ÝDđz˝hoolj'âąX,Ś×ë;FGG_ĽmBIHDE`ŒŮkjj~8<<ü¤ÍfƒŰí†ÇăĄí‚(###ččč@WW|>jjjŢţŻ$$„ˆEevţG{{;ÜnˇŇÓ"ˆ’ĽŤŤ ííí”GB(F™Ň JƘkŐŞU}ÎŽ[ˇŽůĉđz˝$!Ą0nˇ^Ż'NœŔşuëšœ]ľjUcĚĽô܈ҀD„((ОiÓ&ÇŮłgŃÝÝMI¨Ą2Z[[ŃÝݍłgĎbÓŚME‚D„(Œ1ך5k>Ĺ,Ą Ąn\.×!Yłfͧ$$DĄ !¸’šďžű !1I’űîťďP„„($"ڂ!!ą™/BBBBđ„D„Č Ƙ„ ´Ď"BbWxj„ŕˆ9ÁłŻ\šň§~OBĽĂŸy<Ľ§E„đx<đů|ŹľľŐŕő!R!!0{ŚŤŤ‹’Q ‚ŕŠŮlFWWלíĽçE(‰H ĂsUUU}AŰ0A‹ŮŰ5UUU_Pťř҆D¤aŒ™ëęęţŔŮíŰˇŻş|ů2mĂQT<._žĚśoßž ŔŮşşşŚdÖ҄ĘwK ĆXŤ^Ż˙‰^Ż×żńĆŹľ•ňĆ‚P–“'O˘­­-MđL<?ŠôœˆâA‘1f^ľjŐY'8`˜ISzZAhmm…Ďçc0 ‘Ěz–˘#ĽEDJĆXŤÁ`xťśśVßŐŐEy %N<Ç䤔üzrr*í繘4űWKĺœďľˇˇ§ý™J 0>ď8:]yÚ×˗/Ků{9cóţQtwwĂívăÖ­[ŃH$˛—˘#ÚgŮŇ/!D…1fśZ­'¸öďߏöövކŃ8SSŚ§ă˜š’Ç!IqHŇ4€šÂQhzżšßÎű:YPĘËËP^ÎŔòeĺ(+KüIh9™ľ˝˝]˙ňË/ŸXľjUˇßďߏÇG”žQHD4Šœ ˛bĹ ÝŮłg) ˘!ŚŚ$HŇô̟ńäßľݔ…errţŸËb"‹Šüw’ń1›Íččč@kk+ž{îšGôzý cŒrG4 ĺˆhŒ”Š˜{÷î5|üńÇe$!b25%!ÄřxŔ8îÜ â‹/F1<ÂčhăăQD"1LNNiNB2!ąĹ4…H$†ńń(FGĂá‹/FqçNŔ8ĆÇ#ˆF'155˙vĄn\.>ţř㲽{÷œ ĘmB ÁsVVVţŰÄĄőĉ dTq˜š’fţ›Ćä¤Tôm­!IӐ¤é9˙—/_†ĺËËąlY"rBŃő#7BkmmE[[›ť˛˛rcě˙ŠÇă}JύŕED4ÂL‡ÂK=ôĐ*ވQ7ńx<-Ň!G9ĆĆ&GIB ČääÂá(ĆĆ&’Ń“ÔČI)F–DAŽŹy衇V¸D]Yľ‰ˆŕ0ĆěfłůŁŃŃŃo>|ÝÝݔŞ2$i‘H cc¸s'ˆŰˇÇ’[+$Ę399•ÜÚš}{ wî166H$–Lô%ԁŮlFww7>ŒŃŃŃošÍćčĚń!˜™˛ÜOív{ÓĽK—@ÝQŐA<Oşą‰Ĺlq”˙ý(b˘<.]şťÝŢd0>Ľ}ņDDPŒFăœxúé§őÝÝÝp:JOФ‰ĹŚ E0<ÂíŰc$B9b2<B(A,FŃ,%q:čîîĆÓO?­pbfM$„DD0cv“Éôť˛˛˛ýtRŽB¤F=nßĂČČ8Âá(Ug”SSÂá(FFĆÓ¤“˘%ĹGNdíěěDYYŮ~“Éô;ÚŞˇbÖŽ]{ßšsç˜ŰíVzJ%…$M#Ž" ‘`ś !ĽHXqqťÝ8wî[ťví}´U#$"‚0“!N[1EF–ááîÜ "ŠP‚)ą ““‰-ş;w‚‘”‘Ů[5TU#$"*‡1f6›ÍMOO˙ mŇů䃜\ˆl™š’HJŠLęVÍôôôßĚTŐЂŠr¨Ą™ŠaŒ9Fă™ęęjË{ď˝WFQÂ!÷ö˜˜˜¤ˆÁ„”$ÄdůňeXąb9ôúĺtŔ_pťÝp:e_űÚלžžţcl;5@S/Q)Œ1ˇÁ`ř÷͛7ŻüřăIB D4:™Üß› ! ÎääTÚ5.p˜‘N§üqŮć͛W †gŒš•ž1?$"*dŚ ­s˙ţýzjPĆIšĆřx"d>:F$SzJD‰"—ßšÄřx„śn8#7@Űżż@'•řŞ!çƒ0Ć^čěěDGG‡ŇSŇŃčdʢOűő„zHČq4)Ç%áKGG:;;Á{ňFÔ刨ƘÓbąœ$ÉvţüyF[1|ˆÇ㇣ˆD&I<!ˆF'N˘źź ĂrTTč)—„rŢHKKËf‹Ĺr‰1ś‡ňFÔEDTcĚĽ×ëÝĐĐ`÷ů|$!˜š’’űđý DDŽ’Čš$Tš•?N§>Ÿ544ŘőzýŻc.ĽçDˆ(ÎLŐŮ˝{÷($"‘q ‡(÷ƒĐ ‘H ĂĂ!ăt]牜7˛wď^€ł”ÄŞ<$" "'Ľ:tˆúƒäÜÝR>œŒ*_­"WÜÜš¤Žžy ÷9tč@IʊC9" Ŕ3×ÔÔź …žěěěľjĎ 9˙#Ś™(-$icc`,‚Š ĺ‘äH{{;ěv;žţů+WŽ\5<<ü—ńx|Déy•$"E†1fŽŹŹüŠŠŠu—.]˘Ví9@B âń8ĆÇŸ’ܘIbEKKË•••Ä{˜d¤¸ĐÖL™ŠŒšTSSs˙šsç()5K$iĄP~˘ü–$„ ČBâ÷'Ž$ äěěp:8wîŤŠŠšŚ˘†ç"B"R$cNN÷Ť††;uJ͎Ä"›h@“€ÄBČŃBšA}V2GîÄÚĐĐ`×étż")$"E€1ćÔëőżŢ°aĂ ŞŒÉY@äA™#GHHH2GŽ¨Ů°aʙň^’‘"@"R`fJĂ.íÝť×Đ××G’ł„Q‚ȍÔ-’Ě0›Íčëë“Ë{/QyoáĄdŐ2swśľľĄŤŤKáوA$C0H &Ađ$5ŠŐh4Ŕ`Đ)=%Ő#ŻŮoźńF'c ńxźKŃ iŠˆˆŐŤW@çÁƒIB2 ›Jö! !ˆÂÇ“}Hb1ꡳ]]]8xđ tÎŹéD )FŁńÇCCC˙D×-ÍԔ„@`##ă”éOEB’Ś122Ž@`œZÇ/|`ŢĐĐĐ?QăłÂ@[3œŠ¨¨ř׉‰‰g¨QŮâÄăqƒjWM 299…áá ŒFő Yy-ţůçTTT˜Âáđ_(;#mA"Â’Ě‡Ł”„J*"‰!DeĽzĽ§ŁJRd䙊Š ŒđƒD„$!K‹M!ŠP(˜ TH<G(A$2‰Ş*t:ş=̆d¤0ЕĆ’ĹĄm‚‡Š) ##ă´]ł$#üĄdŐ;ďk}>Nœ8ŤŐşŕk2ĹívŁŻŻ/żür'c $#‹C"˛Œ1'€Îśś6ާč~đÁóJČłĎ>›öĄ9~üxZ–73öôôŕ…^Ŕ† ¸Íi)äŒy5‡` ‚P–x<ŽŃŃ0**ô¨ŹÔ+™ŇÔÔ4gÝ ‡Ă8}úôœČIž2ŇŃс‘‘źńƝŒąžx<ޗ׀†rD€1ćÔëőżnkkCWW׹ˇnݚśĺ"K€´Él Iýţ‘#GŠÖkD„}`‚ ԃňÇzzzŇÖČůT{zzđď|gŽ„đę3ŇŐՅśś6čőú_Ď<Řó@"2Œ1§N§űŐúőë ź%H| <l6[š„ČĚŢvąŮl Z|Ą‰DbŠ/(Aˆ‡üŁD‹řůÖÇÔUŸĎ‡ŽŽźőÖ[ó>đmذMMM\ćŇŐՅőë×t:ÝŻHF懶ffÁ3[,– +şťťšűÖ[oĽ…eЍ¨˜óZ›Íżß­[ˇŚ}xf[zoooŢáĂĹ›ĐÔ9AšE|,&ĄşzEŃŢwv4DNP]h&őuO<ń7 ‘éîî†ËĺZqýúőŒąăńř×7‘cćĘĘĘ˙¨ŞŞjčîî†Ůlć2ޜ˜ę÷űÓ"óIěŘą;vě˜S1łuëÖ´n€ mÝä‹$Mct4LQ‚ ¸‰Ä05%ÁdŞ@yyańóECžxâ ôôơ̂w$dçΝغukAćd6›ŃÝݍŻ|ĺ+ ąXě?c“ŒÜ…D$…ššš×§ŚŚÖ˝÷Ţ{Œˇ„wëٗ˛íĹJvÇÇǓ_Hdň!›Âčh˜Şb‚ŕŠÜüĐdŞ€NW¸[|L†ŒŐjEOOϜę ą†îÜš3Łr^żßŸWľ˘ŮlĆ{ď˝WÖŇŇ˛ŽŚŚćužó`ƒDdŁŃřăP(ôäĽK—ŕtňŰƛPzüřńœĂ~ýýýiăńŽG¨C*A#уh••zTV¸ď÷űçTĘřýţ9ë°, [ˇnÍčîƒ>ŔéÓ§ŃÔԔ×v¸ÓéÄšsç؃>ř¤Ńhüq0üŤœÓ$"Hö 9ĐŮŮÉUB`Ϟ=i&.ŸymĐď÷ăȑ#ißăF”÷qŁŃI.ăA,Ćřx’‡ŃhŕZâťTGęl¤żż'NœHŽ+Gˇó•‘ÎÎN<˙üóc˙A=F¨jŒ1€ÎC‡qmX&cłŮć„ý˛­vńů|řţ÷żŸnœ/‡$$iŔ8IAEEŽČ“¤inc.ąŘşu+žóď`ǎKJH8ĆńăÇŃŃё&76lŔΝ;óž§ŰíĆĄC‡ sćTҔtDdŚWČ/öî݋ööö‚˝ĎΝ;Óö-ý~?>řŕƒŒö%çK°˛Ůl\l’Ëë(„ %óF,–J,[VÎeL9R,WÎŽ>\ŠŢŢŢ9e˝łsI䢁|"#íííđz˝xűíˇÁűÓRnxV˛"’RŚkŕŐ5uĄłaäF:ŠĽˇ§OŸ^4<‡ńÖ[oÍ9ŹÉfłÁăńä=×H$†ąą‰źÇ!‚ȇx<Žá᪍WŔ`ĐqS–‘†††Œ#Ç~żo˝ő֜¤V9Ę"ŻŐłŰ(ä##čëë3”zYoɊˆÉd:#I’W™Ž\ÓÔԄ'žxbŽ}oÝş5í܃p8ŒžžžŁ"Řąc|>_2ŃjëÖ­xâ‰'ňŽ– …"Ô%•\ űšJ<ů]ť’üţ`ô6nĆŇüŢýŇ?d5ö_ő/íëzŤőŤ’_oŽŢ¨ZV/W¨ďpG˘¸ŒM`jjUU|’XłÉĄ““QgWŰĚî)2[Bzzz°jŐŞœѓËzm6›Íd2đPN NIŠˆŃhü1cěÁóçĎs)ÓM-ѕecž’°Ů‰ŤKEEl6žóďŕřńăÜ:ýQ“˛Ň!(…qm܇ 4Žk㞤\„ŚĆq-|˝ŕďi,ýÉňŇěüářěď`]EŞ–U&Ľe]Ľ Ćňʙ?ů—Ťę"Žbz:^´ćg>Ÿ˙ň/˙2'ÉuǎŘšsgÚÚ<_Űwš;v>˜Ífœ;wŽ=ňČ#–j%Mɉď ™ăǏĎ9ŔNNt:ţ<ž}öŮd39qUîęˇTTHDFxtOÇăÔŞ]ŁČÂq5ěĂľq/nĆü¸6îCH*LĂťB" ŇiPU^u•6ÔëŹXWiǗ+l$(Dn~fąTüĐźÔ‘2gΑ IČBÝąłĹétâ•W^)+ŐJš’ƘÓ`0źş˙~n2;wîDee地ŘsěččHŰŽ™¸*׌çÓ(g)¨SŞvJa\ť‚kă>ôű1˝Ąhq?TšĆĽąţ„¤řďʝ>=i2nŔşJ6Wo$99‘žĐX凼ԇÉŢŢŢ4)´„ȸÝnôőőáľ×^{ľÔNëeĽR1Á3ĆßmŢźy%Ď3ddü~?NŸ>=':"“šu=űžďŕ;^PeŒŘ\ űpqŹ×Ć˝č ö+*ż?ç{rľŮ|Ug?ßUŕ-LŢ:#&vlŽŢ@9(‚ÂăZQłŠŰëŔÝ5šX’ŠËĺÂŋďƒÁűK%yľdDÄl6T]]ýŕÇ\ĆŤ}ű|ȝýfWťČČ Pł[˙űßç! Y<.Ž]ÁĹą~Um݈$"łŠ*ŻŔćę Ř\˝‘ÄD0”’‘ 6ĚŠ )´„ŔČČžň•ŻL])‰äՒłŮüƒéééż9ţ|ďΊ Ńßߏ>ř`Ţó €„¤ść‰Db#$!*'(…q~ř#\ť˘xÄc)D‘ŮČ“ÍŐńHÍC´•Łrc0 ÜĘ{bśŒ¤R ‘éëëĂ#<2]VVö###ß*ř*ŒćsDc­žYˆö틱aĂlذ˝˝˝8~üřœłf-ˇ}çŃ-•z„¨›Áčmœ ôâÔísEŠ^!ć2őăT´§ü=ŔŔkXWрÇVľ ĹҔVbL¨ů •‘gŸ}~ż_‘HH*)ÉŤßdŒý{<?Y”7VM‹cĚn0Ţ~úé§ Ňž=šššĐÔԄžžœ>}zŽ€¤ň駟ć-"ńxá0•çލŤaNÝ>sTő(UŽ…ŻŁĂwžŁ¨Ó[Ńby­z„śpTF8ƒ^żź`Ő4 ŇŰĐĐP4 ‘qťÝčîîĆ;ďźó6cl}<÷uEDÓ"R]]}fíÚľz^SóaëÖ­iB2ťÂfëÖ­97ĹIEŢOĽR]ĺ!ů“Ą¨ď Ć;C§IJTIJeĺ-é/15őgů4.˕™ÎŤúß˙ţ÷gÜ_Ô7/"šŁŃř㲲˛ĆŽŽ..Sy wKÝşukň €~ɈrĐś‹śH•ÚžQŽBKˆĎ盷:ŚĄĄ!šm^l ÍÎşşşĐŇŇҨĺfgšLVÉ 9ŃŮ٩ؖL&řý~ôööě§&fĹă\ŕ#œş}çóWKi-%ŤćË#–&<śę´XJ˘¨AQ -!2Š‘ÔœYD”¤ŤŤ Ď?˙<ěŃbžˆć""jČ ÉŤŐZPËŚČHaŒŢĆŰC§qęöyU•Ů…ç| ç˝¨*ŻŔcŤÁŢş%)ŒŕîŮ4===i‰ŠJK ý|͉ˆÉdúŠÝnWE^ˆ áO)D?ˆĚIáäÖ EIřRL ‘‘sůŠ˜š ržˆ×ëý)4v8žŚDÄl6˙`tt´IMy!j€d$‚R§nŸÇŰCż ÄSb^ä(IŢŠ˝u˙ ­z„ú“äˆ"ŁF îć‹<řŕƒMfłůZę/R¸&ţE†1ćýćáNjÚ/DŠŐPk FoăőďbĎĽƒčđ% !–d(ęG‡ď(ö\:ˆ×oź‹ÁčmĽ§$JJˆÚq:8|ř0FGGżÉÓ̍NƘš˛˛ňßzč!x<Ľ§SPâńxÎPŠŒdŽ, §üówY$ˆĽIaźţ‡ăxýÇń˜uĎ —) IDAT+ţňŢ')d xHH>k¤x<œ7ŕş9€áę•ѐĆKŻáíĄ_ŕżŘžĂćę JO‰+ŔyŹŠŠĘů=ŐL{{;Nž<Šr‹F8‘ˇdŽ;ŚŮ-™ąą‰Źe€dd.A)ŒĎią aԌ5#Ő&•j9bâŹGŹ\Âç殛b˘FŽ…ŻăëýßĂcÖ­đ؟ÓÄv"#WÓTWŻČů˝ŐŠÜuUÔ-ĄDDŽ’9pŕ€fˇdBĄ"‘XNżK2r—ˇ‡NăőďŞv†äCYtR9–¨]Jäü‘żź÷Iě­ŰŠôtrFI ‘‰Db(+c¨Ş2ä<ľ"oŃźúęŤÂUŃ%"3Ë4ť%“hX–_‚]ŠËČŐ°/}öŞ*ˇaę‚FÜ?l%ůPóIÉďjüŞęc’ÂčđĹŠŰçđßď;€/WؔžRV¨ABdÂá(–-+Ód‘öövtvvęÍfłPÎ„Ƙ n\–Ú„‡ÇXĽ&#A)Œ×oź‹w†N+=•4ŞbzÜ?s“ŁdSő“*%!] Ÿ­ôăw+ýŞŠŔšžŽç~óˇxşn'ţňŢ'…ŘŽQ“„ČŒMh˛ÇČŹFgoĆăńnĽç” ˆHeeĺ˙Ůž}ť&—Éeş<)%š8֏żxUUŇ5ŒXp˙+֌jOšK…ޘŽÁz8ëńšiż[éWÍÖÍ;C§q.đţŽń€Ş“YŐ(!2Z-ëmmmĹîÝťqć̙˙ŕKJĎ'„łŮüƒňňrŤ—eZŚ› Z—ľEAtR96~QGŃ ˛fԌ5Łćd”äʗ†/ŠúńőţďŠ6:˘f ‘ÇÖjYoGG‡Ől6˙`ddä[JĎg)TŻ‚Œ1űččč7żűÝď2ťÝŽôt¸“K…L6,ԙ5dQSój؇}żůś*$¤f˘[|kńĚĺÍp ֓„h9JňĚĺÍŘâ[ŤŠëw†NcßožŤaŸŇSI˘v ‘áš%Ž&ěv;žűÝď˛ŃŃŃo2ĆěJĎg)T/"VŤő¤Ăá€ÇăQz*܏ ,řűhMF^żń.žűÍß*žS4bçŐőxźS˛‰Q:ÜwNJÇű7açŐői§+ÁPԏç~óˇxýĆťŠÎGBd˘ŃIŒG–~Ą`x<8X­V՗ňŞzkfŚgˆăŘącJO…;ąŘĆNj—§…mšÁčm|ăę݈š˙Žž¨UĹÓ0Ą<ľ!#v\[áaôé~ˇR9A~ýÇq.đţ÷—˙EέMBdĆÇŁXž|t:UßłŚŁŁŰśms¨˝ˇˆj#"Œ1łÉdúçśś6Íő ‘¤iŒŽżż…Č‘‘s°ď7Ť¨„ÜNJ'ë@łJBň„ş¨™¨@ło-žü­÷+!ťžŽ}żů[œ |TÔ÷UBdFGÐ¤é˘żo!qš\hkkƒÉdúgƘj3çU+"&“é[ńxÜŹĹŐB%§f‚h2”ÂxéłWńÍŤ‡kN–* ”˙A,EUL§¸„¤0žyő0^úěU‹đš]Bä÷WâąĐttt ›M&“j“VU)"Š ŞZëRčäÔLEFކ}řú•—kŃ^4âńţM$ DNČBňx˙&ĹrHNů{đő+/4‘U "ŁĹäUłŮŹúÄUUŠˆVT#‘XÎíŰyŁv9ř_żň=Eśbj&*°óęz츜žś`ˆźŠ™¨ŔŽkëąóŞ2×Óľđu|ýĘ÷ ˛UŁ% ‘QÓ:Í ľ'ŽŞNDc.żßďĐږĚԔ„`P]™Ůj•‘×oźŤČVLUL-3O°ľ!eŤ íQJDŘśřÖ˘*Ś/ę{Ë[5<Ťj´(!2Á`DusʗŽŽřý~ÇL—rUĄ:1?Ý˝{ˇŚTăń8ĆĆ&Ë Y 5ÉHP ăW„×˙p<ŻqrÁ9x•áEA.űuŢSô÷~ýÇńŤ?Ę;oD˨{ÍΗ˅ݝwĂh4ţTéšĚFU"bľZż Wj-2>Uĺ‡MF 2"烜ôć<‡\¨ ńäop ÖÓAtDŃĐIĺp ÖăÉß:Šž?r>ЛWވÖ%DfjJ*j‹…bĐŃс`0¸Ňjľ~[隤˘aŒ™ÇÇÇ:tZꠍNć}˘n1PRF.Žő=¤*ŚÇٟ­ĂŽkë)•PŒŞ˜;Ž­ÇŁŸ­+ęvœ7rqŹ?Ťß+ ‘ ‡ŁEi:Y,ěv;:„ńńńCj*çUˆ˜LŚoét:–Tĺđž((!#§nŸÇ×űżWÔ|_ÔâńţMt Ą֌šńx˙&lü˘śhď’Âřz˙÷pęöůŒ^_j"Łľ-ÇN§SS9Ż*DDŤĺş##Ęő ɕbĘČaßQź4đZÎď“-r5Ěßh mBuč¤rüń†˘Wן4đűŽ.úšR• 1ď‘íôQc9Ż*D¤ŚŚć‡6›MSĺşáp““SJO#'Š!#/}öjQŹ““QІP;ruM1“Yß:—>{uޟ•˛„ČLNN ąĹž)6› 555?Tz.€ D„1f~˛˝˝]éŠpC IN…’‘ Ćžß|ťhMĘŞbz<Ţż ŽÁú˘źAđÂ1XÇű7-w䔿ű~óí´Š’ť¨˝č [ÚŰŰ1<<ü¤˘"Š‹ˆŐj=ŮŇҡۭôT¸Ą•=EŢ2”Âřú•—Š–”*ç‚PS2"š››aąX”žF5EÍI$ąž„ & ™…h9KávťŃŇҢŠ&gŠŠˆÜźLKѐńqm5Âá%#&sţńó΢HˆN*ÇŁŸ­.dőęŐhllDccŁęnˆZgőęŐŘž};^xá477Ă`0(=Ľ$rîČŁŸ­+Ęő|-|˙řy'Lć ’Y$˘ÝęjL™íííŞhrŚč™ÇVŤľcÓŚMši^Ś…-™ůe$Ÿ§Łň˛2üŻâú݆pel€ó ďR4bŰ@qlX,477cÓŚMsn~¸pá ÷˙‹H°}űv€Á`@SS.\¸ đŒć˛fԌ'ëŔŮĆk2 ö>Ťńż<ˆň˛ÜŸSľ(!2ăăQčőˋ~y!pš\hiiÁ'Ÿ|ŇŔŠÔ<‹ˆh1˘Ľ°ÝlxDFŒË+ń“?űŸŘXÝČqfwqރ×Ö !!ƒťví‹/žˆŚŚŚyŸŔąoß>455)0ĂŇaăƍhlź{Mž9sFÁŮ,ŽN*ǎkë –Čşąş?ůł˙ ăňƜÇв„Čhi­WCTDąˆˆÖ˘!Zے™‘YFžůŐˇšEFä­Q*bš››ąe˖4ůÄ'Ÿ|¨ŻŻÇƍ“?ŰľknŢź‰ÁÁÁ˘ĎľŁ!@" uĺĘg“ŽÁzԍřĺ}×+çłî„dŽźESYŠž-ź\QCTD™1/ÍDC$iá°śNk\ľÉHÍDv\' ňÔSOĽ=}_šrgΜA H{íęŐŤńÜsĎ%e劧žÂ+ŻźRÔů–łTß˙}g“ľĄÄń|ůS ŻČŻĎIHö„Ă1 :”—+^ó‘7íííŘśm›ƒ1ćŠÇăÝĹ~EţŢ{ッś´´h&˘•*™LQË6Íý3‡‡‰ !ŤWŻĆÁƒ“‰DpôčQ;vlŽ„‰Éącǒ_[,–´( ‘?ƒ[ślI~}á…y˙-ԌN*Çăý›p‡5’„䆖Şhä¨Č˝÷Ţ;3™StaŒšnܸń€V˘!‘HLŘĆeů ´Œlń­EłomÎď]lVŹX‘śóá‡.™„:{› žžzĄđdűöíÉ“H$‚?üPáĺNło-śäđy ɏÉÉ)D"ڈ†ˇˇˇăƍ(‘+RtąZ­Z‰†ÄăqƒÚ)ĺĘ%dD'•cçŐő¸/'@%˜- %¨Î÷{2ŤWŻ.ČÜJ‘ĆĆĆ´$ŕ?ü‘ˆŘŸĺűîXą3‹mJ’>ƒMDÄ娈Őjí(ö{UD´V)Ł• 0d‘ڧs#S‘óADIJMj5†ĹbɨF´­QhnnNţ}ppP•ĺşšP2bGgŐđizşä%ĐÖŠR4E-ECb1í„äň% â?ô'Çsc)‘%Dä.Ё@ MFśoßždă˛ÔŸ“”𥊊I˜rÝ\XęłÂCB‚“ăřĎýháú™ˆD$C,&ţ˝RQ‘˘‰ˆÖ˘!Ą6 8_äśí§oý ĎüęŰ‘‘űďX…ŠŒYŠŢŢŢ4ĄH-ÔUjl–?ƒ!-rĺĘMţŐIĺŘquýœ$V^ňĚŻžÓˇ~•lOh瞠DT¤h"R__˙=­DCÂam~”ŠgÇ\ŕ.#÷ßą˘ŮˇV$’"Sˇf7ÓJĽšš9ůłÁÁA!ú[¨ŚŚŚ´(“Ö˘!Šč¤r4űÖ&e„§„Če÷ňŮ4Db›Z 'ôĘQ‘úúúďë=‹""Œ1ű͛7›=O1ŢŽ ÄăqMśqυ—>{uÎŮ1ŐDŢ ÇăÁ͛7›‹u2oQD¤ŚŚć‡6› ­­­Ĺxť‚B Ş űŽâ”żgޟń’‘˙ąëoPWW—ój&5*˛zőędâŞÜÖ=UBŽ;F]U90ť\ˇˇˇWᇺş:ü]S ‘9ĺďÁaßќÇ× ZI\mmm…ÍfCMMÍ‹ń~Ƙ}xxřI-ä†LMI”  ŕÔíóxgčô˘Żá!#z˝nˇ[“2200v#”O~ݡo_r;&ŕÍ7ßÔdCąillLˡ9sćŒđ庙PWWˇŰ ˝^ŸóKIˆĚ;C§qęöůœßG+D"1MlݡˇˇcxxřÉbDE ."&“é€ÍfƒŰí.ô[-˜nž\ëÇKŻeôZ’‘ĹšpáBňfh0Ňú„\¸pGŽĄH'fŸ'S ѐbJˆĚKŻáâXÎď§´pŻpťÝ0™L0™L ý^Ƙ9z´ !ąŘTIvPMĺj؇o\ýQVżC2˛0@`N7ĎŢŢ^źňĘ+%óÄ^ šššćHžÖQBBdžqőG¸öĺüžZ`rrJ式ŃhÔĂ3ň} iŐét:-$ŠƒÚ8S W‚R/}ö*B9”ę‘Œ,ĚěóM,KI$P ƒÁ ™(ŹE””ÍŹĽ^ÖŤ…{†ÇăN§Ó(h‚gAEÄh4ţpϞ=Ěl.¨LœH$IĘ˝s¨˜ŻB&HF&ľ„´ąąqÁr^"{R[éĎ.Ö"JKˆ UŇ$Ne=§Đl6cϞ=Ěh44iľ`"ÂsƒÁ•˘'Šj% :^żń.ÎňßS'™ŸŮMľvíÚĽŕl´ƒĹbI‹†|ňÉ'šŽ6ŠEBdÎzńúwóGd´PeŮŢގ`0¸˛ Î &"r3ťÝ^¨ˇ( á°6ęÂsĺ\ŕ#źţ‡ăÜĆ#™ŸŮçФv˙$rŁąą1-ĎŚŠŠ űöí[˛­žˆ¨MBd^˙Ăqœ |Äm<шÇăÂ79łŰíopVŃJłÄE$vh-ކ}xéłĚ*d˛áĘŘÚß;Œh4÷¨Öddpp0­’c˖-ÎK,Loo/^~ůĺ´˙ŻxńĹŃÜÜŹ™˙ż<$$˘ý˝Ă\%DćĽĎ^+éäŐp8&üĂlĄœDDjkk˙N ĚJ9’OręRÜNJęßJčęę"I!ľRfv’%‘‘Hďż˙>Ž92§›íÁƒ3:YÍ𒐮Ž.T˙Všs6 J=yU QšÁYmmíßbü‚ˆČččč_P4Dl:źo敜şňŮ1044D2’B$I+ç]vJäÎŕŕ Ž=Šcǎ%óD víڅ^xAČaž244igÓđäZř::źorW´qťÝý‹BŒÍ]DcîH$˘˝wH)GCÎ>Z°}{>ÔLTŕo4¤}d$Ůĺźá˕+WćôiY˝z5öíۇ§žzJ˜ü‘BHˆĚßh@ÍDEžSœĂ)OÉć‹h!*âńx‰DôŒ17ďąš‹ˆĹb9ÔÖÖ‘Kv%işdśŒŢ.H^HÍDv\]?ď)ş$#éź˙ţű:qˇ0ȝkgçLL¨ż÷C!%HœÚťăęú‚ČČKŸ˝†ÁčmîăŠŔřxTč6fłmmm°X,‡xÍUDcÎ@ `=21Qş[2߸ú#îy!:ŠÍŢľóJˆ ÉČ]pć̙9‰–_Ţ˙}=zřđĂUßÍśĐ"“Ég6BR8ëîĚZBô{‹ŰíF °3Ü<Çĺ*"ľľľ/Úl6¸\.žĂ•x<.üŒ+Żßxˇ y!~ś.٧+’‘ť¤žCC–=zTőÍΊ%!25xôłu9ż×B\ _/Ůţ"b犸\.9iőEžăr-$Š–jnČŐ°kż™-žľ¨ 3~=ÉAĚĽŘ"S2bËLr9O^˙Ăń’,éŐJŽď¤Un"˘…$ŐR­” (\z˙+îË!Ÿd„ „ČÜwÇZJšo\ýQI–ôŠ^Aăvťš'­rťÝţWťwď:IľTŁ!ŻßxCQ?×1k&*’eşš@2BĘKˆLło-÷äŐĄ¨ż$ˇhDŠ˜ÍfěŢ˝vťýŻxÉEDcvŻ×ű' ‹cýxgč4×1ĺŹű|!!JľHˆĚBUoůđÎĐi\ëç:Śh!*âőz˙„W§U."˛rĺĘgL&“НTŁŃIĄ/Œ\Jaüý˙2ýlˇ‹d„(EÔ&!@âŁÉŤ?Pz]Wăń8˘ŃIĽ§‘3­­­0™LXšrĺ3<Ćă""ąXěŻEކ(Éž!…Ř’qޓUrj&ŒĽ„%DŚ6d„sđŽc–ęč÷ˇŰX,ö×<ĆĘ[DcÎ`0¸Rd‰DbB7šÉ…Ťa÷-™ş ŽÁzŽcʐŒĽ€š%DĆ1Xş ߇w†N—\$M#7Ŕív# ŽäŃS$o‘{‡8\ű›•‰ qCdšňŇg|ˇdtR9ś đŰŚB2Bh$DfŰżíWŢk’ˆ|ďq:ÜzŠä-"###ψÜ;djJÂää”ŇÓ(*oć޸l‹ˇ‘űÂ4$#…Çl6ĂnˇĂétÂĺrÁĺrĺ”˙ŐÚښü}§Ó ťÝ.tU]!IB€ÄƒÇ/߃Ż…ŻămÎQZľ399…Š)IéiäŒÇăÁČČHŢy",ŸMĆX+€ż˙ýďaˇŰó‹"ŒM˖ ƞKšśqßřEíœĂě h ˇŠŤŤC]]]R<Ěf3L&SVc´ˇˇ§ý™)ŁŁŁ×ëĹČȆ††JúßAÔků˙ť÷:Ž|éˇńŞĘ+pâÁ—a,ç΍Z1t¨Ž^Ąô4rÂëőbíÚľ°'ŸĚuœeůL˘ŚŚćŮ5kÖ+!ńxź¤$:źor•Ş˜ÎÉk™ GFňYŔĺČH)ȈÁ`€Ýn‡ÝnG]]l6›˘ó1™L0™Lsćáóů044Ż× Ż×Ťů6÷"K8ďÁuóB:>‰—!)Œď›řď÷ŕ2žD"10ƔžJÖŘív8|ţůçĎPFDĆÇÇ9IUäŚ2špqʧü=\ÇÜöŮýEْ™’‘ĹąŰíXż~=ěv;jkk•žNFŘl6Řl6<üđÀ[ˇnÁëőâÓO?…×ëUvrœ]B€™Ü°ĎîÇĎ7|ÂmĚSţ<śŞ›Ť7pSí„ĂQTV”žFN¸Ýn|ë[ßz,Ÿ1rޚѶ̝;Á’Ş–Ů÷›osÍ qŢS°*™lĐ‚΋őë×'˙Ëç˙GŚäş5“ ŃhŸ~úiň?‘ŃÚ5{yőMô­ţˇńÖU4ŕčýOn㩝ňň2Ź\ɡŠXđ؞É9""úśL4:YRręöyŽR3QĄ (2RlůP ˝^‡Ă‡Ă!´”hMB€DIďusĂ+řlű^ _ÇŠŰçńŘŞG¸Œ§v$iŃč$ôúĺJO%kxlĎä\5#úśL$"nŮTśĽ0űŽróO>/nręR”Z5Ůl†Ëĺ‚ÇăÁÓO? ‡ĂĄi ™,%O?ý4<\.—9Z”ŢkÂaßђę¸*ň=Éívc||<ç활D„1֍F ˘śt—íłTxgđÜŤdxwOĺA)ȈÝnÇŢ˝{qđŕA´´´d]ĺ˘EL&ZZZpđŕAěÝťWľQZ-Kčşşń ~šH!)ŒwÁm<ľ#r”žľľŃhÔ0“˛‘59‰HMMÍł‡CľřĽ(ĽJ™ÁčmŽľůJUÉdŠVeÄétâŔhkkĂ< ôtTË<€śś68p@UMľ.!2ŽÁ{Pă™{{č4َš§vD˝7ÉŰ3555Ďćňű9‰ˆ$IŇśŒź~ă]ŽŃ?ůźAą*™LђŒ8Nx<ěŢ˝[˜Ę5P[[‹ÝťwĂăń(.$Ľ"!@˘Š†çMH —Ô94"ߛÜn7$Iz4—ßÍZDcÎŃŃQ‹¨Ű2"‡ż˛e0z›kšn]Ј5Łę߇ė‘TĄí—Ü1™LŠ I)IˆĚšQ3׳hNů{J&*"rÚ@kk+FGG-šœ=“ľˆTUUíy[&-v$š}|[:eÄnˇăŔ$ œ‘…äŔE[ťJQBdxŻĽő%oĎTUUíĎöwłƒÁđ˜ËĺĘö×TA)uRĺ qŢƒŞ˜ŽŰxĹB1›ÍpťÝhkkŁ-˜R[[‹śś6¸Ýî‚Vٔ˛„@UL'Ç\˛RŠŠD"1äsôŠ’¸\. †ŹŤg˛ƘÝď÷Ż5?DԐW.đ|‚¨Šéąc6|ąQťŒ¸\.;x>1ԍŞě š/JɈËĺÂţýű) ˘bL&öďߟUîIČâԆŒ\ËyK%*"ę=Kv„lşŹf,"ľľľ_s8Bœç0IšĆԔ¤ô4 ďhČßĐV4$•bʈÁ`€ŰíŚ\hiiŰí^rŤ†$$3xŽ%Ľ™š’„Üž1›Íp8¨­­ýZŚż“ąˆH’´]Ôhˆ¨!Žl9uű<ˇąî矡ŤFŠ!#uuuđxúhNä)¨Ą­ly{ˆßqŮĽ I…§Œ´ľľÁáppœĄG˛++IHnđŠđ[óԌ¨÷°G}´ŹŚŚfW&ŻÍH.$IzTÄh nßţl8řCQ?—ąJ-’ /ő&biěv;IHđŒŠ Eý8řˆËXjFÔ{˜ËĺĘř4Ţ%E„1fľˆ("ńxź$š˜ń QŠÜʝ†ˆ˙hŮŔ3Iuăši_*Œ™B’9źÖžRHZőžÖŇŇ‚ŞŞŞ‹˝f)Ů*b4$kž›ę9N€˘!244„ÁÁAĽ§A¨œÁÁA’ ášöđ\ŐČԔ$dşËĺBUUŐÖĹ^ł¨ˆ m1?DÔĞl8uű—qF,%ŰŔ,[Z[[ŠYą$vť˘ZlŞb:4ŒX¸ŒĹkMT3"FEœN'†††6-öšE„1ćDÍŃv4äj؇káë\Ćây"Ś–q:ÔśČ‡Ă1ç Dia2™TenllÄž}ű°k׎´‡ˆ@ €3gÎ ‰ślŮR0™ŇIĺÜzŠh˝š™ˆ÷şĹV瑉‰‰?QD´ŢM•[3Î%ť.\Ŕ‘#G000öýććf.ňĐŘؘ”ĆĆĆźĆZ ťÝއ~¸ ďA”.?ü°ŞšâY,ěÚľ űöí›óŮ:sć Ž9‚ .ŕç?˙9€D´pžh /x­MZon&â˝ÎétbbbâćűŮź"ěj5÷Ĺ1‰'S‚R˜[3žŰ22ƒƒƒ8zô(Ž;†@ üžźp˝đ 9KDęÂwá…źçşÔ“(4jšĆš››ń / ŠŠ)íűWŽ\Á+Żź‚ .$#!WŽ\Á•+W$śoxo˝ĘđZ›Ž…Ż#Čédr5"â˝Îĺr!ŘçűŮaŒ9¨ĘÚ3EÄ$žL9?Ě'‹÷śĚläE,5œ ŤWŻĆž}űđÔSOeľˆ555%K{{{çD]xB[2D1Pz‹făƍón.ô0!sć̙äß ľEĘs{†×šŠFDź×ÉN!;F*óED슿$ "†Ş˛áâŘ.ăđޖYˆ .ŕĺ—_Fooú^­ź677/š×œŽD"†˜ÍfŞ’!ŠFKKKŃťV/ô0‰DđţűďĎť˝šJ Hú¸iÓ˘G‡äŻ5Š×šŠVDťçĽ8…}öĎć§ˆ ˛h˙(ŮŇěç2N!śeB^ŕŽ=:g““Og‡…SijjJĘʇ~8ďS/Ô.'J‡b]sƒťvíZp{4Ěy`XˆO>ů$9fĄšœńZŁx­™jEÄ{ތ[,ŠŻŻß.f˘ŞvËvyuS­™¨(čśĚB ŕčŃŁx˙ý÷çä,”(gąX’ѐ@ PĐhˆÝn§ĆeDŃąŮl<Ë ăł…`` )ŤWŻÎ)kŊ\ć8TŽš‰ŠźÇŃz—Uďy3•3s˛—ÍţF,kó ;ńě0S.Žń1{^{ŻšŇŰۋO>ůMMMie€hllDoo/.\¸€@ €]ťv%/uoşěÜšł ăÄBěÜšŻžú*÷qç”â Š˙ý÷100‹Ĺ’”ćććŒňŻęëëšĎu>F,^‘˛éĹą~|šB›"ŢóĚf3bąXĂěďωˆřýţ5"V̈˜ź“)źö:׌(/˜rŽÇ‘#G’Yř2r3Ľ]ťv%ŸĐ漎'N§ľľľŸ Łśśź#ĐÍÍÍsš F"œ9sŻźňJR8R#™4+[˝zuZçäBn•ňZŤ´œ'"â=ĎĺrÁď÷Ż™ýý4ľbFÄ˝˛lŕáîäE ŔącÇpôčŃdŘŽI #:"˘tڂ÷5ŘŰۛVąÖŰۋ—_~yŢíÍÔÜŤíۡ/(#xîšç’_ TDxm#óŠ&ŤŃî} UÎĚޚ1§žX´ÜÖýj؇‡zxĽˇeb``GŽASSӜrÂŢŢŢ4IáÓé¤r]BqL&œN'úúú¸Œ'G?6n܈3gÎ,úŠD"8věXňPťíŰˇŁąąWŽ\A €ĹbI6LĽĐý|€Äšőť•ůĺƅ¤0ކ}šÝž‘¤i,[Vüźż\Iq‹´×lq9Žḃ+˘Ya6đ2úşŸ“- EjţČöíۓ‹i!ĄhĄ\.7Ÿ§L+ańóŸ˙?ţx˛{ńbÉŤrŽIĄŠ ó@Űy"SSôúĺJO#+._žěĐ-/MDjkk…LTąË\Ś\÷r§6XÍeœB"ç\šriáeŢP4„PźŁ"Ů"G@üńËrea)d”2^kŻ5TÄb*+•žEv˜ÍfÔÖÖŚ%ŹŚ‰ˆ$I›E|JœžÖîÖ ZřޘU1‡Ů‡lúäĘWżúՂŽOŮňŐŻ~U1˘qäČlܸÉd×ÁÁA % ’JUL‡Ş˜!]4Żq´ÜODÄ{ŸËĺÂ'Ÿ|˛9ő{i"Fď1"˘Ő‘ ćŇ?¤.¨îm™bcˇŰŠR†Pľľľ°Űíđz˝ŠÎ#ő\ĽŠ ńť•ů‰ČPԏ †ą\=ÉúźńŢg6›FďMý^ZŐL0\)Z33mç‡đY ԞRl(B¨ş6ÓáľvišŒW´{ ÓéD0\™ú˝¤ˆ0ĆěŠ~ţAžˆh„™rmœOW@KX{Ošb6›ńŔ(= ‚˜—x@¸5¸đZťx­ĽjD´{ |}ËΤGDě¸7×)4˘Ů`6đŘŰT[˙Ľíú&JşFďÂŤŸˆ–óDDťŚ\ßvů/i"b4ĹR+’Wz c0z;ď1j(’-ň„ÚĄk4kľT­ˆxœq ťüušˆlŢźyžÓxUhaŠLᖨRŮnąXż~=•ěŞÇd2aýúőJOC5đXĂä„U-"â=pĆ5ěň×Ińľ‡ˆhaŠLáľ§I‘ťĐâNˆ]Ťwᵆi5ODÄ{ŕě^"I‘$iłˆ!Áx\ź°T&đ:žÚBů!Ihq'DŽŐťđZĂx­ŠjCÄ{ Óé„$IÉ^"ÂmŤ"˘ f n€:Š\¨Ff…dýúőĐëőJOƒ 2BŻ×“ŒĚPÓqIXŐr‡UŃď…IńűýŃ""ÓÓâ™`ŚÜŒĺŸBŰ2wĄE şfďÂc-㹌ŞŃî…N§~ż?y°]ZDD´Ń-p1xěgRŮî]hQ'DƒŽŮťđXË´š#ˆw/œíe€¸ÍĚDÜ˄ FˆC†wUŒś"€DKwږ!DCŻ×§›^ŇđXËBRXł•3˘Ý g75“#"v@źúuë§3*fřBO–„¨Đľ›€*gG´{áěŚfB'ŤŠX? AiœË8T1“€ž* QĄk7ŻľŒ×ÚŞ6Dż&#""vUŐ*źŹGŚšč :i—–ÚÚZ Ľ§Ą8źÖ2­FDD$ľťjRDDěŞ:99Ľô vÄuA:q 'JB|čNŔcMÓjŤwÝU…“R@ËefņqBtčć­­ę¤ ŞŞŞ„‹]‹–%œ ĄŠü÷1錙uuuJO ň‚Žá<Ö4kŤZńž(ťG †?uš\ŠN([&'ĹŞ›Î†káëJOA3Řl6Ľ§@yA×0?´źśŠvOtš\0  Đ֌fĄz’$´]Ë´Śi•ĄŐƒ™”€oB+ĐľĚZcŐGH’dRz"Ů"b–p&„ŚřtţŁ"âu &ˆ… k™ßšĆkU"Ţe÷(€@ `-G„Xę!BŐ„v k™Ö4­áršěmͨŽ‹cW”ž‚f §HB+ĐľĚZcŐ‰ˆĄ¤Ž&“p;Ž1/t-' ľM›+"ą˜XĽJDqĄ'HBkĐ5M,†Č÷DaEDŤhľqąĄE›ĐtMóÖXőQĆłt‘Ť-ˆuŇ23şž ­A×4ŸľÚźŤůzfŒŮ“‡Î8N§Dđ¤†JwiŃ&4]Ó´śi‰ç°ÓÖ AAŠA"Bhzz$´]ӄV6™ŔbŠTz áÝ/ýƒŇS B…88Ľ§AÜĄˆAAŠA"BA„bˆAĄ$"AA(†°ÉŞíííJO Jžîînôy$5 ęç""AA(ŕp6+<‚ DC~őIŒ ĺ`ŒŔ6ŠˆAĄ$"AA(‰AAŠA"BA„b[žŒcrrJéipçŻúż‡KcýyáźŽÁzN3“ÖÖV:—ƒĐ—/_Ćɓ'•ž†˘\^}}Ť˙×VoŔ7ü7N3R˗/ö 6ŠˆšdddDé)Wčš&´ ‰AAŠQŔ }}}Ę΄ŕĆđаŇSPzz$´]Ó´śi‰çđ–Ĺăq/@šZ¨×Yó#VŽ˝Ü™lĄë™ĐtMóYŰxŹąDţČ×s<÷Ň֌ĘX­_Ľô4-ڄ֠kš´ĆŞaED§+Wz „ŠĄE›ĐtM‹!ň=QX!fČTz Ş`ttTé)čZN@k›6!Q›Ť7*=Í@O„V k™´Ćޏ2°X,Ţîîn…§Bđ$V.)=Ĺńz˝JO ¸@×2­iZŁťť‹Ĺ ̈Hyyšpqżĺ˅m ť(UË*¸Œ 27zŠ$4]ËüÖ4^kŹÚńž(ťmͨŒ/Wؔž‚fRz Áş–ůAkŹú Ń(”ÔE‹7ĄčZŚ5M˔@$ůľh9"˗‹[Ş´ë*”ž‚fđů|JO ň‚Ža~hymížŘÝݍH$ňk`FDBĄĐ-e§”=Œ1Ľ§P0Ş–ĺ‚âPŐ‡™ˆ=I˘C×pkľU­ˆxO”݃śfTľ ćU˘C×0?hmU'˛ˆx/^ź8­čLr@Ä,áLŕт˜öSĐ"Nˆ]Ă xŹiZmď.â˝pĆ9ź@ŠˆƒAŠŽ¨„u•|˛şŠîˆD"¸uK¸G‚Üşu ‘HDéi(ŻľŒ×ÚJäόsxÁˇfĘ˅žţ‚ËůěcR/‘ôDIˆ ]ť x­eźÖVľ!ú˝0€žž>ĺf’ĺĺâ%çd/kŽ €O?ýTé)DNĐľ›€×ZŚŐˆˆh÷Â×đ3"Ç˝€xÝűDĚÎcyŞĘóďţŇE9ĚF|ź^/˘QúAˆE4ĽˆČ <Ö˛Şň 9ŹŤjD´{Ąě˛{”Í÷CQXśLŹşélŕaîĂ´5“„ž, Ń kö.<Ö2­FCń]#)"VŤő˛h[3eebY`6đ(3Ł­™ťĐ˘Nˆ]łw᱖ištW´{a__ŹVëeůkĄ3\DłŔlXWiĎ{ŒXš„.–˙d4Ŕ§Ÿ~JŰ3„0DŁQ‘Bş—ŞkŞZý^˜‘ňňň‹˘EDńöĆ2…×ÁLT9sZŘ Q kő.źÖ0­v'â=°ŻŻĺĺĺ寓"rëÖ­ë˘ĺˆâ›ŕBPĺ hq'DŽŐťPĹĚâˆxÁ­[ˇŽË_§nÍŮ]Uôúé…0–W NŸ˙ž&9s—O?ýŁŁŁJOƒ ett”Dä˙oďýƒ›şď|ď÷ąlIÖË2r°)AŽ)L I11Ďłˇ%`§Ă˝Đ¤†”MşłĂĐôޙ`ş˝łÝM٘˝O7ŰöîTÎÎd{çŠ2ňdą!sa†!rŔ˝ÍśŚr÷bÒ ˙(˛%YśĚyţĺ_úń=:?ôyÍ0AF:ú¤s^çó뛋sXÎŚÚŽ%^S§Şi"˘ÄéŞJëŸÎăˆ)"2%ډâ‚>Ł3aqSëhw@™×ŔÔŠŞ@šˆĘű(1,•)ć5y#Ž™˘6Ţ”öů&ŠúŒ>`Ź<¤P•ĹšTŽ(í˜>Ě HĽ5SbX*SXĺ4I qýúuŠ—AsrýúuŝƒĹ„ŐšK­ő!€ňށéẰ´ö]łŮ<Ş4Wš fÓk™gŘD;ńŚňë_˙Zę%ĜĐgs&ŹÎ]ŹÎĽrDi×@ÇłŮ<šúł{ëtşĎƒÁŕ’Â.+4šLM)ŽÎvQ„‚Őá˜?Żă°Ř>;ęëëQ__ÚÚZ@ ŔŔŔúúú$Y€×ëĹČČ–.]*é:"•‘‘éž‹sŞĘ‹`0N÷yęĎfˆˆFŁšâvť×vYůSR˘NšÍc—ň:Ƅ6† mڏ–ŃŞŚśśĎ<óLR@RillÄĐĐ>řŕ d=sńë_˙ßţöˇ%{‚HGěhHcc#D}VLhăLö˜Qs}HI‰ňDÄívϘ!¤Ľf”:KDŤUVh*XM1Śˇąąű÷ďŸSBjkkńŇK/aíZéÂĽ‡Zy Ů …D-RŐëőزe ^}őUlܸz˝^´÷bŤs–š'Ş*ńڗ>C˜=âÝÝŰŰ ĽĄ´Y6ůříˇßž÷äÖÝݍ .$_÷ÔSOÍzÎƍgÝľéőzŠŠŞŁŃžžž"/DwďŢ ŤŐ*ę:˛Őü5GCĺ]űˇ\C`–NŮlśŰJ<9—•ĽwԍֳەâŐ˙¤žÄúúú-DíîîNŠĘƍ“ŻŻŻŻÇŤŻž:#” $:nŽ?ŽîînŚëöx<azL‚Č”‘‘QŁ!k׎MŢ ŕěŮł8vězzzf=o˙ţý˛ŠauŽRsŰŽŻynˇ6›ívúĎgýM´Zí­`0řpa–ĹŽ˛2 žüňžÔËVVŤ2€uC˘k!3z^wwwň$)ČHjTH¤x._žĚ\@R9wî^~ůeюOóqîÜ9QŸ 9{ö,€„ÔŸ={}}}ظqcň;—š íîîž%+…„EZPwD¤ŹLYŃ Q¨ŞŐjoĽ˙|–ˆ ^đx<Ó.wJK••+ˆŐ;“y"B¸S;Ĺţ|÷îÝŹ_300€ĄĄĄ9sŘ@"Œ|áÂf5!óáőzáóů`ˇŤ7—LČŸĎ'j<5Ňxá…Ymť@ccăŒ¤ŐjŎ;°víÚ‘ËBÁ*\ŁłŠş>D‰×<ÇƒÁÁÁ é?Ÿëoâéęę*Ŕ’آ´\Yś°ę…żÍčN#ĄĄĄ¤0<öŘc˝ŚąąqÎźôŔŔŽ;†łgϊ.!.—Ť ďCb~ćRkŻ˘Ńč‚эžžttt$ëśęëëągĎěŘąŁ éVç(5Ď”y͛v‹YšČšDÄ PÁŞÜ`•ëźeŻNäęŐŤp žŐ××c˙ţýłNp@ďż˙>Ž?^đagÁ`JpB™tuu‰ş§Lj­G&QĹh4Šîînźů曳&766âŔ3şâĄŐ9JÍő!€ňŽy)NáM˙łY"˘ÔÎ@™Ĺ;™˛šj“ăÜŞ 0ŠFŸ‹Ô:ŽÔÜ´€ŐjĹîÝťągϞóC˘Ń(.\¸0çI°¸ÝnrFˆN(ľSŚžž>™ęĘŞÖcž›Ôhb"ŒkŚ˜Ő‡°:gĘ%^ëćë˜授Ŕjľz•Ř9ŁÄ)s™bְʰ‚ÉąÄJρäIoíھɖ^ŻÇƍç<‰őôôŕŘąc˘ŁfĽhąű3–šHOˇdАMŚĚw3Á VçŚU†ŞÝ_PćľÎívĂjľzçúł9E¤źźüߔś / źPUślŻnbr1Ó3Š'Žgžy7nÄţýűgEH’ÓVĺ´÷…×ëĹ'Ÿ|"ő2•ňÉ'Ÿˆm@4E___ޅŚÝÝÝččč˜uŁ ¤WY§kX›X+劯uĺĺĺ˙6ןÍ)"ƒƒƒ•("Z­ňÂUŮĐde3ĚKĚôŒĐn <çŚÖ‹­ƒÇ—ĺ4G€R4„8ˆ’ä!×hH:ŠŠÓÔďŹ ;Ź`™–auŽ”+JźÖMwĚ\œëĎćë˙ńř|>—$Ç)näm6ÔęŞQŁł19–Xéóśü]¸paÎaJr#âĉR/ƒP'Nœ(XX4ei† ž˙ţű¸|ů2Ó÷`uNŞŃŮPŤŤfr,9˘Ń”€ă8Š—‘5ÓN1g„c^”9ţZ‰E<ŮĐdeS€őiU~3Iăý÷ßOžtűúúđć›o˘ťťť`'â|Ś.‚]]]–zLžĎŹo(X“X#劯q).‘šˆđ<4›ÍŁĘLĎ(/w– ŰŤ739ΰ9Œ mœÉąć"âíˇßžqĽ4Ün7”$ä…ĎçSäMÝb°źŠ˜ĐĆ1lfłŰ.Ťs¤\Qâ5ÎăńŔl6ňŮ IDATÄŹ´TŁČů!nˇ—zÎb"r^Šł”hŽŮPŤŤĆfF_źž‡(˝555hmm…N§“z)„ĚŃéthmm%ÉVçžÍÖFU1”{MëęęÂÄÄÄů…žłXů­b›)ő-XfĹ5SY’"[HFçł%~f[M¨˝HPć5mąAf ŠĎó^ĺ6+Ud+šŹ`b´ĂdßC#LŽŁ6HBˆ\!YVç“Ć úś]Žă)")ƒĚź =oцdĽ6ŽLę%ˆŤ;ąňFLl&Ş’"_HFćfÄĆXy„ÉąŠ!˘ÔkŮbƒĚ‘ÁÁÁS}ôĎfY…E‰Łpłĺ…šm̎ĺŠý‚Ůą”IÁ ’‘ٰ<×°<ĘĽ^Ë>úč#~ppđÔbĎËdD›{||œSbzFŠ™ ,‹V‡ÍěîR” IÁ’‘Œ•G˜í+S EŞ€2Żeăăă÷bĎ]TDxž÷Z,–€Ó3Ç)r]ś° Möy­I!$# XžcŠ!-SVŚĚzGˇŰ ‹ĹXŹ>Č,"FsQ‰"(s]ś4Y70ŰďÓ%ţ˘pĆBBbą =S1^Ż7ŻM‹]F&´qf“Tkt6ŐŠĘ˝†šÝnh4šy÷—I%#;{ńâĹűů-K”ŇʅjžĹěX˝EX+ÂJBœN':;;iź éííEgg'œN'ÉHް<ˇ°<çÉĽ^Ă.^źxllěl&ĎÍt?w8.QbTDŁ)Qý”U ˘dŐĘ[lQ–"lrćršhŁ<qć̙äŘöááa’‘` 1i E‘–)-Ő(rˇ]ˇŰp8\‚ ęC€ E„çyŻÍfť­D˝^™F™ fiőxąDEĐÇƒŁGćuÁ"¤%‹áčŃŁł6°#ɜѐm03şń’3J˝všÝnŘlśŰ™Ô‡™GD Ńh.(u#'Ľ†ś˛…ĺ§KüŞď SB†‡‡áp8„m° áóůŕp8üˇ%Ɍąňӝž‹!(÷Úĺrš Ńh.dúüŒEdddä_z{{ s[™„KzŚVWíśM̎÷›ĺˇ˜KnBB˘Ń(œN'”şoS1ŇŐŐ§Ó‰h4şŕóHF2ƒĺšdťmSQ´ě*5- ŃŰۋ‘‘‘Éô5˙-yžwPěöÖJ qeËžĺĎ1;Ö°9ŹĘiŤ…”TÜn7Ž=ŠP(”óűâ …pôčŃŹö×"Y˜S˜ŮÜ€í9NÎ(őš%8‚ŕ ™•n-[śŹ[Šu"J qe ë¨Čż>ŹŽ¨ˆT"0<<Œ#GŽŕ“O>Éůý qřä“Opäȑœ˙]IFć†ĺ9¤X˘!€rŻYnˇ˖-ëÎć5Y‰Čŕŕŕ?Ÿ>}Z‘ăŢ5š’˘n°˝c+¨fg^Š%D âÜšsčěě¤čˆ …BčěěÄšsçMĹ,ÉČl>c\kV,ѐ˛˛REŚeŕôéÓüŕŕŕ?góšl˙Ś.ĽŽ{€ňrefś°ŽŠxj™m×-r‘Tź^/ՎHHWWł!t$#ˆkŚŕŠdvźbІ(őZ•2Ö=ŤŽŹDDhău:ŮźL6(5ԕ ,ď&´1E~—Ł„¤âvťŃŃŃA5ÄçóĄŁŁ#ŤZL!IĐ˙Đ&´ěZ׋%(÷Zĺt:łjŰČ:öF?TjÇqĐëľR/Ł °Š|ĄČ!gr—`0˜œĘ:2˘\é“;###Éé¨bvťŒLhăLwŘ-Śhˆ^ŻUäŢ2@âŚ*~˜í벑‰‰‰Ł˝˝˝ŠÝOCŠsűsőDˇ}€éńÄF)’Š×ëő#GpćĚŞaH(™3gpäȑ‚ťŠYFXŸ+Š+˘Ěk”×ëEoo/&&&ŽfűÚŹE„çyĹb (ľW§+SlPś°ŽŠ ›Ă¸mQĆ%JH*‡ƒ„$Oq8łŚŁ‚b”‘Ű– ÓvÝbІh4%ŠM˸\.aˇÝŹżh9]‘5ÍEĽÖ‰ĘíĎ΅}˟cś hœ{áŞŇ%$•T!Ą”M㌌ŒH* Š“ŒÄ5SLŰuMCQEC”|mr:ﶛNN"266öŽ’Ó3ĹR'$˘",÷ ™ĐĆd˝š$$Çƒ#GŽ łłׯ_—z9˛ĺúőëčěěđ#G$TŠEFzkż`Z úBÍś˘‰†Ę˝6 i™ąąąwry}N"ÂóźK§ÓE•šžQrř+žŻýÓ¨HßC#˛œ¸ŞV IĹëőâĉččč@WWĽmHżtuuĄŁŁ'Nœí ’ÚedÄFĂî:“Ć€çkżĹěxrGÉe.— :.šÍ4ŐTrţ[Ć)=Ł ĚÚ÷0=ŚÜ&Žƒ„¤ ávťáp8đŢ{率ˇˇ¨vůĹbčííĹ{gáۭˆ}°Ô,#ŹĎ í{Šb‡]%_“œN'ŒFcÖÝ2Ďç6(•㸧oŢź‰şşş\ß_RFGؚş/ő2 ƞűK܈°;Y4 }놖1;^Ž›„,ÄŁ>šü•Ď˙LiooŸń_1‰Ĺb¸víZň—’QŰgśˇviťî*Ă â fǓ;M –,1K˝Œœđz˝xä‘G`gŽ‘œű„xžwéőú¨ËĺҡľľĺzIŃëË09Yqˉů.ţ•••3~ ?[ˇn]VÇďííMƒÁżˆ(5MsŰd*!@bîQ1I¸)i ψÔÔÔüżz˝~Ż’óˇŔ$žüňžÔË((Ź WľS<÷։ž˘! ‘Ž×_}ÖĎŠˆ>|XäŠ %}śăš)œ|ź—épĂb+P€˛˛RX­FŠ—‘3uuuˆFŁżţł|Ž“wÓňČČț>ŸOVÃ˛EŠ[.çël\3…ęo0=f:J:QDś(Šľ÷ŁúĚ',[] ěkÇăĎçĂČČțů+oáyŢc6›G•=SDŤŘA2š˛Ú`Çó 'މ˝hzk™S€$„(” #˝ľƒL÷’€çkśaľMľRĐhJ;IHĚ1›ÍŁšě-““ŤŻVŤý{%‹âĎ\ű–?‡é1=ľ_0ŸşJBr–‘S˜yŤnÎVTűÉ(ýšăt:ĄŐj˙žĹą˜ˆČčč蝥PJů$Ćë*ľ}*WĚţşž}8ôâJva[’˘‘ŁŒÄ5S¸¸’}úőŻë_)şUŽă˝ÍˆËĺB(Âčččť,ŽÇDDxž÷ÖŐŐýŤ’Ł"Ç)şz9WžŹXĂĆóź“Ő1™śŠX,–r8,YpŠ5*˛Ú`ÇžŻěb~ÜËö›YŻ’„Äl¤’‘S—óˆlÎÇžŻě*ş.@ůŃp8°X,˙Äň˜LED˜)âvťYś p‡ňň⋊‰Í*űQíWŢČ(Ź[L˛qăFěŮłőőőR/Ľ(ĐëőظqŁÔËȋBËČXyD”âÔU†E™’” qťÝĚf‡¤ÂTDxž÷X­VŻ’Ó3ŠVD€D¸ÔÄ8\×LĄťî悅nĹ$!z˝O=őęëëągĎěŘąVŤUęeŠ–ĆĆF8p[ślQźřJF2ůÎć‚I¤4°ŕ8NŃsC€DZĆjľzYĚI…ůŻ@ p¸łłSŃEŤM‰â{źsĽVWC+żÇü¸cĺ‘yŤî‹IB źźÉǍŘżżâďŘĺĘÚľkĄ×ë;věx5ů#śŒ]oŹ‹SŕĐĘďĄVWÍü¸JŔ`PöŕĚ`0ˆÎÎNć{70˙ż˘†˘U xkE ÉşŰm›˜wŹ<‚ß,ŸšżMąIź˙ţű8~ü8†††$˘$[ślÁŤŻžŠľk×JźBuqá…äď­VŤ*„OLůÍň[˘HČvŰ&4Y70?ŽPKmë"UQôL EŤĹÚA#ĐV÷’(ő"Ÿ.ń'Űz‹QBRŔącÇpöěYDŁQ‰ ĺîÝťągĎÔÖÖJźBu044„žžžä㧞z*!Q2bČHˇýŚ(2Ť +ĐV÷óă*Ľ×†‰´ ë"UQDdddäo|>Ÿ˘'­Ĺ1k 8´ňćő"@BFĆ׾„¤ŇÓӃŽŽŽwîőőőŘż?věŘĄŠ‹ŚÔ\¸p!){BôI °”‘ńÇ5˘Hˆiú\RŒ­ş€:˘!.—K(Rý1Ž/Šˆđ<ď]ślY7EE”Íjƒ]”z‘ľőhö IH ŃhÝÝÝxóÍ7gՏ8pŽNůDŁQ\ž|9ů¸ąąQ5'V2ŇţěAŹ­`_Ě{hĺ÷ОUW@ чÁe˖ułš¤šŽh•3ƒƒƒ?îęę‚×ëë- B1GE€D˝Ëů"k+ęńî7ހšĚ˜ó1Ô&!Š?~|F*AŻ×ϐ"7şťť’ŐŘȈšĚˆwżńSŮ÷•]E[¨#âőzŃŐՅÁÁÁ‹ő˘‰ĎónłŮ<ÚŢŢ.Ö[Žă`6wh|ßňç°Ůš˙9IHfX­V<öŘcÉÇ.\˜q%rçěŮłÉß××׍Ş0Xn2˛ŮÚX´óBĚf˝âodŰŰŰa6›Gyžw‹ő˘ö…Ăáž>}šWr+/čőĘnťbÁĄ•ŻäUźJ’97nLօDŁŃŃ"?fD—Ôä##Ť +ph%ű˝•„FS˘řš!Á`§OŸćĂáđĹ|ąŻŽŽx<Wz­˜ÍĺR/ARň)^% ɜúúúő ŠE–DţX­ÖŃ%ľ´óŚ"ľŒ{qŞ€Ž‡ńx<@ÔÎQE„çů N§s(}ŚhľĽ(++•z’˛Ú`Ďz*"IHv¤^ÓŰN‰ÜIÓ’^řűÔSOŠn˛­”2ňłŐ?(ęâT(++…VŤüë…Ăá€N§sđŰ˘ŻŻ:eP__—^z)ńŔńăÇqěŘą˘—.jĄ2ň|Í6lŻŢœóű¨˝^‹ŇRÔËțöövTUUŤe7•‚T`ŽýP ÎuTAłŕ }ĎźcŕYHHřËIüŐٟ…„‰ aj*ECň§śśťwďNţ˝páŽ?žźžžžäˆ}ŞŒŠ ůŤł?GřËɜą˜ŒlˇmÂAűžœŻÔŇe) 0ľHU  "˘–g@âƒVŹâĽ3W' + ůîŻţ§ľżNŽƒW3VŤuF4äňĺËԮˀgžyf†„twwĎzNú4[5ŽëśßÄiíŻńÝ_ýĽ(2B20Ő1wJěféŹ'Upćvť ő–˘a0čTzcÁ[k%e„Ľ„ô'îZ…˝iXoG.'R%$P*ÖŽ]›œœ:000§„Ö×ח|œÚ:­t⚩{Çô0—‘U†xkí!&ëU:ĽĽĹ/ˇŰ-úłt &"<Ďťm6[ŻjEŔdRÇÉ*_ĚŢZ{ۖ~ƒš„|şÄóŤŻŠRF҇juwwSť.Rë=REc.ŇwçUCT$Ž™ÂůŐ×fíĂRFś-ýŢZ{¨čŰtÔrMhoo‡Ífës€Y:Ňĺ÷űŰÔŃjKŠpuŤÎŒŘđ#Q$D`Ź<‚󍯉˛=š”¤FC(ˆÔvÜĹŇ\@`†ŒlٲEŃíź‹}WXÉČ?lřŹ:sÎÇPz˝VíşB4Äď÷ˇň} *"j‹ŠPáj"iľĄ)ÉýŁ´˜„'ŘS8ç÷’7nœąńÚ|é"?2‘ŠžžUěC3b g$ě,dDSRŤŐXôijľ¨ŇDC€‹ Ž¨ˆš>€š HH>2–Š„Ä5S8ˇú>aťňB˘×ëńÔSO%÷ôôPť.CRťa2iËMĽżvíZĹľó~śÄsY¤0YČÇqE/#jš!•*H "<Ďť—/_~]-Q˝^[”WĽT.Űo*şŁćЧž˘v]Lţ>Š¨ŻŻŸ!†ŃhwďŢm}ŹéśßÄĺž$#ůQVŚž}{{;–/_~˝ĐŃ@€Ď?˙üľDE ˘˘\Fœ)RKˆŔ§Küř`ÍUĹąŚďorůňe*PeL__ߌTËîÝťg¤ÁŹV+vďލ={ö$Ĺp``3˘*r%Ž™ÂkŽÎ*JÍ’‘Üŕ8ĘßOx ůüóĎ%éÖäVžçywuuuo{{ű:5ȈFSƒA‹ÉÉܧ*šHˆŔXy'ďĹ7?[…ĽĘ(œŰącGň÷@€jCDâý÷ßÇţýű$Raű÷ďG___2ZňŘcÍ“h4ŠË—/+ćßcÄĆŕ7˜ˆ¸ #ůtž 2LâŢ=eÝä‚Á ž]مڐ;wxÉţ/ŞŠVŒF˝ęďä&!BÝHoíŕâO–˜ô4ĽdÄchhgϞńłľk×b˖-زeË éééAGG‡b$¤ˇv0ŤzL ČH攖j`4ŞŁ>PĘÚÉDDm4TŚ› šJH*žÚ/p~•źç¤ˇë.6ă‚ȏžžžcÝS Tß|óMœ={V鹸f çW]ƒ§ö QŽO2’j:×KŐ)“ФU–ÓQ‘Ün7š››Ľ\ –ŹS]І…„LÝżżř]‡h"0lăäă˝xĘ[‡C•˘žWśčőzôôô$ U)R000ŤŐšlĺ˝{÷Ž"j@Ršm âr݀č˘Ý7>€żř]ţaÏrnËWsšĆhTĎdm!@˛h aDPgTDm)Âó8Š—Â ƒA§ŘAgbIH*‡Vž‚çkśĺ|ülńÔ~Ö\UÍxxB˝Œ˜Âř`ÍUŃ Rçâůšm8´rî$#‰ÁejŘYWŔápŔçóallě‡Rݐ‰ˆđ<ďľX,?}ýő×ů`0(őr˜QYiP\ŠŚ"pĐž‡ęż—óűdËXyçV_“]íAjAÎxsÇCőßĂAűžŸSĚ2Âq*+ŐłĂp0Ä믿Î[,–ŸĘ!ČDD  ý]<Ť)*˘´œb!%D`{őfźľćÇ0p+qĄväśE=ŇK(›Ű–`AkAŔ¤1ŕ­5?ĆöęÍ=żXeD-5‡ńx< …ţNęľČFDxžĆdž×ë•z9ĚĐéĘғBBžŹXƒˇÖţ¸`EŹ@˘väâĘ8żę&´ń‚˝/A¤2ĄăüŞk¸¸ňFÁjAĄ(őÇx˛bMVŻ+61tĐéʤ^3ź^/> ŁŃx˜çyŮ܉ÉFDŔď÷ża6›GŰÚ$mifŽÜ‹œ¤”Ő;ŢZ{›­9Ż!„š#˝ľƒ”Ž! F\3…ŢÚAœ|źĂćÂÖ-mś6â­ľ‡°Ú`ĎéőĹ"#ĽĽ˜LęiŐ€śś6˜ÍćQżß˙†ÔkIEV"ápř;gΜQÍčwŕAŠFŽá=9Hˆ€YcŔĎV˙űž˛+Żăä‚PĚúY›‡D&|6˝Yc!‹Qö}e~śú3:crAí2˘´´z&¸Ýnœ9sápř;RŻ%ىˆ0äLmQ‘ŇR ĚfyŮľœ$$•}˟ĂOW,hݐH×\śß¤îB„n˜Ëö›MÉzŸŽ>ˆ}˟cvL5ˈُŽÁ”@""‡áes!;CÎz{{UŐÎ z˝z˝VęeŻ„4Y7źnD@čŽ9żŞ°Ý „:+ŕüŞkď†ęAšŹ˜[2"§ó4+z{{e1źl.d)"jmçŘRáä.!BÝHĄ†ŸĽ3lNÜÁvŰoRA+‘5Ú8ş§#l…ŽŘn۔W=H&¨IFJK5ŞKÉČą]7YАhçĺ8.¨ś X,ŇÍQŠ„˜5ZůŠ$ЁO—řqňń^"#9ůx/>•¨ćHHĹZůJŢő ™ á8‹zć…´ľľă¸ œÚuӑ­ˆđ< …BÖŮŮŠŞÂUĐhJ$ůŔ+MBRi˛nŔń'ţV’T@ސPʆHgŹ<"š€‰TĚń'ţV”TĚB(]F,4Ů^sÂívŁłłĄPčĎäÔŽ›ŽŹ˙Żó<ďRcá*hľĽݏFÉ"PŤŤĆń'ސ¤Ť&•O§ťÎŻşFE­FLaœ_u Źš*Š€‰Ž˜ăOźZ]ľ$ďŻT1uĐj•š%ÇB¤¨ş¤^ËBČZDőހэ/Ȱ5HH*ű–?‡ˇŸř[Ôčl’ŽcŘĆšŐרíˇHÚpĎ­ž&Y ˆ@Î†ˇŸř[Ś]1š˘4Ńëľ0ĺŐŃČš¨Ś"{I-\UÓÄUą‹WŐ&!Ť v⍂nœ7cĺ\śßĝ뎠ˇvęHT̄6ŽŢÚAźťî .Ë$E÷|Í6â Q RłE)2"Çą ,đz˝˛/PMEö"Á`đGSSS~5Śh„)1ŠWŐ*!fí{đ֚KÓ2=ľ_ŕäă˝ř¨ţSÚËFEÜśńQý§8ůx/<ľ_Čb oÎ†ˇÖüí{ Rš-r—1Ď˝RÓÖÖ†ŠŠ)0ü‘ÔkÉEˆLNNţń™3gŕrÉ:ՕM ŹV#ÓcŞ]BRy˛bl˘#ˇ*¸¸ňN>ţ{Š’(!úqňńßăâʸUzII„(Hś{Ĺ9ˈŐjT]q*¸\.œ9s“““,őZ2E1Ő9<Ďťkjj~ůňË/ˇú|>ޞ˛Rę%1Eč_żËäXĹ"!Btd{őfüˇĎŽŕFä–ÔK˜ÖęŠýžÚ/P6ăŤc6<´B;%áMÄLâš)ÜŽ ŕÓ*żäusąĘ°‡Vž"Ť4Ěb2’Ď9IVç$9Ěsƒ`0ˆ—_~™_ştŠsxxŘ-őz2E1"###?Đétßmoo׍ąxUŻ×âŢ˝űˆDr˙\Œ’ŠP;rbřţçç'11%}_`ŘN\Üě7ą"hŊP%I‰ äă–%(Ť¨G*&ű–?‡dőË9ɈÁ SÝäTöövÄbąŘřřř¤^K6(JDxžr÷ݎŽŽÓ---hnn–zIĚ1™ô¸ŸG4š}(żŘ%$•jśa{őf8źoăC˙%Š—3‹[•ÄE¤D” Űm›ĐV÷’,ë@˛A2˘×kUˇŁŽ€ŰíFGG|WÎ3CćBQ"$f‹TWWť[[[›=Ԗ˘aĂ{÷ڞú˘‘„ĚF˜Ęş˝ş żđ˝-›tM:ŠRRu׀A+V˘ęޞ/H~żż k ɍZ]5ö-§×w ÍžGťüňŚFgC›}NŻď (H°ľâńxpđŕAX,–ŸňˆÍÖFŠ—DȌÍÖFütőA¸:đBÍ6ŐĎ’‘ŮÓS-KO0ü‘Ôëa‰ŞDBĄĐwŽ_żkkk“z)3Řľkěö;fú|>źóÎ;˘ż/I{šŹđłŐ?Ŕ鞯ŮFi›"Ƥ1ŕůšm8ÝŕŔĎV˙MÖ R/I5ŒĚ¤­­ ׯ_…BĄďH˝Ö¨NDxž÷FŁŃ:;;!§Ů"ƒmmm3˘#===Ét„ˆK­Ží{paĂ˙ (I‘!D?.lř8hßCé‘ Iŕt:ŃŮىh4úĎó^Š×ĂՉhé5™LG8ŔËŠ^d.9ţ<.]bߙARXRŁ$mö=XeX!ő’ĆŹ2ŹHÔ~PôŁ JFzzz ^ť— ŕM&Óžçߪ;Şhߝ‹p8ü}‹Ĺň[[[WşÝnŮěŇkˇŰąk׎i™wŢy+Vʘ‘şÉ’é¨ŐUㅚmxĄfţ=âÇw>FWࡎÉďG,NÎ†&ëlŻŢŒŐ6ßO"{X´ö.ÄĽK—’çä]ťvaÓŚMłjű¤@¨ 0‡ż/ńrDƒSsȋă¸:˝^íůçŸ×*MséŇ%ô÷÷ĂçóͰkƒÁ0ŤF$ľXUˆ–ä+#$!ňD R2đĚŮY?kooŸńßTę?Ř!ňŠÄäCž°˜*NŞ„ lŰśMr!immĹ{kFŁŞ1%# jŽăZœţĺ/)˜Ľ(řý~;v,Ż!vť}Vę&˘Ńxrťţ÷ż˙} ëz‘K—.͒5kÖ`çΝóŚZ"‘ČŹč‰ßďgfŢz˝ĽĽŞ8—9fŰŤ7c{őf‰Εń~\ďÕń~LLf֌Ú1i x˛"!OVŹĄ”‹‚s]ťÝŽM›6Íh°ŮlłÎçBÄ{͚5Řşu+ÖŹYĂ|-Á`Ď>űěý’’’ߊm^Č|…ˆ@(Úr˙ţýO[ZZ–°ŢŚżżĆă5kÖ`ą9&ƒAÔ3đ ŸJ2˘VěXm°ă…šmˆÉI/EL˛@ˆxŹ2֑x(1%D`Űśm3D¤şşťvíŠS§f I?úűűŃŘ؈]ťvĺźoŘB´´´  Âáđć—)E#"<Ď9ŽŰňÉ'Ÿüş­­M'ćž4Rä磴TƒŞ*BĄ°*ALÂS\ďÍIzÂýŠÝ)z9ŠŃŮPŤŤž;žŹXKŠPZށĹb€F#î” ›Í6#*"ˆĆO~ňœ?çΝ›5ť§§===Ř´iśmŰĆLHÚÚÚđÉ'ŸÄ˘Ńčľě#“ E#"@b?Žă^éččřeCCłâŐôĄÜzŃ5š’dd„dD٘54Y7 ÉşűŚžŠŕƤ˙ńáƤƒq?nLúT—Ö1i Xe´c™Ö†UĆ:Ź6ŘąĘh'éP!btÇ,ÄśmŰĐÓӓŽS§NĄąą[ˇnEcc#N:…žžžYŻťtézzz˜tŘ8NtttŔ+jÚG&Tß53fłů9ŽŰ˙ńÇ3)^íďëŻÁ`ŔO~ňYôĄ§3>~Ńh\ęe@”đÔ$nLú0ťƒÁ¸÷&sęÖ)D×Ě*Ă ˜JXŚMD9˘a$á("ôz-**ʙ3‰,z>>ţüŒI×[ˇnĹŽ]ť’űűűńÎ;ďĚ{Ł™ţülđx<Řźyó}žçŠy^Č|Ľˆ@eeĺoyžŇçóq,ŠWO:…óçĎ'łjŐ´tVb31E$cr,BŮü{ć‰{‰ĎוńžäĎiIĺdÓĎg˝~!yŽëżÎx,ȅŔ“kŚRŐpƒA“IĎô˜—.]ÂĽK—=G"źöÚk3Ň0őW5Ťá`ŽtÍŚM›đâ‹/ć´ž`0ťÝÎsw% ĺ¸Ţ˘Žă*­VëďVŹXQÇbňj$Ăá˜Ń cˇŰąsçΜ‹R…vŢK—.Ąąąű÷ďĎkŠĐŹ"[zČ2ëg ‰ČţyE„š`=#˜9'$“›Ăô¨Č|~ż?™ŽÉWBš››qëÖ-o X_Lu!Š­ˆÇq :îżđ z“Wç’ ńa~ńĹ3.hňŽéÝ8ů„ţć‚6”"˛D„ą:c҇•e2˝zލȋ/ž8o‚ĎçËkvkk+Nœ8Ĺb_/śşTTšé]Śđ<ď‰ĹbßęěěœóDš-Â=őC›Š„řý~œ?Ä;ďź3KBöä 5bśĆȦXç \$DxŢśmŰfüěÔŠSłşfň‘öövtvv"‹}Ť˜%(˛Ž™šŕyŢÍqÜŢÇ˙˛ŽŽ.ďNƒÁ€_|ŰśmC˙˘­źýýýčééÉX0.]şÄ´=Xč¨ ‡ŁTÄJDÁĐëľ0›őĚ;cr•­[ˇâă?NĽF"‘ädUV8N>|öň<ďfv`…RÔžç&“éČŢ˝{áń°SĄ7}."‘.]ş„×^{ ‡cN Iß$OŕO˙ôO™Ź/ŽăPQQŁQÇüŘAé:TT”‹.!@b7Ýl#éQ‘óçĎ絏X*{÷î…Éd:Âóź“ÉA‰Č4ápřűUUU'›ššxV2’ŽPŕôÚkŻÍŰf0°k×.´ľľáΝ;3ţ,—/T6zTVŽwŸ ˆâ‚ă8TVa4˛íŒćßŔîÔŠSYKÄŚM›fĽÓOŸ>×ú€„„455ńUUU'‹ąMw>Š>5“ĘŘŘŘ>ŁŃřÄłĎ>űU–{ŇŠ—šę>Ňwyt83ň’vť=ďÍđ2AŤ-ĽIŹA0GĚIŠ í˘+4d“ž7~ǎK>Îwzް‡ĚÔÔÔ§ăăăűEń@‘xžNNNţŃÄÄÄ­ććfƒl:Š|>ßźbłŮđâ‹/â'?ůIRBΟ??ëůb¤dćCŁ)AU•‰y+Aʼn^ŻEU•Š "Ô鼜éÎ×Ń8×듣ňiŃ´éNLLܚœœüŁbmӝŠˆ¤1˝'ÍÎÉÉÉ_577—łHÓěÚľ ~żƈŕôˆ@˙Œ>váőbŚd棢˘Z­áp”Z| ‚ČŽă`6ëE˝ŠillÄĽK—ŕóůfŚŽXąbFdyŽČˆĎç›!!鲹uëÖäÍb>477Łżż˙n<ßI2ŠˆĚĎóžx<ţk׎EYíGóâ‹/ÂnˇĎˆD"łĚźP)™ůĐ뾢ď~I„úö‹;˛*ȇ0|LŒš˜ĽGFRúűűgľé ăňĄľľ׎]‹Ćăńo{›î|PDdŚ7Čűzggçď€DťU>_–…ŚúÍUŔZȔĚ|'”Éɍ†'bQÄŐžđű最*ČČ|‘‘Ô(uz:‡­­­čě쀢XśY€éÎŢÎÎÎ9?äٲЇ\˜ŚšŠT)™šŕ8&“‹şj‚˜Žă`ą *!‹ąPdD›Í–óVóŃÖÖ&HČ^’…!Y„é>ď˝yGEćChëMEę”Ě|čte°ŮĚ(+Ł`A(++…Íf†NW&őRf1ŸŒTWWĎő˛œq:čččâdzpB"’<Ď;kjjţËŢ˝{E‘‘cǎÍĘMîÜš“ůű°BŘÂdb?‘ e!DK­VyĎ bš#úB8NěÝť555˙…$$3HD2dhhč-aú*K™kbßÖ­[™‡ ĹŔ`ĐQ!+A1Bý˜Á ŒŠĚóÉČ\…Şš HˆÉd:244ôVŢ,HD˛ żźźü]–2’^"´ő*aÓ*OŅѨS䦙sÉČŚM›ňŽ”R^^ţ.MMÍ‘,‰D"ÂRFŇżbTnŁQŻČ“AŮńŕćC>ŠŮ’*#ů+fJH$ůFË,HDr€ĽŒ¤śő*%%3ŠŃ9çŠ ‚ČŽă™ AFHB¤‡Zr$‰ü‰Á`ŔŢ˝{ż $úĹsĹnˇc׎]hlldľiŒ Y­śUU&D"1LNĆ(]C2B(FUĘL) †Œd†Á Cyš–Ň5!( ł8$!⥎h‰DţDŒ ŹjCH×TU™hß‚ˆ˛˛D”˛˘˘œ$dR'Ś’„°‡DDÂáđ÷…˝iXěÚŤf„ѕ•FŐľ„\ŃhJPYi¤-2 ­­-šw MLş‰ĄĄĄˇ8Ž‹tttü2 Rtd´ÚR,YbŚv_‚jÇ͎ÖÖVtvvŔŢĄĄ!§ÄËQ-$""Âóź“ă8tvvţÉHíž‘H ‘Hœ„„ Ŕq - šzœ)ŠBťčŠ ĹÂEfúźţĉц†ƒAŠ—${ŐűzŘlfOy tÂ$žKTŒš Á` 8qâDŔz’ń!)<Ď{bąŘ×űűűď677“Œd Aä HnƒA477Łżż˙n,ű:ĎóŠ×T ˆžç=ńxüˇnÝň~ík_ťďńĐç;S!ŠŞ2Qh™  uc:ěđx<řÚמv˙Ö­[Ţx<ţ ’ÂA"R@xž÷őcccŸ655ń$#١єŔd˘ A¤“Ą´ěđx ëIB }Z ĎóÁÉÉÉ?*--=ľ~ýz*`ÍJŮDJÁäÓéÄúőëQZZzjrrňxž§Üy!‘žçƒŁŁŁßŸľˇˇK˝$E’*$jܢœ ćCŁ)AEE9 Hž´ˇˇ'•ŽŽ~‡$DčĚ-!ÓĂqö>|­­­TĚ#ÇAŻ×bɒ„Đ¤VB­”••˘˘˘K–˜Ą×kI@r$ ˘ľľ‡€˝4¨LZčŒ-1ÓłFź'Nœř_GďvťQYY)ő˛‹^Ż…^ŻĹ˝{SˆDⴗ Ą ôz- -MAe€ĐsíÚľ(€oń<ď–zMĹEDdĎóîX,öő[ˇnyív;ą2 ´TƒŠŠrTWWŔhÔQچPM ŒFŞŤ+PQQNÇťÝÎßşuË;ݞë–zM‰ˆl:j8Žť˛yóćűTÄĘĄŽdÉ3,tş2Š—D ˘Ó•Áb1`ÉŞ˙`‰ÓéÄć͛ďsw…:c䉈Œŕy> 7đ<Œ6ĚcĎĚsýaRę•+WFŁŃč =cä ‰ˆŒáyއż:>>ţ;ŞĄŘj5bÉ3L&=Ě(-ŐŔdJDáŹV#ľŢŠŒP2>>ţťp8üUŞ‘7$"2G¨)))ůůŢ˝{iŢHĐhJ`0$öë )!r%U>„}’(ő".Â|˝{÷˘¤¤äçT˘ č[Ą‚Áŕě|ď˝÷bÍÍÍ TMa˜KJ(}CĚGYY)ɇDx<477ă˝÷ދŘ9}Î$}CĎóŽh4úč͛7?kjjâ)USX)ąZÉü>…Ř‹!Ľ'Ô|X­F’ p:hjjâoŢźůY4}”çy—Ôk"2‡ž- ƒçyo(úęýű÷RŞF:Ň/@••‰ ĽpÔOiŠƒ••$¤R“šŠš˙ţŃP(ôUžç˝RŻ‹ČŠ1+”p8ü}ŽăÎż÷Ţ{'<Îét˘ĄĄAęe-Zm)´ÚÄ׉çyÄă÷‹Ý×_ŢĂÔÔ}‰WGäƒFS‚˛˛Rčt‰cyŕńxĐÚڊëׯǟ‡) ˘P("˘`„T×ëíYż~=‡ÔK"ˆ–mÁK–˜“›ńéőZ Ů+Ś$íJý÷ÓéĘHBd‚ĂáŔúőëáőz{(Ł|("˘pŚĂ*++˙îŕÁƒáršŕršhă<ĄŃ”@ŁŃBŻO<"&÷îM!ŸÂ—_ޓvENYY)´Z JK5ń9Á`---čęę‚Ĺbů)¤Şş=S Ó_ČőżýíoďŘívŢ墚"DLŒF=ŹV#zȂŞ***Ęa0č¨+GDĘĘJa0čPQQŽŞ*zČŤŐŁQO™ărš`ˇŰůßţöˇwŹ' Q$"*‚çyĎäääęňňrçΝ;АUA”–j ×ka2͔‹ĹŁ1!'”ÖÉĄŽĂhÔÁb1̐“I˝^K…Ĺ A(HÝšs'ĘË˝“““Ťi@™ş [/•1=źçĎ8Žű—'Nź{ńâEíŰoż]ŇÜÜ,ő҈,)-M¤ ѓ?żwo SS÷§˙Ë'Ďóźt‹•ŽăPZŞ™N}=ř= †zpťÝx饗î˙áˆřîđđ0…zU‰ˆJáyŢĹq\íÝťwO?ýôÓÍ@{{;Վ¨€TAIçŢ˝)ÜżĎ'ĹDŠŤERT‚hâQR‘l¨œ`0ˆöövtttŔfł}‹Ĺv҄TőB"˘bŚż¸Os×rôčŃ.—Kçt:AŃő"\ …Vâšŕy_~9•|œ.(ńřTúKrbžZ­V3ďóĘĘ4T§Qä¸Ýn´śśbdd$ŕ…;wîPDĺpĹÎ-V8ŽŤ´Ůl§ý~?EG‚iQˇßď§(H‘@"RdpעÓéŢŐétşÎÎNŽĽĽEę%Qä¸\.źüňË|,Áwi.HqAeřEĎóŽX,V+tÖ´´´ŔëőJ˝,‚ ŠŻ×‹–––dGL,Ť% )>("RÄp×l4˙?Fc;|ř0×ÖÖ&ő’‚(^ýu~jjĘ?99ůÇ<ĎťĽ^! )bxžwOLL<ÄqÜĎ<ˆ††¸ÝnŠ—E„ŠqťÝhhhŔÁƒÁqÜĎ&&&" )nHDa*ë#_|ńEďÓO?MƒĐ‚`Ž0˜ě駟Ć_|Ń ŕšŽJ$"Ä4<Ď{ďÜšÓ`§Ëĺ Řívž6Ń#‚‡Ó[OěźsçNĂô>YA"B̄çyW(ާt Aů’ž† …BőTŒJ¤C"B̂çů`zş†şk‚ČĄ&= CsAˆš !ć%%]óôŋGyä´ˇˇSýAs" %{ä‘GpńâĹQOS†X jß%2ĆfłýĺäääëZ­VKížA¤"´ăĆăń¸Ńh<ě÷űߐzM„2 ˆ‘1~ż˙h4Z#ԏÔŐŐÁétJ˝,‚ $Äét˘ŽŽ.YFkHBˆl !˛"ľ~$ŸÜťw/š››Š • Š ˇŰććfěÝťápř$¨„Č"'xž÷ŽŽŽ~Ŕ#WŻ^í}úé§IH˘ä駟ĆŐŤW{<2::úŞ!r…D„ȋԂV‚P/s˘L !˜Ŕ󼛄„ ÔÇâ–zm„: !˜’*$Ÿ}öŮu‚P&ŠňŮgŸ] !$"„(đ<ďž}űöŁ  A(Šš" ˇoß~”„ BTćKٸ\4ĺ™ ä„Ëĺ˘ ! $"DAH’7ntďÜš“搄 ć€ěÜš7nÜč Q`h˛*! ÇŐUUUý÷ąąąç, ÚÚÚĐÖֆĘĘJŠ—FŞ' ÂápŔáp  ĄŞŞęäŘŘŘŠ†BR8ŽŤłX,ŻÄbą6­VŤÝšs'×Ţގşş:Š—FŞĂëő˘˝˝§OŸćăńx\§Ó9BĄĐBJHDYŔq\%€łŮüßĂá𒦦&´ľľĄĽĽE꼄âqš\p8čęę‚Ůl ‡Ă?ŕ˘)¨„ Bđ<äyŢ9>>nCZ‰Ăá  "K„pÓë?ĆÇÇm<Ď;IBš@BśpWˇtéŇż…BFu/żü2Z[[ŃÜÜ,őŇBś¸Ýn8NtvvBŻ×Ç,Ë?ŒŒü Ľ_šB"B(ŽăZ­Vëë@ ÎnˇŁ­­ ­­­TÜJHD?œN'|>ŹVŤ7ćyŢ)őÚb1HDEÁq\ĂŇĽK_˘$ßţöˇŃÚÚJľ$DQâršŕt:qć̙ÔčǛ<Ď{¤^Ad ‰ĄX8Žk­ŤŤűž×ëýż- Z[[ŃÚڊ††Š—F˘áńxŕt:át: …PWW÷Ż^Ż÷)úA(BńpWˇdɒďĆăń?‡ĂK„ÔMKK ľŞŔëő&;_|>Ěfó¨VŤýűŃŃŃwŠöƒP:$"„ŞR7Á`đťąXLżnÝşdꆤ„P‚|8NôööB§ÓE+++ßĽÔ Ą6HDŐÂq\KUUՋ“““ŰIJ%0—|ĆÇĆĆŢáyž6h"T ‰Q”r…äƒ(vHDˆ˘C’ŠŠŠo†B!ëşuëĐÜÜL…ŽDÁ NÝn7z{{aąXć"ÉQŒˆE Çq &“é{z˝~ťß︢˘‚ßšs'×Ü܌––šSB0! ÂĺrÁívăôéÓüřř8głŮnGŁŃ'&&ŽRÍQ̐ˆÄ4ÇŐhYślŮw7ŔşuëĐŇ҂ććfščJd…Űí†Űí†ËĺBoo/`ٲe݃ƒƒ˙ŒÄ>/^IH2D„ ćă¸–ĽK—>;55ľĹď÷?l6›ďó›ß,¤„Ň8D*')/^ź‡KR˘ç)ĺBsC"B0-iŽŞŞÚ!ԖTTTđO?ý4'H ELŠ ˇŰ”>úˆçRj=ÎpSԃ ‡D„ r@“eË–í ‡Ă߇ĂK ŠŠ ŠbB5&ę  Î<*a¸űIDATŽŽ.€Ůl5›Íż<‚Č ‚`Ŕ´˜4˜LŚ­&“iÓđđđc`ˇŰ“RBQĺ ¤X<<|> ŚŚćęÄÄÄĽ‰‰‰ó<$‘?$"!Ç5hXślŮ7SŁ&‚œbRWWGłL$ÂëőÂëőÎ))ю‹HH‡[ŇĹ„J!!ˆÁq\%€LËÉÝťwŸuŸ755ĄĄĄ•••$(ŒIŽ`0Ç“LŻ€Őjő–——˙› HˆGP˛DA"BĂq\€:$eK<_á÷űţ|ÝşuI9ŠŹŹLĘ uíĚÄăń$%C¨éƒÉÖY°ŮlˇľZí­ÁÁÁ H‡—fx„´ˆ„L™”JÍK—.]155őd,[.¤xŔl6ßňÉ'KRĹD5Ɋ ‚h¤ţěʕ+÷Ăáp‰đ\łŮ<ŞÓé>×h4WFFFnp’p„óóóÝšŻˇŰíkO=őT•úľ×ëÍúyî×ŕt:áńx¸Œ7 ořžßďßňë>úh-V!‡–––I°X,ÍÍÍ}އÂB˛BB"B…1ćŕŕUE#‹}96ŤŻQ#S<œN'W‘ĐUdÂá0@Ö÷r…ĹnˇßˇŮlżË?€°˘(}FOÄvˆ„ÎŹ ‡€§ŁŁŁ?ďžŸŸDýyww7œN'ź^oZ.Ě$źPĹDýÓď÷#crr2ýš–––ßZ­ÖĎďŢ˝{ @Š( Ač‰A” Ƙ€)áx~eeĺk™š}}}iÉđz˝pťÝpťÝ[Ž(€`0ˆ`0˜–“@ €ńńńôĎ].W°śśö˙ť{÷î/‘”€˘(ג‚ŕ‰Ahc̋uéˆFŁ˘.ŠtvvÂăńŔăńpčLŚ ĚĚĚH-ńŘíöSĺDQżŽƒ%“B"BXOő444ěkhhŘ;;;ű$đP:ź^oúOÂř¨b˘ţŠĘI[[ۧ‹‹‹7Ż"%'A]J&€D„ Š`]<ź/dF;úúú˛¤Ăétę:N‚jΉ*'ę˛NFÔä]~‚(‚ČU<ššš$“Éç#‘ˆŤąąQyîšçE;*“L1š~ýşňŕÁćp8B‹ĺ— —AbByA"B[ŔhmmýN2™ěŸŸŸÄnˇŻ=˙üóU^Ż7ő UJü~?~ůË_ŽEŁŃŞ–––ßZ,–ksssď)Š2Ś÷ ˆˆÄ:ëQŽŽŽ?ť{÷n/*€*‘/Ş”ŒĽKˆ;::nŢ˝{÷çĆ(ZB)HDˆŠ†1ćihh8&IҟÎĎĎ?ŇŘب:tˆy˝^ PŽÁ…p8Œąą1řý~\źxQyđŕkiiů­,ËżX\\|›z™• ‰Qq0Ćššš^Vs=şťťáőzáóůhš…( @ŁŁŁđűý˜œœDFnÉ;´„CT$"DE ĘÇŇŇҟĆb1Šťť>ŸÔÃЕ`0ˆąą1ŒŽŽbrr6›MŽŻŻ˙I Q)ˆŚ…äƒ ’˘!!LcĚÓÚÚúj8ţÉ!2›I‰Óéü—šššˇ(§„0$"„đ0ĆÜÍÍÍߋÇăF›;;;144DňA˜UJFFF033ťÝ~ßjľţß÷ďß˙Şž!D‡D„ƘĎív˙E0ü#‡ĂŸĎG §„éQ]GGG‰Dŕvť˙ß`0řEQFőA‰!ęŇK$ůsY–mL/˝DĽĄ.Ý\şt ’$ĹÇ?ÓŇ !$"„0Ć|.—ëd(rŤK/>Ÿú|R}JFGGÓK7.—+ …^Ł( !$"„aaŒš[[[˙V~ ÂçóQ‡S‚ؿߏŃŃQœ={63JňQ. aTHDĂÁóvttüÝÝťw{)úAő%Yo/˙*Šâ×{l‘ ‰acNvťýÇŃh´šŻŻ/]ůBDi¨7ăăă°Űí÷ŁŃčEjż›°Ţc#BWcn‡Ăq<‹ Y­VëĄC‡Řđđ0•Ý„ƒA ăâŋJ<Űlś‘H$rŠ–m=!!t1ćnjjúńÂÂÂw;;;áóů044DË/QÂá0FFF0::Š™™455ý? ˙•„„ТŹäć Ăçóé=,‚¨XFGG1<‚Á .^źˆŻ~őŤ˝ŽďÚľ+Ŕóę=6˘2 !4%S@ž|ňÉîëׯĂď÷S*AŒřý~\ż~O>ůd7HHˆ2A"BhcĚűČ#L!G@¨AŻ×ťAHyä‘)B+HDŽdF@žň•Ż("R!0ÜťvíşŕâńăÇĽőÄ1˝‡EĚḚ̌ăǏKH%ł^§čHĺ@‘ €16 IŇůÖÖVŰčč(ĺT8Š˘`u5™ţzu5‘őóx<™ű+—Ť~Ă÷†‡‡łţĚ$ZÚô8VŤ%ë뚚ꌿ[ŔŰô÷ˆĘŔď÷Ăçóann.&ËňaŠŽ˜Ÿę_Bˆ cĚŮŇŇr€÷ŘącŚj““H$ąśŚ ‘HBQ$“ ’É5…Ckś:ßĆďÇ6}*(K,ĆŞŤ-¨ŞJýI˜5™uxxŘöć›o^Üľk—~~ţ˘(a˝ÇFh‰ˆIQsAjkk­×Ż_§(ˆ‰H$’H&×Ö˙TŇ7[tS–ŐŐÍފ‰**ęßIRÄÇétbddxĺ•WžełŮî1Ć(wĤPŽˆÉȨˆšxřđaé“O>Š" “D"‰XlKK2BĄ%ÜżĹďÁÂÂ""‘e,-Ĺ ËqŹŽ&L'!ůZbJ@–ăXZŠ!YĆÂÂ"~˙űîߏ"ZÂҒŒXl‰ÄćËM„ąńz˝řä“OŞ>,¸H•5ć„""&‚1抯Ż˙ו••–‹/‚’QĹ!‘HŽ˙ˇ†ŐŐdٗQĚF2š†drmĂżcMM5jj,¨ŽNEN(zb|ÔFhôŐ××`ŒýoŠ˘ôÁŠˆ˜„ő…ó›ßÜE1ĆFQ”ŹH‡ĺxđ`ËË1’ Y]M`y9†VŇѓĚČI%F–DA­Źůć7żš ŔÇԕŐ<ˆcĚít:?ŒD"?xă7ŕ÷ű)!Ő`$“kĺ8$"ł^–;ĺvť{>ţřcPwTc (J–xЍM,rĹQ}˙(bb†††đńÇĂív÷H’4E;úŠ ‰ˆ ŘíöŸ¸řŇK/Ůü~?<ŢCŞhâńe,,,â‹/x˜ULԈÉÂÂ"eÄăÍҏǿߏ—^zÉŕâúœH‰ˆ`0Ć܇ă?ŞŞŞŽ9s†vĘՉ̨Ç_<@8ź„ĺĺUgT‰DËË1„ĂKYŇIђňŁ&˛ž9sUUUÇÇĐRxˆ„şó裏~e||œů|>˝‡TQ$“kX^Ž!˘‘"WHCĄ”R$Źźř|>ŒłG}ô+´T#$"‚°ž!NK1eF•……EÜżĹâ˘L ŚÄ–ŹŽŚ–čîߏbaa‘¤¤Œä.ŐPU8ˆƘÓét~¸śśößh)Ś>NIŠ’LŽaqQĆü|Şü–$„ R¨B2?ŸÚ’€’ł Ăăń`||œ555ýázE MÎe„D¤L0Ć+ůŁvbÝ˝{ˇŰjľţÉHů )Œ1Ífűőă?^K•1ůŁ ˆ!"Ô Iţ¨5?ţxízy/ÉH јőҰ>,’<ČšD ˘82—lHHňĂét"¨ĺ˝SyŻöP˛Ş†Ź_Ŕg1::ŞóhÄ@–ăˆFiÂ$žd&ľÚí$ÉŞ÷ :gŸ={ö c Š˘Œę: Choo˙/Μ8q‚$$âńDşIAhƒ˘(é>$ń8őŰىŃŃQœ8qÎŹĎ鄐ˆh€Ýn˙ÉěěěßÓĆu;“H$ -!^˘L‚(ÉäÂá%„BKÔ:~Ô ófgg˙žŸi-ÍpŚŽŽîŸWVVžGĘśGQDŁ2ľŤ&Y]M`aa’d…Ý.Q’-Pçňď˙űÇëęęËËËŽďˆĚ‰GHBňcy9FI¨a d9ŽXlőő6ÔŐŮôŽ!ɐ‘ďŐŐՁd„$"œ ٙx<ĹE™BÁa@EÁ⢠Y^ECƒŤ•nšŒh]i ŮZ†!qH$’‡—hšf HFřCÉŞ%B˛=˛Çü|”$„ ̟Ýĺĺeźűîť%Ççóá̙3XYYů^]]Ý?sZEC"RZIČüü<ˇcéE2š†Ph‰Ęq B`ÔrßPČUm7nÜŔŐŤWńÎ;ď”|,’~ˆ‰VňÎ;ďŕő×_ZF––d,,,bu•ú„PŤk––d˝‡R4óóó¸rĺ €”Œ‘"ĐRBnܸĺĺeLLLp;nšH$’ë“UÄ„ŮPťł.,, ™p~ĺĘ,//§żžqăfffJ>.ÉH鐈ˆÖ˘ňĹ_p;v9PŁ "NPAäĎĂq˘#óóóYó+ôôô łł“ËńIFJƒD¤ěvűOĘ!!đůçŸs;ž–dFA‚¨DŠŽä&¨ÖŐŐá…^ŕzŽLĄŹ…A"’'ííí˙eqqńx9$—ĄÖ,/ǨE4AT0ę ËËĆ}ůěłĎ6,uďßż---ÜĎĽĘČâââqڛ&¨HŹď˘ű÷吐őşt)á:䉢(‡—)• ˆt#´X,§łÎp}GŽ^˝šőuKK öíۧŮů2úŒü=cl™víÝ‘X—3'NœĐ\Böî݋]ťvĽĂˆŸţ9ęëë111Ď>ű °{÷n|ë[ßŇÄćó![Ľ’\‚ 6°şšŔü|ľ°Ůjôd͝*/żüňŚŻ™™ÁŋŃŇҲĺkňĹçó!ŕÍ7ß<ĂÉČöˆlcĚŕĚŕŕ ×]tŻ^˝şŠ„źüňËYšwß}7+ËH…oܸŁGâńÇç6ŚP3ć‚%B_EA$˛Œş:ęëmşGGrsCzzz6̛ËË˸rĺʆČIŠ2222‚p8ŒłgϞaŒE ”t@C9"[ŔóŘlś_btt”ëą÷îݛľä˘J€ŹIŽ„d~˙ôéÓeë5"Â:0AĆÁůc7nÜȚ#7KP˝qă~řĂn^}FFGG188›Íöëő[bHD61ćąZ­˙śgωˇ„ŠÄĐĐ:;;ł$D%wŮĽłłsK‹×YŽë>Ą!ęŒ-â7›3Tgff022‚wŢygÓžÇ===\Ć2::Š={öHVŤőßHF6‡–fr`Œ9].×ĹÝťw×úý~nÇ}çw˛Â‚ŞŒÔŐŐmxmgg'ćççąwďŢŹOŽĽOLL”>܎VLľĎAĺEm'ŃŘX[śóćFCÔŐ­–a2_÷ /p“żßŻ×[űůçŸ_dŒ}CQ”0׉HŒ1g}}ýż744ěöűýp:\ŽŤ&ŚÎĎĎgE66“ءoöíۡĄbfďŢ˝YݡZş)•dr ‘Č2EA‚ŕ‚,ǑH$ápÔÁbŃ6żY4ä…^Ŕ76ÍťR˛˙~ěÝťW“19Nřý~|ýë_ߏÇ˙1ö4ÉČCHD2hjjú‡D"ńŐ÷Ţ{ń–ŕa=űNś˝]ÉîŇŇRúď[‰L)Äă D"ËTCWÔć‡GŹVín=ę6*---¸qăƆę 5‡îßż?ŻrŢůůů’ޝN'Ţ{ェžžžŻ655ý€?+ú`&ƒDdťÝţ“ĹĹĹď~üńÇđxř-ăĺ&”žűîťE‡ý>ű쳏ăń.-ÉÔ!• ÍHő ZB}˝ őő÷ăĎĎĎo¨”™ŸŸß0Ť˛wďŢźčŽ^˝Š+WŽ §§§¤ĺpÇƒńńqöo|ăťvťý'Ńhô/Š>˜‰ AşWČń3gÎp•8tčP–‰Ť{œŸŸÇéÓ§łžÇ+Œ¨ŽăĆbŤ\ŽGąKK1$“ ěv‰k‰ďNŠ Ď>ű /^LWn—*#gΜÁ÷ż˙ý㌱§#T5ƘŔ™“'OrmXŚŇŮŮš!ěWhľËĚĚ ^ýőŹpăf9$ŐLŽ!Z" !˘Ź¨yÉäˇcnąŘťw/~řĂbßž};JČňň2Ţ}÷]ŒŒŒdÉÍă?Žýű÷—ŒžŠ›&Ž­)T*NŕÁƒ$khhŕ“ÄZHšŒš[m“ŰS$WBnܸ]ťv˝‰žZÖŰŮŮŮép8ŽřfQœŠťÝţĆŘ7~őŤ_q)ÓÍ,ŃUecł’°ÜÄ՝˘"řáˆwß}—[§?jRV90ĆPScc ŐՖ´\¨_kMŚŘ¤žÎ}…mĂď$I(Š’–őëŐŐ$-!VËË1Ź­)ek~633ƒú§ڐäşoß>ěßż?knŢŹíťÚťœN'ĆÇÇ١žő­oTj%Mʼnď ™wß}wĂvj˘ÓŻ~ő+źüňËé&fjâŞÚŐo§¨ŠŒđ螪( ľj7)ŞpTW[P]]‹Ľ ŐŐÝ7+U6JKęN$’H&אHŹ!‘H’ ˜ľů™ËUŻů5œŮ RehhhÖ[IČVÝą ĹăńୡުŞÔJšŠŞšaŒy$I:uâÄ n2ű÷ďßršd~~###YÔĺZö•+W4ßźN­Œ! ĆlśÔ×KpšęŃÜlÇŽ]p:ëŃĐ A’Ź¨ŠŠRBv"%\Ր$+$8őŘľŤÍÍv¸\ő¨Ż—`łŐ˜ň˙˝ŇPéyVÔlĆË/żź!˘‘›—§ľ„¨ř|>œ8q’$Ş´=iXĽşF˙°R’W+FDœN燍ßřä“OŞxľoß ľł_ŽUŤ¨ Pš-‡_ýő’ÚoIˆx¨âQS“úÓčO÷F‘\EA<žŔęj’ÄD0ô’‘Ç|CÖáp_˙ú××ü~?~öłŸgŒíQ%XÖ”S‹HccăľG}ÔĆŤsj)ěÝť7KH2ç¨?/ś)N&ęz*UÉčɇ˜¨Ťş:I‰¨ŽśhZŇťYbjćĎJi\V,ëWmżůÍoŽřòžźŒ˜vv´Űí?Đ5::ĘĽs*ÔnŠŻżţzÖ^1źŤfʕÜEl$sمÂýâCď§1ĐZBfffśmVĆëAąPœN'FGG kýžfJLY_SăÖ´Œ7ސôôô`bbB“ œ"#ĺĹfŤIG?s’š|ŁFIh×jíŃZB€”tźüňËiÉĚ áŃ=ľ<Ţ|óMśŢě쪢(cş F#LWžťž2őŇK/ŮÖM˛˘ĄŽŞÚaąTĄśÖŠÚÚĘ­źĽ|W+EÁĘJ++q͛oU"吐Lnܸ7n”515_|>~öłŸĹdY6]žˆé""‡ăçnˇŰy!F€"#üĄčĄÂKç“P”„/ĺ–ŕa.ŸŃ$x˜/ “mŽgŞĹN§ÓůŁH$Ňc¤ź#@9#ĽŁŢpš›íp8ęHBˆ Řl5p8ęĐÜlG]­bŁd<ĐCBTŒ(!ŔĂ|‘H$Ňcśţ"ŚƘ'‰üŕ7Ţ0d^ˆŢŒ‡ĹR…úz --v44H”¨HěˆĹR…††Ô5S_O×LĄč)!FÇăńŕ7ŢŔzÓÜčLń aŒ9ëëë˙ľŻŻCCCzGSJÉé!É‹Ľ*Ýí´žžžn‰ÂaŒĄžŢ–îâJB˛3<$Älyš ĄŻŻőőő˙Ę3Ečß9"­­­˙ceeĽelĚtÉÄYČrËËń’>¨”3˛=Šˆ ’dŐ{(ş Ó—ełłłéŻ˝^oAÇĘÜ\˛­šGç$ IDAT­ ’$ĽżvťÝEŽPL$É I˛B–ăXZŠQbë&đ’Ph uuVS†ÇĆĆĐŮŮŮŇÚÚú?üďz§T„‘őRÝď_źxŃÔy!jŰv…–HF8S ˘JFłłˆĹb;ţ~Ą"2>>žăkl6ÚÚÚ<”“Ü?Í Éćđ”D"™ž+Íúyv:8{ö,;tčĐ÷cď‰^Ň+´ˆ0Ü6›í_>ŒóśăO$’ˆF可IFř`6QŁłłł‡Ăéżç#z‹ĹŇ{,Šf Œ**mmmp:éżgFWD„„ä!ź%D%•Q]m1íRôŔŔqţüůaŒľ+ŠÖ{LĹ"´ˆ´´´\Ź­­ľššTW•ŽÜuO’‘Ň0ƒ€¨Ň Ó‰DôWTQÉÝŇáp¤ĽÄív +'•.$ZIHć÷͜722‚_ţň—Ö•••‹žÓ{<Ĺ"ʈŹ/ÉxŻ_żnÚ%™drmS Q!)œ‡}ÄkB‡ Óâ177§÷t#‰ ‰ŕΝ;éJkkkZLÜnˇPó‚$YałŐ`y9Žĺĺ˜é.m%$÷çMM ŚLv:řéOZőÜsĎyc˘.Ń)"Œ1§$Iç;Vđşľ((Š‚HdyÇ ‰d$$É ť]F@dYĆÔÔTZ>ĚíŕÍÜÜććć099 5QĽdϞ=†˜¨U6uuVDٞć[ŢëI9$$óu‘ȲiK‚˝^/Nœ8ˇß~ű#"őőő˙łżżß”ËÔ2]žT’ŒX­Ő°Űľ—ÇÔԁîÜšŁ÷Pˆ"‰D"Çřř8{ě1x<Ă,ÝÔŐŮ`łŐ ]1t2Ť%DĹŹe˝8xđ Ž]ťö?üŢăÉ!DÄétţČbഘąqYžeşĹ`v1ZD–eÜşu‹˘&äΝ;¸sçN:JňĚ3Ďč^ląTÁéŹ7ltÄȢۏe˝###čîînq:? ‡Ă­÷xvÂđ"ÂsřÁoźa¨5[^h˝ŢkVŠŽśŔá¨3ÄÓĚěě,nÝş•î_A˜—Ě(Iww7žyćÝKŐčH$˛l¨Ď§‘%DE]w8ę4;‡¸ÝnźöÚkě/˙ň/Ŕ;Ľ(JPď1m‡áE¤ĽĽeěK_ú†††ô w––dÄbŤšŸÇl2R_/Ąž^˙(H0„ßďßĐ~œ¨ Ô\’ÎÎNx˝^]”,–*455`i)†Ľ%yç_ĐQ$D%[ĹҒŒúzsUŇ att˙ůŸ˙9ŔŁ÷xśĂĐ"˛Ţ3¤ű… z…;ńxKK嫜0ƒŒX,Up8ętO0 ¸uë5#¤6ë;{ö,Z[[ńĚ3ĎŔăŃoÎŻŻˇÁfŤF$˛ŹËž5˘IˆĘŇR 55Ő°Z }K,˜‘‘<÷ÜsÝFď-˘\{ cN‡ĂńƒƒƒŚë’LŽ!Y.űyˇÚ@ŻT)ˇ Řl5hjjĐUBFFFpéŇ%’bsss¸téFFFt‡ZYcłŐ”ýź"JˆŠ^ňŚ%^Żƒƒƒp8˙Č3Fé×&VDÇ_+Šâ4c‚ŞVÉŠů šŒ¨íŽŽ:ÝĘ2„’P‰ˆD"ş c GkËňš]BÔóëń€¨5###PĹép8 ›´jHaŒš#‘Č^{í5f”ú}^Ą‘(2˘Nnz5 ƒ8uę Qސœ:u Á`P—1H’ľlŸS‘%D…g?'Łŕt:ńÚkŻąH$ňƒőâĂaČ1ł&¨ĘrÜ0;j=gÄfŤ)ŰÓ\.łłł¸rĺ %Ą\˜››ĂŮłgŃŮىýű÷—˝ĘF…V¸'ǛIBTd9ŤŐbŞÎŤFO\5œˆŹwP5]‚j"‘D4Şo6{.F•˝ŞbÂá0ü~?•áš033ƒˇß~ÝÝÝđz˝eíÖŞ.ŐđŹŞ1Ł„¨Dٞé:Żf$ŽzÖqŐp"bˇŰţüóϛ*AUQĎŐ×ŰP]]Uň|df ÎŮfjvćőzqđŕAüň—żü9€˝Ç“‰ĄrDZZZţ&6›-Aui)fČ›ŠrFԉ­Ü 122‚ńńq’˘lÄb1Œcdd¤ěů#6[ —ĎŞY%D%‘H–ľĹB9A4mniiů˝Ç’‰aD„1ć\ZZ:yňäISuPĹV…Řş[OąZŤË^‡qţüyœ={–Q ݈D"8{ö,Ο?p¸|ťśŤ2QhߌJ‘•ĺĺXYšN– ˇŰ“'Obii餑Ęy #"‡ăŻ­VŤŐL ŞjxOôI˛Âé,ořóÖ­[8uęmHG†;wîŕÔŠS¸uëVŮÎɃәUZĽIˆŠQ—Ő‹ehhVŤŐj¤r^CˆˆYËuĂaýú…K9e¤ĄABccmŃç)”ŮŮYŒŽŽâęŐŤ´ CŽX,†ŤWŻbttłłłe;occ-śoo^ФÛ§żˆËy !"MMM?îěě4Ušîňr ŤŤĆݞ{;Ę!#ľeÝ5×ď÷ăíˇßŚ’\Âđ¨Ő5~żżl珍łmůPPɢ˛şšb‰=_†††ĐŮŮ‰ŚŚŚë=Ŕ"Âs/,,|wxxXďĄpĂ INZÉc MM eŤŃ‡Ă8uęĆÇÇËr>‚ŕĹřř8N:UśÜI˛˘ŠŠ!K8HBbô˘ƒBĆÂÂÂwŃ]DZZZĆúúúŕóůô 7̲ŚČ[FĘ˝Oš BűÂůĐŰŰ —ËĽ÷0˛˜››+kîHŚx„d#ZÎßNř|>ôőőĄĽĽE÷Íđtí#˘6/3S4diI6ŇN…gŸ‘ľ5‹öî+Ë2ĆĆƄKFmooGmm*< … …tQĺĐŢގţţ~<űěłřŕƒ011Y6FB5w$ b``’¤ívőęŚyUUŒ$$‡T´[F}˝śďAš6D“3]E¤ĽĽeäÉ'Ÿ4Mó23,Él/ąX´ŻŒ ƒ8ţź0ɨ.— ˝˝˝xňÉ'7Ü`ڧ§qóćMLOOë4şĘĄżż IzzzpóćMG´‘;wî`dd‡ÖźĹAŠ f”•ĽĽlśSt]őz˝čëëçŸ~:[żë&"fŒ†˜)l— Ńżß/L.ˆ$IčďďGOOϖŻéęęBWW._žŒ‰‰‰2ŽŽ˛xâ‰'ĐŐՕţúÚľk:Žf{bąΞ=‹žž>Ă>Ŕ™YBTŹůRM!T‚„ćZ˘1BTD1[4$™\Ăň˛1vŐŐŁÉˆÚD„ĽI’đâ‹/f=}ßž}׎]ېŇŢގW^y%}“yńĹńÖ[o•uź•@n‚ęĺ˗uMaĚĚĚ`dd>ŸŻě;únFĽHˆĘňr’d-KޛÖčŃĺ_đË_ţň)#‡ Ĺ,U2ůÂŁš†@ożýśŇŢގ'N¤%D–eœ;w.\Ř4)őŢ˝{Č܁ÚĺreEIˆŇ‘$ Ď>űlúë›7o — ‹ĹđöŰo#č:ŽJ“Ŕ\U4jTäË_ţň)=Î_vaŒy÷ťß=f–hˆ,Džm\V zËČŘŘ.]ş¤Ëš‹Ąśś6+„ţÁ옄šťLĐŃŃĄŮř*‘ţţţô{"Ë2>řŕGT<—.]Âؘ>U˜•(!*ŤŤ Ȳ9˘áĂĂĂřÝď~÷ŘúŠEY)ťˆ´´´Œ˜%˘( ˘Qc”řé2"Ë2FGG199Yśsň W*zzzňZŰϔ•öövMĆV‰tuue% đÁ†)×-–ÉÉIŒŽŽ–ő˙Ł’%D%•uó@Š´´´Œ”űÜeƘw~~Ţ4š!fšKĄœ2˘ćƒˆ’”šKf5†ËĺÚśbFE´ĽQčííM˙ýŢ˝{†,×-†™™™˛íUC’ÂL¤ĂĂØŸŸď.wT¤Ź"bŚhHłârš˛Ęu7ëßbF.]ş„?üPłăWW[ĐŘXŤŮńEbi)fŠźÁĄĄ!Ü˝{ˇˇ\;ó–EDššš~ÜŮىrœNS(A5ECƒI˛jzŽŻ}ík†hÔ¤™Q‘ööötâjWWŽ9’%!.\ ŽŞČ-×­”śůmmmřÚמŚé9$Ɋ†ńťŒ–ŠYWĐŮŮ‰ŚŚŚ—ă|š‹cĚ˝°°đ]3ä†$IJPEjŇŠŤłi~›Íf˜Ž‘ź™žžÎşö÷÷ăčŃŁ8räHz9& á§?ýŠ)sĘMWWWVžÍľkׄ/×͇śś6ř|>ŘlÚ^ëęlš?œˆ€,ÇMąt?<<Œ………ď–#*˘šˆ8Žăđů|ZŸJsĚ`şĽbľV—5 kfšyófúf(IRVŸ›7oâôéÓ áDî~2• )§„¨46ÖÂjŐuSwC`†{…ĎçƒĂá€Ăá8Žőš4Ƙ3‹ ™ABâńDEvPͤşÚ‡ŁŽěç5ŤŒ„BĄ Ý<'&&đÖ[oUĚ{9čééŮ yfG Qq8ę4́ŐՄ)Ęy‡††‹Ĺ†cN-ĎŁuDdŔjľZ͐¤šcObaŒĄąąVˇR=łĘHîţ&.—Ť"(˅$IYѐÜDa3˘§„úĎFÁ ÷ŒĄĄ!X­V+M<5ťÝţăC‡1§SS™ŇYŽW|ă­+dňÁŹ2’YBÚŐŐľe9/Q8™­ôsK§ÍˆŢ˘B•4ŠF˘ç:N:tˆŮívM“V5Ƙ76‹ž¤j–,čR¨Ż—`łŐč= 攑ܦZĐq4ćÁĺreEC>ýôSSG›Œ"!*6[ ęë+ť’Ć U–ĂĂÈFŁÍZ68ÓLDÔfnˇ[ŤS”…ĺesԅKj21ĆÄŚbFÉ݇&łű'Q]]]Yy6===8räȎmőEÄh˘R_o3ĚCŒ(Š"|“3ˇŰ­yƒ3MDÄ, ĚR‘ŘĄľRĐ2źúᇖÔ-Ől2rďŢ˝ŹJŽgŸ}6ŻÝy‰­™˜˜Ŕ›ož™őďÚŐՅW_}˝˝˝Śů÷ĺ!!ąXLłŚgFXÖՓĺĺ¸đłZ78ÓDDZ[[˙Ö Ě*9˘eÂY Ŕ/~ń‹’÷1›ŒdVĘä&YĹ!Ë2._žŒÓ§Oočf{âĉźv@62ź$dttżřĹ/4i_éÉŤfˆŠ¨ ÎZ[[˙V‹ăk""‘HäĎ)"6vť¤ÉSŒşw đpG]’‘˛,g•ó斝ĹsďŢ=œ;w.\Hç‰H’„ŕčŃŁB&ó”u§^­öŚŠŽśŔn7GŞĚńů|ˆD"ŽĹąš‹cĚ'˲MôŢ!• ąŮj4é8;;‹+WŽlřÉČCrËy)*—۷oočÓŇŢގ#GŽŕĹ_&D Qšrĺʆďń@’Ź›/b†¨ČĐĐdYś1Ć|źÍ]D\.×ÉÁÁAˆ\˛›LŽUěĆvK•&y!Ű ÉH6—/_ŠĐŽťÚ vŽÍÍYY1~ď-%d§Ÿ•Jcc-,–˛mún(––bBˇp:„Ëĺ:ÉűŘ\ŻƘ' šE†ŹŹTĂQÇ}-W–eŒm+$#™žžĆľk×6$Z| …B¸|ů2Ν;‡ééi|đÁ†ďfŤľ„džfllŒűżcL—îĚFAô{‹ĎçC(r3Ć<<ËUDZ[[_íěě„×ëĺyز˘(ŠđKąÔ×k“rţüyĚÍÍíř:’‘‡dîCChËôô4Ν;gřfgĺ’•šš9œ?žčsmEuľĽbű‹ŹŹˆ+âőzŐ¤ŐWy—Ťˆ˜!IľRsCR“˙ccc˜™™Éűő$#ą‘rKˆĘĚĚ ĆĆÊ>çVÔ×Ű*˛¤×,š"ź“Vš‰ˆ’T+ľRFŤpi ŔäädÁżG2BŃKBT&''5ФŃbXDŻ ńů|ܓVš‰ˆŰíţ‹ƒ ¤ZŠŃúz÷˛ŮŮŮt™nążO2BT:zKˆĘĽK—¸'ŻZ,U†ëÚ\DŠ8Nâńř_ń8VÉw!Ƙ'6‹,"˛şŃL1TW[¸/ɃAŒs=Ś ÉQ YBTĆÇÇ šłŽŽňŞh’É5Ȳ¸é>ŸŃh´™GO‘’EDíâńpíoRVVVÄ ‘ ď,kśÍ„d„03"HˆĘůóçšç‹hľÓˇ‘ůŢăńx¸ő)YDÂáđ÷Dî’H$ąşšĐ{eE‹§:§ň‚dD{œN'Ün7<ź^/ź^oQů_éß÷xZQ…’d6 ń裏Ŕ!EQŠ6Ӓꦚšš^~ä‘G„•EQ*JBŔn—¸JH8枼–jd¤” \ŒT‚ŒH’ˇŰ ˇŰśś6tvvę:‡Ă‡Ăąa333˜E0D04}›{‘%H%§ďŮł‡[¤+UÉ'áÁăo>Č YŽsŸ—Ë…ŰíFww7~űŰßž @YZZúS‘“TEn*S Vk5$ÉĘő˜ţúŻ˙úOK9FŃK3fX–š?ZQŐ2MM \sCü~żfU2…`† {öěI˙WĘżGžť4S ąX SSSé˙DĆl×l__×ÍN‰$šĎčX,Uhnśë=Œ˘ŕą,--˝+…ş:÷*ŢÝSyP 2âvťqřđaœ8q}}}Wš˜‡Ăžž>œ8q‡6l”Ö̤oÝşĹíxŒąŠ*ç9J?00€X,&­§lLQ"ŇÔÔôrwwˇa?đ;Q)á> ľöXWÇ/AUŻ*™|1ŤŒx<?~ƒƒƒxěąÇôŽayěąÇ088ˆăǏŞÉ˘Ů%DĹď÷#s;^]•ű6FFÔ{“ş<ÓÔÔôr1ż_Ô;œL&Ÿ§e1¨Żç šrĺŠnU2ůb&ńx<ÂÁƒ…Š|1­­­8x𠆆†t’J‘ 5NžK4ŒąŠÚ‡Fä{“ĎçC2™|ž˜ß-XDcžH$âuYFäđWĄX,U\ËuƒÁ îÜšĂíxZ"şŒd -żĂáĐUH*IBTîܚþÔZ’*'*"rÚŔŔŔ"‘ˆŤ˜˝g ~wމź,‹UNm:ď' Ţ-ľFDqťÝ8~ü8 gT!9~üxŮćŽJ”ŢsE%EED˝GŠË3 Ç ý݂ED’¤?ĺY/^N*Š“*ďhˆßďG$ávźr!ŠŒ8Nř|> ŇŒ†´śśbpp>ŸOÓ*›J– ŐŸg.Y%EEd9.l‹{Ż× I’ Žž)čeŒšççç5?DԐW1đ|‚‡Ă\łáˍŃeÄëőâĉşˇ]Ż$:;;qâÄ ŽM¸T*]BTnÝşĹ5qľ˛˘"bŢŤ|>ćççaŒš ů˝Bs ąąQŃ;ůŤXDŢrš´ˆ†=Au'Œ(#nˇCCCčëëăr<˘púúú044Ämš†$ä!ąXŒ˘"E"ę˝Ęăń ąąQPPiAďjGGǟ:tHČ3ÉäVWĹ\{+žOłłł˜œœäv<=1ŠŒH’„ýű÷cppň@ €ĂáŔŕŕ öďßI*žńIČF&''šţżTJTdu5!lQĹĄC‡XGGǟň;‰ČÝťw{EÍ5ÔU(źŁ!Fë Z*zËH[[Ž?žŢԍ0O?ý4Ž?^ôűJ˛9<çJŠŠˆzĎňz˝¸{÷no!ż“÷;ŞvLľlWäúěBŕůÄ  ŮAľTô’Ż×‹cǎQÄŔ8;vŹ Ü’í™™™áZÎ[)QQďYŞ#Ňe5oimmýNwwˇű9ä’LŽ!‘Hę= ÍĄhHţ”SF$I‚ĎçŁ\čëëƒĎçŰqІ$$?(*R8‰DRČĺ§Ó‰îîn´śś~'ßßÉűÝL&“ý˘FCD q O ˜››ăv<#RikkĂĐĐUÄHgg'†††ś}oIBňcnn@€ŰńxÎuFFÔ{×ŔŔ’ÉdžŻĎKDÔ˛]QóCD qBjƒ(ž•2•€–2âńxpěŘąŠÚ×lŘl6;vlCWV’Âá9§ÔŐY+bg^Qď]^ݎ 2Ţ|#"^ťÝž&˘ˆTʲLm-żf ˛yYąh!#8xđ Ż!:sđŕÁôÚ7IHqD"nQĆjkÍuyĆëőÂnˇŻđćóúę|^ÔÔÔt`ďŢ˝B.ʉÚ*žĘJ‰†d˘ĘH)7UFîÝť'ěÄÖtwwĂáp ˝˝$¤Hü~?ˇ=jk­X^ťżQ>ÄbŤ¨Ť/ŞúüóĎWݸqă€Ń^›—\$“ÉçEŒ†âöí/›­†[ňVĽEC2á! 1/nˇ›$¤xFE,–*Řl5\ŽedD˝‡y˝ŢźwăÝńîĹsG"—ˆ"˘(JE41“$~F‘[šó€‡ŒÄfTş„¨đœcxÎ}Feu5!äŢ3^ŻWݍ׽ÓkóyŒöŠÚÖ˝–ex>ƒAÓWĘäÉÁ’‡ĚÍÍqë+Â3ldDź—e´{÷îôÚßÁŽŽŽž{î9!ӓăńĘHRĺE%ć†lÉÁ ’đœk*!iUÔ{ŮsĎ=Ç:::^Řéu;ŠH4ý—e1-˛Px}gggMŮEľHFˆR! ٜ™™n˙&• "˘ŢËź^/˘ŃčŸěôşmE„1ćŽFŁÍ".ËÄăbŽŤ1˘… IDAT‚ÍVĂ­dˇŇsCś‚d„(’íá5ç0ĆLŸ´Ş( âqńň=˘ŃhóNy";ED< ÚwÁ(ˆřŚ ŻD-Y–MłĂސŒ…B˛3“““e™Ëą*!iUÄ{Z†;lÍŘVDö‰ş?†ˆoZ!đLRĽhČΐŒůB’?źćžJHZőžÖ×ׇ†††}Ű˝f'Ů+b4DQÓwSĺŠäš„™™Ĺ˝{÷ôapîÝťG’'<çł/Ď$I!Ó ź^/ön÷šmEdvvöIóCDMě)^ĄČŠŠŠŠm`V(ÔŹŒŘˇŰ Q7-7‘HSSS\ŽEË3ĆÄăń`vvöÉí^łĽˆ0Ćź€¨ů!掆TW[P]már,Іä‡ÇăAwwˇŢĂ Ąťť›[+słĂkâ9/ťŹŞĄ:ĹflńtvvÂétňU0{7U^ć‡qçÎ.Ç23mmm´Q0ܰ#3ą‘;wî s9–ŮŁ""ŢۜN':;;mVˇ‘ŽŽŽçE4údrMČÝ ×Z(ECvF’$ř|>˝‡AŠĎçƒ$IzĂđđš‹Ěž'"ęýÍăń ŁŁcË}gśQ™‰hŒ…P]máşÁą=‡.i“3˘˛ąŮl8|ř°ŢĂ(‰ŽŽ.´ˇˇkzžá™}yFÄ{ÜNÍ6˝Ł1Üâ623w~ŻĐc0䚤jƧ>Ż×Ť† ˘h:;;…Ěľsš\xńĹqäČźňĘ+š~Ć#‘ˇýgĚž<#â=.ŁąŮŚš[=Z ŰČLD[,Ł.ËôôôŕŐW_ĹO<Áő¸zŃÖÖQ{čĆŁŻŻO˜|I’ĐŰۛőy–$ /žř˘ŚçĽĺ™üńˇScł-EDÄ'AEQ„\?Ë‹ĽŠŰ˛ Ż’9 őäôěłĎf=Aš\.nÇWĎQލ‹$IÂ‡Ó ăqřđaĂG{zzpôčQô÷÷ořYWWz{{5;7Ż9‰çřŕƒMŸŇxa”p9Q9”ëš“$ Řry4 mx`؊O?ý4}L­šœńšŁx͙FEÄ{Ţş[ěéččč3QŐźeťźşŠÎÎÎjş,łÓÓÓ8wî._žź!dŤD9—Ë•Ž†„B!MŁ!nˇ›—e§łłSóČłš0ž+üÓÓÓiůhoo/*Ťśś–Ës‰Ĺb˜-ů8fď˛*â=o˝rfCśóeŒÇăťĹÜčN<;ĚŤUźhČfLLLŕÓO?EOOOV`WWşşş011›7o" áŔéßË\›Ö‚ýű÷kz|‚ŘŠýű÷ăÔŠS܏ŰŐŐľĄHIýĺ˗1== —Ë•”ŢŢŢźňŻ:::¸u3ŚŚŚ¸4€łZŤ…Œ䃈÷<§Ó‰x<ž;÷űłçççąbFÄä|ŠŠ+?d;Ô\Ó§O§łđUÔfJH?ĄMOOoxO<Z[[5;>AlGkk+xG {{{74”e׎]Ă[o˝•ŽĚHc>ÍĘÚŰŰł:'kšTĘoyĆźďy^Żóóóä~?KDD­˜1ŤńŞđˆˆČ˛Œšš9ŁáC(… pîÜštZŽÉ #k Qş sÁűœ˜˜ČŞX›˜˜Ŕ›ožšéňffîU˙–2ŇŐՅW^y%ýőôô´Ś"277—ő˙P,ź˘ÉFE´{ßV•3šď’3óŢ`ćśîźę፠ٌééiœ>}===Ę '&&˛$…7‡Ęu Ýq8đx<ÜöZQŁO<ńŽ]ťśígH–e\¸p!˝Š]?şşşpűöm„B!¸\ŽtCÁL´îç¤ćŹRŁEj˙%ŃnŘů’LŽ •“áYůš"âíîî.Çx¸b֋ ŕgôźvśÔŠĚü‘ţţţôdŞ% !Œ‚×ëĺşĺÄÄDޕ0÷îÝĂűᅬoűŰéîĹŰ%ŻŞ9&Z š,[™9O$‘H ×/Ľťť“““^~ő{YwšÖÖV!UEě2—/ŐŐ|6o2şˆóGnßžŽŽ..ĄŮ­ ha$xGE E€|űŰß޲,W-Ł”™đšłxÍĄF$O˘ž^ďQ†ÓéDkkkVÂj–ˆ$“ɧD|J\[3ďŇ Zřp8ŒH$Âa4奐žĹňĚ3Ďhz|‚(”gžyF7R˘qúôi<ńÄčęęJ'ťŢťwÓÓÓe‰‚d‰D‡Knüfć~""Ţűź^/>ýôÓ§2ż—őĹbą/‹1kŽcŒK˙˘!ĺÄívSĽ a8Z[[ávťu˙źfî+Ł7<–g,–*0Ć ( §Qď}N§ąXě˙ßËşËEŁŃfњ™™uí¨œürCŃ¨о™ ŻšËĚŐ3˘Ý=˘Ńhsć÷Ň"Âs(űţĽ"˘ć Żlh] ͂ÓéÄc=Ś÷0bS{ě1áć`-á5w‰TYR(˘ÝŐë[u ;"âŔ˝šŽÖˆfƒ…ŔcÓ&ŁőŃŃŽo˘ň kô!üú‰˜WDDťf\ßnő/Y"bˇŰĹR+ɤůÖýTŞŞřě/C<„&yÂčĐ5š 9ŒÇ\jTDźŽť†[ý:KDžzę)áŢ-ŃÂRůB‰ŞüŮłg•ě†Çáp`Ϟ=zĂ0đ˜ĂԄU3"â=pÝ5Üę×é;¨=DD Kĺ Ż=("ňšÜ Q kő!źć0łî;#â=0ˇ—HZD’ÉäS"†ÍX’P˘ŞĐäNˆ]ŤĄ„ŐíńčńxL&Ó˝D„[ŠÉDDĚÝeYŞ‘™–ěŮł6›MďaD^Řl6’‘u"‘—„U3wXý^˜~gćççťE‹ˆŹ­‰g‚ůÂ#?„˘!ĄI şfÂc.ă1§Ńóóóéí˛ŢŃrDDˇŔíŕF$yMę„hĐ5űs™Y—fńŽQˆŰĚLÄľą|`ŒqÉđ‡ĂF#>nˇ›–eá°Ůl™ŰŚW4<ć2^óŞí^˜ŰÔLˆ¸ńę×EʟÎޘá =Y˘B×n ŞœŮŃM̈́^4ą~:xY;‰H zŞ$D…ŽÝźć2łFDDżŚ#""vU5+źÖ2cą—ăˆŒ$I´Ó.!,­­­$Iďa诚ĚĚy"˘‘Ů]5-""vU]]Mč=M°XJˇv捚‚ž( ŃĄk8ŸŤćŒˆˆx/ĚěŽ*œ|Tf.3+74‰˘C×0?hn5&UĐĐĐ \ěZ´,áB౎I‘mmmz J‚Žá<ć4łćˆbŢU÷¨I’ţŘëőę: BY]Ťnşh“z J‚Ža~˜ynížčőz!IŇ´4cZ("BO’„y k™ć43C"b0Ělěĺ†&oÂ,о̚cG$“I‡Ţ)ł„óĄŞŠzˆđB´NÁąt-ó›ÓxÍąFCÄ{˘ęU …ܢĺˆŰC=D¨Ú€0t-Ӝf6ź^/BĄ ĽĂQSS­÷L=EfŽe~Đk‰ĄŒ1xŽ""AAč‰AAşA"BA„nˆAĄ–ď†BKX]Mč= î¸\ő¨Š)ímńűýç4"1 }9S199‰ąą1˝‡Ą+}}}%şš@(´Äg@˘ŚŚZŘ=Ř("B˜’p8Ź÷‚+tMf…D„ ‚ ݨ€@  ďHn´ľľé=ÝĄ§GÂlĐ5Ms›™ČpŽ`•˘(A€.rِLŽ•| I’8ŒDlčz&Ě]Ó|ć6s,Q:ęőŹ(J–f F2Iny@“6a6čšćÍąĆCXąZ-z004ifƒŽib;Dž' +"ÄÖ¸Ýn˝‡`"‘ˆŢC .Đľœ‚ć6sB"b0ĚŘE/č ’0 t-óƒćXăQ.—+č÷űu Á›ÍŚ÷t' ę=‚ŕ]Ë4§™ żß—ËÖEÄbą÷+ľű¨QY[ă“HEenôI˜ş–ůÍiźćXŁ!â=QuZš1‰DRď!˜†ŮŮY˝‡@\ k™4Ç“BI]4y恎ešÓĚLȲükŃrDjjÄ-UÚ 2v~ĚĚĚč=‚( ş†ůaćšU´{˘ßď‡,ËżÖEdqqqNß!cLď!h†˘”ž†IO)čI’ş†Sđ˜ÓxĚ­FEÄ{˘ę´4c@¨1?¨Ú€ş†ůAsŤ1QE$řŃG ÷‰˜%œÁ`ąXLďaDAÄb1ŠˆŹĂc.ă5ŻŃk¨îQľŮEĄşZŹşéBౖI‘‡Đ“%!tÍ>„Ç\fćüŃî…šŽ‘‘–––IіfŞŞÄ˛ŔBŕQfF"ňšÔ Ń kö!<ć23—îŠv/ hii™Tż:ĂE4 ,„D˘ô$Ip8F#>SSS´-"ˆˇ6–/źÂˆyMě„(Đľú^s˜Y—fDźX,–ÔŻÓ"277÷šh9"€ř&¸T9ĂšÜ Q kő!T1ł="ŢĂá0ććć>WżÎ\š˛ťŞčőÓ[Ą( —5Mę°úŠŠ)D"˝‡AۉDHD2ŕÓQuÍ´3"Ţ3ťŞ9""bwUŃę§ amVy#âň#QYĐ5š 9ŒÇ\jTDźfvUrDďC bX*_âńŇC‰’$Ąľľ•Ăh́h×7QyĐ5úÖÖV.‰Ş<ćRŁ"Ú=0ˇ™!"˘651,•/”°ĘŸp8Œ;wîč= ‚Ř”;wî7k %ŞîŒh÷ŔÜff@NůŽÝnż/š‹fƒ…'¸‡ňD˛šuë–ŢC ˆMĄk3^sŻšÔˆˆv °Űí÷3ż—ľw°Ífű]8n.ď°JÇbŠ2eł5aľTăŐKDşşşĐŐՅööv@(Âôô4nßž­ËxT‚Á ćććhɊ0sssÔŇ=JTÝѢ!@*"błŮ~—ů˝,ąX,ůýţîňŤtŞŞĚ)"°şš€Ĺb-éN§‡Łl#ííířöˇżLzzzpďŢ=ź˙ţű¸wď^YĆłˇnÝÂÁƒu;?Aä˘u4¤§§ÓÓÓ…Bšž‡‡ƒË3ŤŤć†TU‰'"~ż?Ť‡ł4#j/ŤUŹĐT!đęXލHOOŽ=şŠ„¨´ˇˇă•W^ÁO˘D‘˘HĘJĽmĹĹ츸3wîœTô\{¨[Ó^łš]ËŅăâ sŸâyúĚOäĹÖÝ;ŒĘ›í?nŠ&X'ŤMž0jŽv“ĄĘ¨ńŰUĽ’V–jI$E‘>¤H}Ÿ?čCSÔ/ţřžüźÁúA~m“çźÎç×÷S=žœŰÚZˇNáľG‚’Q‘ғح[ˇś-D/ŠĘţýű‹żżk×.źňĘ+뢹X ožů&ĆÇÇšŽ;‰`nnŽë1 ˘Rććć†ěÝťˇxƒ055…ŤWŻâ… ˜˜˜X÷¸S§NiŚ~„ךʨűËúźć…ĂatwwZţýułŮüI<˙\c–ŏś6“aŤŁyYýž={066ĆĺX[133SŃăĆÇNj'IYFĘĂĒ$áý÷ßç. Ľźűîť8qâ„bÇ'ˆÍx÷Ýw=~i4äęŐŤ RőęUÜşu ű÷ď/žçä‚ցŒŻ“•FÂKDŒikÓW4(ŞšÍćOĘżżNDfffŽG"‘ýĺß×:­­úʕUC.—ç2O¤ˇˇ‹™L†ÓĘńŕÁƒŞgjj ÷îÝŰ0‡ ÂČׯ__JV‚h4Šééiôőő)ú<QĘôô´˘iđŇHăőë×׾íNMMajj k".— ‡ĆŢ˝{×D.…Ĺbá2Q5Ÿ_5t}ˆŻy‘H333×ËżżŃß$҈ťfŢč-WV-ź˘=JĽgîÝťW†/~ń‹ýÎŔŔŔ†y銊)\¸pWŻ^U\BdBĄPCž‡ d”|͕Ö^I’´etcbb###Ĺş-™]ťváĹ_ÄáÇšŽá—–1n4Đç5ďĄ[ŹËEn$"Q€ VľŻM›”Źůř㏠ŕś*|ŰľkN:ľî‹ĹđÎ;ďŕÍ7ßlř°łx<ސ´A…˛’3›Jk=*‰*J’„ńńqźńĆë& ŕŐW_]Ó§$üŇ2ƍ†úťć•8E´ügëDDŻ3€>‹w*%“Yárœ={öŔbąp9V9ĽuĽši—Ë…çŸ/žřâšů!’$áúőëžI8Śv^Bq‰„˘2ťví*Ś:ďÝťWU­Çf7ĽєDhąX¸‰ŻsŚŃăľnłŽ`ăˆ\.WT3zœ2W)<űᕊŠÄbąâIoď޽Ŗ(ŠŘż˙†'ą‰‰ \¸pAŃbÔj  Ą4JżĆJ#ĺé–J‘ÓŁĺєÍn&xÁëÜÄkţ’VŃăľ.ĂĺrE7úن"ŇŢŢţs˝í č/TU-’Ä/*˘Ľ'ŽŻ}íkŘż?N:ľ.B255UœśŞĽ˝/˘Ń(>üđCľ—A”?üPńhóÔÔ$I­[ˇę.4ÇČČČş9˝Ę;]ĂëÜÄë\ŠUôx­‹D"hoo˙ůF?Ű0ž333s#‰|MŮeńÇlÖ_¸Ş2™ttÔ_4&§g”čž‘Űmĺ*ür‰ĹbŞˇnG8ƞ={ŕp8Ô^ a ”NÉČČďŻööv.ǓS§8|řpąÝW–^PZŚrôx­{Ř1scٟmÖ˙™žžVpIĘ ‚îFŢVC>żĘm—a%Ł"›ľü]ż~}ĂaJZC’$\źxQíeăâŋ ë“$‰{¤Q*řÎ;ď ‹áý÷ßçúźÎI<ϓZÄdj j/Łj:Ĺ†Š–MEĐçřk=ńT/Ó÷x<\ŽłďźóNń¤{ëÖ-źńĆo؉¸^fggŠ‹†ŕĆŘŘfggŐ^ä÷3ď ^ç$ŁGCôx+q‰ EdĂżc,ŢŮŮš‰Dvx˝^eVŚfł :šÖՄ$­Ŕj­żëĹívĂáp(Ö%"I~ôŁĄ˝˝˝á‘x‡ávťiĐQÓÓÓşźŠŰž7‡ƒŰnťFŻŃcĄj$Ýn_XZZÚ°g}Ó<†Ýn˙=žyôh‹Ő OYĺŇQ‘{÷îéVBd.^ź¨H- Ńd2JóUŻs‘ѧŠúźĆ…ĂaŘíöŮě益ČÂUeVĽ &S‹ĄëDý¤gŒ€$Iƒj/ƒĐ)Á`P7éH5Ą´LečőúśUĄ*°…ˆŕaÁŞ’Ó˙”BĆX źBN§ťwďćr,#3;;‹+Wލ˝ Bg\šrĹ0u!J˛{÷n8N.Ç2zZF×śx<žeĄ*°…ˆ0Ć€> V-ýýgUC.—ç~¤¨HeD"LNNŞ˝ B'LNNBe5ŕuây^Ô*zźśÉ!;ĹFlăéííýXo&=öXW Ďáf4/Ł2BĄ.ˇ> K4Ľ ˝âp8hˆYčńÚ‰DĐŰŰűńVŮRD–——oę1""‚.'ĎUĎ\(EE*َˇW‘ąÖ„ąxüńÇšlcß đ<÷˝>¤ľŐ¤Ëů!ápËËË7ˇzĚv"rMŻłôhŽŐĎŻr{ăíۡËqŒLoo/†††Ű00‹CCC$#Ŕëܓɬzˆ ßkÚŘŘ–——Żmő˜íĘou;ŘLŻ˙iŐŔ+)Š"úűűšˈ„ŐB2˛=ýýýĹúˇŹ(-ŁUśd&łĽˆ0Ƣvť}AŻu"z cUC&łÂm‡IŠŠl IQ+$#[ĂëœĂ3|ZF]Šˆ<Ȍ1ÝęqŰ6$ëu°X,mj/Aq<Čr9Noo/M-ƒ$„¨’‘éëëăöoÂë¨eôz-Űn™Ěś"233sé˝÷ŢăsŰÝ`ô8 ˇZxž ő6Î_IHB^ŒŹ‡çšŚDDŻ×˛÷Ţ{ÍĚĚ\Úîq•Œh /-- zLĎčŐ"ŤgŃŞŰíFOO—cé’‚7$#čééᜯL3Šúź–E",-- ÂŰ=v[aŒEGLéAt9‰ŽZxj5{­IĄ$#xžcšĄHľ­MŸőŽáp‡#ś]}PYD&“é†EĐç$şjáyWŕńxšvŔ Éd24ôĚŔDŁŃş6Alvq8\7¸3z‘* ßkX8†ÉdÚt™R*‘ĹĹĹŤ7nÜĐeüK!­Z Z‘úŕ%!Á`ŁŁŁ4ހLNNbttÁ`d¤F¨6¤zôz ťqăĆęâââŐJ[é6~ád2٢ǨˆÉÔbř)Ť@áMÉŤ•ˇŮ˘"<%DŢä, ŃFyâʕ+Ĺąíłłł$#5Ŕ3Âk im5érˇÝp8Œd2ق ęC€ E„1íîîţT"˘¨OŁŹĆŇiŠŠT‹"‰Dpţüůş.X„şd2œ?~Ýv$#ŐĂóœ’NóťńŇ2z˝v…ĂatwwZI}PyD&“éş^7rŇkhŤZ$‰Ÿˆx<ĂwĐ()!2łłłň6؄Ž˜žžF Řň˙–d¤2zzz¸î+Ăó\§eôzí …B0™L×+}|Ĺ"277÷“““ˆÇăľ­LEš%=“ĎŻr}ƒ:tˆŰý/ER IDATą´F#$DF’$ƒAčuߌfdll Á`’$mů8’‘Ęŕy.‘¤lS´ěę5-Ç199‰šššŹôw*ţ[2ĆBtť˝ľ^C\ՒJńK¸ÝnCN[m¤„”‡qţüy$‰šŸ—P–D"óçĎWľżÉČÖôőőq›đ=Çi˝^łdGĄŞŇ­;wŽëľNDŻ!ŽjĄ¨ČÖ¨%!2łłł8wî>üđڟŸP†?üçΝŤů˙•ddc(Rz˝f…ĂaěÜšsźšßŠJDfffţáňĺËşŹ2™Zšb¸Ŕ÷ŽĄˇˇ×0;óŞ-!2’$áÝwßĹčč(EG4@"‘Ŕčč(Ţ}÷ÝmS1[A2˛žţţ~Ž—f‰†´ľľę2-—/_f333˙PÍďTű7 éuÜ;´ˇëÓ0Ť…wTÄëőę~ҨV$¤”h4Š@ @ľ#*266†@ ŔmÉČ#, ×N™fІčőZU2Ö˝ŞŽŞDDnă ƒŐüšfĐk¨ŤxŢ98N]~ע„”‡122B5 dzz###UՂT ÉH}űöÁétr;^łDCý^Ť‚Á`Umť2UÇ~$Iú'˝Ö‰‚Q4Ť˝Œ† DTDCδ.!2ńxź8•unnNąçivćććŠÓQ•ělvq8 ŠQ4ëro pS%IŇ?Uű{U‹ČňňňůÉÉIÝךýľŔűÂçóq=žŇčEBJ‰FŁ8wîŽ\šBő#I$¸rĺ Ν;×°sW3ËďsEsECôyŠF٘œœÄňňňůjˇjaŒEGLŻmźK›n‹€Ş…wTÄívc÷îÝ܎§$z”R"‘ IČÖMGmÍ(#ťwďćÚŽŰLŃ“ŠEˇi™P($ďś[ő­Ś+˛ÉdşĄ×:@żýŮľJe¸ŽB>tčć Wő.!Ľ” Ľl*gnnNU)Ľ™dÄbąpm×eŒ5U4DĎ׌`0XńnťĺÔ$"‹‹‹oé9=Ó,u"@!*Âs§ÓŠé}hŒ$!ĽD"œ;wŁŁŁ¸{÷ŽÚËŃ,wďŢĹčč(Ν;§ş€”Ň,2âőzš¨ŚÓÍ ô{m’Ó2‹‹‹oŐňű5‰c,dąX$˝ŚgôţŞ…tšoTdßž}šœ¸jT )%ââŋÁŘŘĽmPHżŒadd/^Ôě ’Ńe¤ŻŻkw]a#Ď扆čšl  ÁbąHŐLS-ĽćżľÍfű'JĎčĆ’ÉÚ5m„Ö&Ž6ƒ„”Ç‡đöŰocrr˛ŠvůÍd2˜œœÄŰoż@ €p8Ź‹}°Œ,#źĎ ɤÔ;ěĘčůš ałŮŞî–‘ŠšŔľk׸Żô~×˝˝˝číí…Óé„Űí†ÓéŹzzn­"’H$ÇFÇ1;;ŰÔ˙z}-äďUť óäädą€4Żů Ą×4ÍîÝťšďAĹ{î‘°Zőٲ đIËuFD ˇˇ÷Š˘xRĎůŰX,…••œÚËh(]]Ü WÂÖö@@ń Iˆzźţúë랡UDäěŮł ŻČXčéľmąXŕ÷ű!Šünhrš<—šO´ľľÂĺâWˇ×hÜn7$Iúáěěěˇë9NÝMËsssoLOOkjxPľčuËĺzŕƒEǏçzĚrôt˘&ˆjŃSkďńăÇšJŔ˙œ¤ô|í‰D"˜žžĆÜÜÜőŤnaŒEěvű‚žgŠ˜u;HŚVrš<÷aAnˇƒƒƒ\)CB4z‘ÁÁAî5étš\žë1ľŽÉÔ˘ŰIŞ@avˆÝn_¨eo™r¸\}Ífó_ëYDŔf3Ö̅JHĽ2Üç¨x˝^îSWIBˆfBË2Ň××Ç˝U7Ÿ_mŞ1 2zżćƒA˜Íćżćq,."˛°°đăD"˝Ž| ÎôÚ>U+…‰ŤüáǏç6L‹$„hF´(#‹E‘ôk2ů é TAĐí3 P¤šH$°°°đcÇă""Œą¨ŰíţW=GEAĐuőr­dł9î)Q144T÷qHBˆfFk2244Ä˝.$Î ›mŽF Đ)Łçß`0ˇŰý݌ą(ăq+ŒˆFŁwĺĘ]ˇćY­]ż8jE‰Moo/Ž9R×͎Vdäȑ#ÜÓ<͚’)Üôę7-ÇqĺĘDŁŃżăuLn"Â Š˘˜Ą¨ˆţ`Œ!‘Hs?ŽÇăŠzö@BĽ¨-#ýýýđx<5?÷f$éŚKÉƈ†ˆ˘˜aŒy“kŤˆĂářű@ Ŕó §YŁ"š\^‘ťŸĎWUń*IAŹG-éëëƒĎçŤů97#•jž.@˙Ńp8Ďó˜\EDž)‡yśĄ‚€öö拊@*%)rr8~üxE›Ž5“„ěßż/žř"víÚĽöRšQą˙~ľ—Q–‘žžEŠS 7=ĆŢCh3ô ‡ĂÜf‡”ÂUDc—ËŐsz@ӊ L¸TEř|ž-Ł™$DE<óĚ3Řľk^|ńE>|.—Kíe–źúęŤxîšçt/~’‹ĹŸĎÇ˝8UŠ4°A×sC€BZĆĺrEyĚ)…űŻX,vvttT×EŤ&S‹î{źk%Ÿ_UdÂáV˘ŃLíí혚š*~=00€S§NéţŽ]ŤěÝťˇxA=|ř°ĘŤŠĽeDɁhKK¸ĆëŤU߃3ăń8FGG‹Ĺ¸ďÝŔý_ĹEŤ@ó֊@&ł˘ČF€˝˝˝8tčĐşď5“„@,Ă;7ß|÷îÝPˆ’<÷Üsxĺ•W°wď^•Wh,Ž_ż^üÜĺrBř””‘C‡)"!’”E&łÂý¸zŔ(ľ!ź‹TeŃ3#­6kL2ŠL˝ˆÇă)śő6Ł„”255… .ŕęŐŤ¤BÎÜĺráůçŸÇ‹/žˆÇ\ĺƒ{÷îabb˘řő3Ď<Ă=ĺ JČȑ#GéÉĺňH&›ł.ĐmPHËđ.R•QDDćććţ|zzZדVćŽŠ0ư´¤ĚÄCÇƒŻ~őŤM-!ĽLLL`ddd͝űŽ]ťpęÔ)>|ŘMľš~ýzQöäč“ŕ)#_ýęW‘%Ď%zŔѐP($ŠţšÇWDDcѝ;wŽSTDßäryĹvÄ|úé§IBJ$ ăăăxă7ÖՏźúęŤPquúG’$ź˙ţűĹŻ qâ%#O?ý4ÇU=biéASśęĘ!°sçÎq^“TËQŹrfff濍!*ő Ą™Ł"@Ą^DkӍ&!ĽÄb1źůć›kR ˘(Ž‘˘6ĆÇNjŊ_%*đ‘%HĽ2M[#F166†™™™˙ŚÔs(&"Œą°Ýn_Vę)‚ °Ű›;4žJIš9™YBd\.žřĹ/żž~ýúš (Q;WŻ^-~žk×.CkMF 71Í[vť¨űŮááaŘíöĆXXŠçP´—(™L~÷ňĺËLĎ­ź ŠúnťâÂŤÍ !@aؙ\"IҚčQSSSk˘KFŠŠÚ‘%ÓşzÁdjŃýܐx<ŽË—/łd2ů]%ŸGéŤk(›Ífő^+v{ťÚKPľ ΚEBvíÚľŚ¤´Č’¨—Ëľ&şd”vŢRԖľĎZÁ׌@ €l6› h牢"‹[,–€ŢgŠ€Ů܊śśVľ—Ą*š\^•ŠˆÍ"!Ö\ËŰN‰Ú)ÓR^řűĚ3Ďn˛­š2’H¤UžŞM[[+Ěfý_/,K€1ŚhZCń|C"‘87== #ČHł×Š@6›khČľ™$d```ÍňŇv^˘vüńu“kďÝťˇŚ×hQ@YZz€l6×°çÓ*F¸VƒA$ $‰sJ?—â"‹vuuýDďEŤĐÚjŇ}Ώ’”E:­üÉ­™$¤|śĹ­[ˇ¨S†ťvíÂK/˝TŒxLMMáÍ7ßą ֈ^š…FĘH:Qd"łŢE3Z[Mj/Łn†‡‡ŃŐŐőĽZvKiHćâââw0ŕ 0F4–—%ĹO:?˙ůϛBB€Â…°´@•˘!őóřăăůçŸ/ţť^ż~ožůfQđ&&&Š#ö2*däç?˙š˘Ď!IY,/S-“Qş,ĺf‹‹‹ŠŠĘ4DDŒ2ŕ (źĐšuCźr”î¤yú駋ăŕŒËĺZ y˙ý÷Š]—_űÚ×ÖHČřřřşÇ”Oł5âŕ¸#GŽ(6Ź  ™Rl6c̝Rz€Y9 ëI•œ…ĂáF=ĽbX­C„Ţx‹Ľ•yošzڰjR ‰ĹbT Ę˝{÷'§NMMm(!ňĎnÝşUüş´uZďX,ĹöŽ‘ÉĺňˆĹRŠ_O´śšt?ź Âá°âĚĘi˜ˆ0ĆÂÝÝݓF¨€ŽcœŹę…1†X,ĽčÖޏ§î}i´JůP­ńńqj×ĺ@i˝GŠhlDůîźFˆŠČűÇ()!ůü*bąTÓˇéĘĺš0<<ŒîîîI%˜•ÓĐ)]óóó~ŁDEĚćV*\}ˆÉԂ–eÑňN˝===Š>OŁ)†LMMQ4„Ľí¸ŰĽšbąŘyîšçtÝÎŰÓÓłfG]Ľhiš~ĐŁŒ(š ŃŽ+GCćççý|ކžŠŒĄÂŐB8Ňĺ˛5äßA–‘žž>şŤěßżÍĆk›Ľˆú¨D*&&& ąM___C$(ÔËš\śŚOSĽ@P'4XDcEEŒôŹ…FJˆŒ(ŠBÞS DQÄ3ĎUžť™e¤­Í8)úááa|öłŸ˝Űčh ‚ˆŔŻýëÓF‰Š@ggť!Œ¸RԖÇƒ—_~YwEŹĺű›ź˙ţűT Ę™[ˇn­Iľ<˙üókŇ`2.— Ď?˙<^|ńŢNMMadddMTEŤX,źüňˊĽVB3ʈ čěÔ˙~2ŔŁhČŻýëÓj<ż*ˇňŒąđc=69<<Üo1™Z`ľš‘Jicűm%ъ„ČôööÂď÷ăâŋ˜žžV{9qřđáâçąXŒjCâwŢÁŠS§Ra§NÂ­[ˇŠŃ’/~ń‹käD’$ź˙ţűşů˙čëëĂńăÇ5Ón,ˈŇ-ýZÁj5ÎŽěrmČýű÷Ăj<żj9…‡ľ"ď…Ăax˝^ľ–Á ›MD&“3ôPk"#׍ČVŻeĘÓ”’QŽ{÷îáęŐŤkÄoďŢ˝kÚĽe&&&tľÓńŕŕ &ϛÍ"#­­&ŘlÚŔz)9o6ź6DF51ZT(¤h—Ő^†"hUBJńz˝pťÝ¸xń˘j[ oGyťîv3.ˆúťaöďßżŽND’$|üńÇ×Í$[‹Ĺ‚ăǏĂívŤ˝”Mi1JJP?¨("€ń˘"Kś.EĂCBcX]eЇ2Ýn7ü~?BĄîŢ˝ŤčsU‹(Š˜˜˜(ŞR4¤1LMMajj .—ŤŘĘűŕÁ]Ԁ”˛{÷nř|ž†¤bňůU´´5żç,#6›q&kk!¨,"FŒŠ-EĂKBä髍(hEǏÇ|€p8Ź™čˆ$IÇ­[ˇ°wď^Ý]őN,ÓM䣋ů׋}űö5äůäąí&SK]ď}#ʈ‘R2€6˘!€J]3ĽĚĎĎűĆĆĆ Ő^ 7ŒŇEĂSBršüšĎÁž}űpúôiÍMcĽU˘Rzzzpúôé†KclÍçľb¤n#uÉ@0”熨Ó÷]‚ę"‹vuuýÄ(sE€G)=Ă[BJżˇ¸¸ IĘňXćś8Nœ>}ƒƒƒ y>‚ŕĹŕŕ NŸ> §Óِ瓤,—׈ÉČ#Œ”’ ŃŽŽŽŸ4j‡Ý­P]D`qqńťÓÓÓj/…VŤEˇƒÎ”’R–– n\ĘÄëőâĺ—_6Ěxx¸ôőőáĺ—_nhÝ\:ÁŇŇĆÜHF ƒËŒ°łŽL Ŕôô4żŤöZˆc,ęp8~đú믳x<Žör¸átZu—˘i„„Č,/K›žü”@ŢŤćŕÁƒş‚F‹Ĺ‚ƒ6lŻ™ĽĽX^Ţşmš™eD8Vľ—Áx<Ž×_9Žh!hDD ‘Hüe6›Í)*˘ˇœb#%DF’˛ˆÇť•¸\;˛{÷î†='AlĹîÝťZ ŢŤńxŞâ4iłĘˆQjţd˛Ůl6‘HüĽÚk‘ŃŒˆ0Ćâ6›íěŮłgFŐ^7,–6]„ôԐ™l6×đĘz§Ó‰ăǏăĉp8 {^‚(Ĺápŕĉ8~üxĂjA€GR‘Íćjú˝f‘ŤŐ‹ĽMíep#âěŮł°ŮlgcšI?hFD`~~ţ/ěvű‚߯jK3w´^䤌„ČČ'¸LfĽć5Ԃ}ýýý n˘ščďďÇéÓ§Uâ˜JeH¤ëN…]Fô–VŻ„p8Œ+WŽ ™L~Cíľ”Ł9aŒ…ťťť'im5Ánז]kIBJIĽ$.'Ëjq:đů|Ô]C(‚Ü ăóůš†J%Ÿß^:F–ť]ÔܚęĹď÷Łťť{’1V{-ĺhND€ÂłÉÉICľó€(š!Šfľ—@ť"Soř¸äîš'NhnĄ?zzzpâĉ†wĂČ(™ö4˘Œhé<͋@ €ÉÉIM /ŰMŠˆQŰyBśÚo8­KˆŒ|’kÔđłrÜn7NŸ>#GŽPA+Q5‡GŽÁéÓ§UۤN’˛ {ŸAFZ[M†KÉhą]ˇMŠPhç!n´ 8ęÍы„”>×ŇŇUR52~żŸ„„¨Y@ü~?<*kS1KKňž1‚Œ‚‡Ă8óBdü~?AˆkŠ]ˇ͎ţdŒĹAřöččč奥!CěÎ+c2ľŔá°"O5ôyő&!Ľd2+X\ĚĂá°Şv˘ňx<đx<ˆD"řŕƒ077§Ę:mŇÓӃ}űöŠ&2š\‰Důüjß7KévŁ<‡ĂŞřîŕ&cttž­ĽvÝr4+"Ŕ =öŘc“~żż?‰¨˝Ž˜Í­°Ů, ëŃł„ČäóŤX\\†Í&Şş—,$ŃhápÓÓÓŞ­…PŸžž>x˝^ŐŇ/Ľ¤RŽŠŐ˘WąŮ,0›5}9Ź š@őţýű!ľ×˛š˙—ŸŸŸ÷ÍĎĎ˙*Ŕhi›MD.ˇŞřě #HH)Š”„LfEő;ˇŰĄĄ!ĚÎÎâƒ>Ŕää¤jk!O?öíۧJj9ůü*‰´&ޟz“Q4ĂfÓVG#äUš,P-Eó"‹:Îźţúë˙§Ďç´p×Á“ÎÎvÄbŤŠ˝ŮŒ&!2š\ţatĢúäÚŢŢ^ř|>:t|đ"‘‰„Şk"”ÁápŔăń`ßž}Em\źŇé RЌj5TĄŃâXDŁQš@őŻâńxTíől‡ćEâńř÷:::ží÷ű …4aŞš@Ş|űmUBdcX^–Íć`ˇˇŤžßE^Ż^ŻwîÜA$ÁÝťwU]Á‡ÝťwĂăń`Ϟ=j/ĽH>żŠdňAŐcÚ…ÖeD>÷jqĐd˝řý~äóůůĺĺĺ–JЅˆ@*•úă+WŽź …ŕói>ŇT&S \.—šÓčRJ6›ÓLtDfϞ=Řłgâń8"‘EItˆýđx< @śZŒ‚l„–eÄ岊~ó˘ĄPWŽ\€?V{-•˘aŒ…{{{xâĉĄééiAk'†z‘űח–p9VłHˆŒ‘¤MĚj‘q:Ĺ(I4E$Á;wÉ4~Œ=ą=‹{öěÇăŃDńi9š\KKtóž´)#Z:Gđ$ăĉʧ§'8;;V{=•˘€ššš˙jąXž9<<,mę*P(šĘĺV‘N×~‘jF )EŽąZ-°Ů,š ťşÝîâĹíΝ;Ĺ’u‘ĺCţĐ"Œ1¤R™şÎ j˘%ąZ-†›œ*3<<ŒL&“YZZúŻjŻĽt%"g‹|sddä˛Ďç3Ôl™ŽŤŤŹŚi˘Í.!Ľ¤ÓżŠĺĺÂk&•˘×LľđŁ‰DđÚkŻÁápü€1Ś­ ]FD ĎápL !׎(Z­Ö5_§Ói\¸pétş!ĎOR?……ťŰD"LfEí%#“YA"‘ĆÂB˛)f( ÉČzä銇c"OíőđÄP"‰DâwďŢÍřý~ľ—˛†cǎĄŻŻŻřőôô4Ţzë-ş—$„?tÁ!dHP•ƒdd-~żwďŢÍ$‰o¨˝ŢNDcQI’ŽŽŽBKłEŹV+ü~˙ščČÄÄD1]Ł$!Ę"‡ŕďß_˘‹P“!ËčýűK”˛S’‘Á`ŁŁŁ$é8c,Şözxc8 -˝ç^}őUŚĽz‘däÚľk¸yó&÷ç" i,ĽQ’ĺe‰ţÝ ˆź E?KŁddbb˘áľ{•‰Dđꍯ˛ŽŽŽsŒ1ݡęn„!E’ÉäwLi­^¤ŻŻÉžJß[IDATǎ[ó˝ˇŢzŤŽNšrHBÔ#Ÿ_E:Áââ2—‘NSËŚžĄ˙Om ´ŒÜźy.\Ŕ™3gpíÚľ†Őďm‡\`ęá5͐bŽČf‚ŕEńΟüɟX•Śšyó&nßžééé5vmľZ×Ո”žŘĺhIécj$D›´śš Šm°XÚ ľď…çˆäóŤČdV I+ô>Ň<挔sóćÍuőzVŤ‡ÂłĎ>ťŽŮ ‘ áíˇßÎH’´Çˆ)C‹‚ŕpů‡?üĄl–Š0?? .ÔŮčëë[—şŠIĘÇÁÚĂdjĹŇQlÓýjوH.—‡$­ “YĄˆ‡†éělç:dp# )Ľťťť($& âäɓpÔ¨)ăܚmc,äp8~đĘ+ŻŹ*U/’N§ń7ó7u§Wڧ§šÔ‹ˆ˘í†ěŁWJĂý÷ď/aié$)KŔ’ĎŻ…ýţý%JťhA¸K|ţóŸßňçóóóxë­ˇpćĚLLLp}D"xĺ•WVÎ 1´„ŘkŚâńř÷œNçs_˙úןúŮĎ~ÖÂ{?š›7oŽ+rzňÉ'qôčŃMS-étz¸ĚĎĎs3oQ4ŁľŐDç‡1IĘB’ _ˇśš`6ˇ˘­­đ'É$cČfsXYÉ#›ÍQĘEG(š‹n__ž}öŮ57€ÝÝÝëÎçrÄűÉ'ŸÄÁƒńä“Or_‹L<Ç׿ţőՖ––3ÚźÍh €D"ńÜęęężű|žź÷Łš}űöšŻŸ|ňIl7ÇÄjľ*úbĺSIFôC.—_s‘”Ťľľmm­†Ş/Q’|~++9ärŤ$:FI ‘9tčĐyěąÇpěŘ1\ştiÜž}ˇoßĆŔŔŽ;Vמa›áóůÇcÉdň9î×(MsVcŒĹ“Éäs~řĄâĂÎÔČ'nFkŤ ]]şŻEhVrš<ŇéLqßyfI*•ÁĘJŽR x$ŠTŚ8ŰCŢß%Î„č”Fťşťť×œłoßžt:ď˙ű8vě؆5{8sć Ţzë-Ž-ż~ż~řa&™L>g”}d*ĄiD(ěG#IŇ鑑ŽĂÎĘ­Xk˝č&S‹âwDc`Œ!“YA*%!Kĺ$OayY‚$eą˛’3dŒ1†••$)‹ĺe ńxŞ(ąX Š”„LfŐ÷fCŽć6*xčĐĄ5ÂqéŇ%¤ÓitAĐŐŐAŰÚšţAŽœÄb… ´,(rE–-Grš|Q6ä‡,÷ď/!K#ŮŹ1…ŤŮE3şş:¸ÖGmw>–ťcJ˙îťďvęÔ)řýţ S1ét—.]*>žäâԇC˂5H§ž}w3œNçOcż;==-đ(^˝téŽ]ťVüšW+.đčMÄŤŸ}yYB:ár,Bß´śšĐŇR8᡾=*3™„uwŁĽ?—ŮŞ}we%ˇćë|~ů<[÷óŐUŚi9"‡ŐjAG‡Čő˜7oŢÄ͛7ˇ=§Óiœ9sf´üٟýŮş†ƒk׎­ťŮ|öŮgń /Ô´žx<Žžž>&ÂGńxü隢sš.""“H$ž3™LÓ^Ż—ËäŐC‡­XÖ˛Vƒź1ŢkŻ˝ĆuƒźŽí܎Gč—\ŽĐE’ÍćJIĹ9˛RúQ-ĺżż´ô`ÍsČĎKB…!JHˆ<š:l‘‡˜•růňĺu+O×Ô+!^Ż&“i:‘H4Mqj9MA<‹ĺ?~\äQ3’N§Öľĺ>ůä“xá…*ްžyó&&&&ÖIĚÁƒ׍‡ŻÚPЍ†Ď|Ćąî{[ED~űۄÂ+"Œ€R1ĺĂĘ*™^˝QTä…^Ř´azzşŽiŘCCC¸xń˘”ÉdžÜlu!Ľ4mD(Żf2™?ĺ2R~Ą—žh+•ůůy\ťv­ýŘ(’Â{ƒ<ę¨!BM”:Ő"!ňăĘŁ"ráęFÔ#!ĂĂĂE&“ůĂf– ‰ćˆlc,,ÂÉłgĎţĐív×=Ţjľâ…^ŔĄC‡pűöím[yoßž‰‰‰Šăć͛\ۃ厚d˛ĐqAŃDŃ ť]ä>´ŻV ‘9xđ ţůŸ˙šŘý(ŽňŒFƒAœ={N2ĆÂÜŹSš:""#wҜŮfłp?6AD96›E‘m(6Ú;ćŘącUG.ĘŁ"׎]ăśCz$Áɓ'ŃŹ2A"ňd2ůŽŽŽŸ 2Ľö¤™ŸŸÇĽK—ś„cľZqěŘ1řý~ÜżÍĎjyCUƒÍ&Âéäťł%A„Œ p:m°ŮřĽ›o`wéŇĽŞ%âŮgŸ]—NߨpľZ"‘YWW×O’Éäwę> AhúÔL)‹‹‹˙Ůfłý‡Żýë_ŕš'œzŮŞƒŚ|—Çň ďžž>‰~éK_ZDčő])˛tuuPh™ ś tc:ęˆD"řҗž´úÉ'ŸDłŮě4‘‹Äbą§˙}pp‘ŒT‡ÉԂŽАD9ĺę@ŤŽH$‚ÁÁAś¸¸řďąXě)’ĆBŻÖĂ‹§RŠßkmm˝ôÔSOQk Pʆ P Ś~‚Á žzę)´śś^JĽRżÇŁÜyƒ!QĆX|aaáňŕłááaľ—¤KJ…Ĉ[”Äf˜L-čěl'Š“áááâ ˛………o„¨šUäápœ“gϞĹĐĐąÖˆ E3vě( Mj%ŒJ[[+:;Űąc‡˘h&Š‘x<ŽĄĄ!œ={NŇ 2uĄ3śĘ<œ5˝xńâ˙‰DÄp8 §ÓŠö˛t‹(š!Šfäry¤ÓYÚˆ0˘h†ŐjŚ)¨;cîÜš#řCĆXXí55;ьąp&“ůň'Ÿ|íëëŁ"V´śšĐŮَÇë„ÍfĄ´ Ą;LŚŘl<öX':;ŰIB8‰DĐ××Ç>ůä“čĂöܰÚk"HD4ƒÜQ#ÂG_ůĘWVЈ•rɎv8VX,mj/‰ śÄbiƒĂaŎT˙Á“`0ˆŻ|ĺ+Ť‚ |D1ڂDDC0ĆâńxüiĆŘÚ0?kOđ%!´ƒý aVyă:Ć؅‡çX*ČÓt&Ö rëůóç34‰•?…“ţŁ( mF¨…(š×D?HŽů"׃œ?>*JŐ,ôŞ×(Œą $Iű>ú裚ĪK[ą–„:nˆF wžČŻ9Š~(ƒ<)őŁ>Z$iíŁ]HD4 c,’L&ż°´´ôoT7˘,r °ËeÎvttˆTHpŁľŐ„ŽŽBÎĺ˛Që­ÂČő KKK˙–L&ż@ő چDDăČu#---˙ýäɓ4o¤˜L-°Z űu”ľR*ň>I”zQy>Čɓ'ŃŇŇňߊDĐťB'Äăńď8úöŰogź^/(UÓ6’Jß›ŃÖÖJňĄ‘H^ŻożývŔчçLBĐ;DG0ĆB’$íůŐŻ~őËÁÁAFКĆ"K‰Ëe+ć÷)ÄŢÜČ)=šćĂ岑|¨@0Äŕŕ űŐŻ~őKI’ö0ĆBj݉¨zˇč ĆX4‘H|auuő<ĽjÔŁüät.@”Â1>­­&X­8$¤jSšŠY]]=ŸH$žŔ‹Ş˝.˘:(ĆŹS’ÉäwA¸ööŰo_ŒD"–`0ÇŁö˛šłšfsáíÄC6›C&“ĂĘJůüŞĘŤ#ęÁdjA[[+,–Â˙1 ‡6ˆD"ÂÝťw3Ž'“IŠ‚čŠˆč9UF'žzę)ľ—D -‘Ű‚wě°7ăE3…ěu€ÉÔRŒv•ţ˙Y,m$!!੧žB4 TŒţĄˆˆÎy†|ÚétţĺkŻ˝ö…B!„B!Ú8OC˜L-0™ĚĹÂ×rÄ$—Ë#›Íce%§î›œśśV˜Í&´śš(âĄqâń8|>ĆĆĆŕp8~@ŠĆ€nĎ ÂĂ7äS?ýéOď÷őőąPˆn´Š1ąŮD¸\6|ć3tuu łłVŤ…şr¤­­VŤíčęęŔg>ă€ËeƒÍ&RÄCă„B!ôőőąŸţô§÷?ŸJĽţ˜1V{M„:PD¤‰aŒ…———?#Â_˝öÚkđx<‡Ăj/‹ ‡áńxđÚkŻA„żZ^^ţ IHsC"BČSYŸřÍo~3yŕŔ„FwäÁdŔo~ó›IOĐtT !‹޿ßŕh(Šőőő1ÚD <Üz"ŕčýű÷=÷É"b-ŒąP"‘ŘEé‚ ęĽ< “H$vQ1*Q‰ąĆXź<]CÝ5ATŠÜ Sž†Ąš ÄFˆ›R’Ž9păƍ…'žxĂĂĂT?BĆČCɞxâ ܸqcŔJĂŰAíťDĹtww˙ߊTęułŮlŚv_‚ J‘ŰqłŮlÖfłŸŸ˙ ľ×DčŠˆ3??˙’$őĘő#nˇÁ`PíeĄ"Á`nˇťX"IR/IQ $"DU”֏$“ÉŸœ88Ç§ÓI‚™RáˆÇăˆD"Ĺô ¸\Žh{{űĎeé@A<âŞ-˜ š‚PA<Ü(ĘsŮlöóóóóŸ“Ţßß_”§ÓY”ęÚYK$)J†\ӏNj­łĐÝÝýŠŮlţdffć: ÂĽĄ.$"ĄQ Š€ˇ§§çóů|ţw3™ĚgĺŘíöŐßýÝßm)YTŒ$+˛\ȢQú˝>úh5™LśČľŰí ‹ĺ×&“飚ššO„ÄI8B›ˆ„yX[üEćççűË/ ‹üuyŃěFE´ýĎmmmßŕV`Ą°Bˆ€(ˆ˘RŒ1 #Ť4úűűçwww§Hß#ŒŕĐ`ąX`4š ĽIAĆăńŔĺr űÚČŔ˘×ë;âââ.'ßďw)3zBČD(ˆ˘°ĄŔa`™7o^Áőë×´ˇˇgHž ŁŃŤŐZ źHÁDúŐétÂăń žž>đ=ŠŠŠgΜůÍĺ˗Ť¸0¸ŠB…Q!$JcF Žúúú–×fäççB†Őj…Éd‚Édďr$ nˇnˇ;N\.jjjž””äž5kÖŮ˗/˙'ŠËď÷ŢK"„pGA„™0ĆŹ ÝÝÝ÷H[*™™™°X,°X,8P\.\.ZZZ nńčőúÓR8ńűýNEKˆFQ!„ƒĄâQËěŮłWΞ={EkkëRŕVč°Z­_‰úIÁDúU 'éééçžűß}÷Ý1 †ˇ˘%D(ˆ2CÁĂ:o޼ǂW;ňó󇅣Ѩč8 R͉N¤m U“÷8)˜> "„„@ ÉÉÉŤoܸń€×ëMJLLôß˙ýŒV;bSp09qℿŤŤ‹ †ÎéÓ§˙çľk׎€‚ !!Ą BČ8cEiiiŢ¸qŁ ˝˝=CŻ×ß|ŕŚY­ÖŔŞ!)”8NüçţçÍîîîiŠŠŠ§OŸ^ÝÖÖößďŻTzŒ„¨B† ­z͛7ďń˗/çƒGg‹ŠŠ …BB%…’ĘĘĘŔâyó杺|ůňA•´ZBČ "$Ś1Ć,łgĎޢÓéiooĎHLLôŻYł†Y­VQáÂăń ˛˛N§‡öwuuąÔÔԋ>ŸďĂďžűnő2!ąŒ‚‰9Œą˘ää䧤ZěělX­VŘl6Ún!Qáršŕp8ŕt:Q__ Ú’wh ‡Ä "$&Hᣧ§ç‘ţţ~]vv6l6ŠŠŠ¨‡Q”ŰíFee%ęëëçKHHřB ‰DˆfQř ˘ĄPBb˘)Œ1KZZÚ6ÇłžÂŮXĄÄh4žŰÖÖöՔ-Ą B„Ç3Ľ¤¤Źż~ýú?vww§dffÂnˇSř š!…’˛˛2´´´@Ż×w̜9ó˙éččx—NßŃQ!ÂbŒŮL&ÓßťÝîż5 °ŮlTpJ4O*tu8đz˝0™L˙ŻŰíţßďw(=6BŚ‚‚Š´őâőz7ř|ž¸ÂÂÂŔÖ !ąFÚşŠŞŞ‚N§ë7 űh놈†‚c̖””´˝łłÓ$m˝Řl6ęóAű”8ŽŔÖMRR’ťłłóZ%!"  BT‹1fJKKűŸŇęGqq1l6u8%dN§‡{öě ^%ů_TKBԊ‚QƘuŢźyŻ]ž|9V?™š‘Ť$Cíĺ_ňűýNĽÇFH0 "DcFEz˝ţ7ÝÝÝ)ůůů“/„ČH'njjj ×ë;şťť˙ŸwăQzl„P!ŠbŒ™ ĂÖţţ~ű̙3gŽYł†•––Ňą[BdŕvťQZZŠĂ‡űŻ_ż~=..ŽĚëőî¤m˘$ "DŒ1SrrňoŽ]ťś6336› vťś_‰Çƒ˛˛28´´´ 99ůĐľk×ţ˘ "$ŞFÖ”––Âfł)=,Bb–Ăá@ii)ՑĹLSz$60ĆŹsćĚq8ńƒü ďđáĂpťÝBQ˜ÍfƒŰíĆáÇńƒü Ŕ‰9sć¸cVĽÇFb"Ťŕ˛téŇě'NŔétR*!*STT§Ó‰'N`éŇĽŮ @B˘„‚‘c̚‘‘Ń„„z€˘nVŤuT ÉČČh˘@BäBA„pźňýďBÄHž˙ýď/­™P!\ŒˇC„ąľBB„đDA„D„1f˘BˆöMHL Ž‚™Ƙ)%%ĺ € @‰c’ ))))Š˘ BÂÂ3Ć7\Đëők+**(€ƒ¤@RQQ˝^żŔŁŃřĆĐă ˛ÔÔÔt:]Ťßď˙ůŽ;¨!$Їdǎđűý?×ét­ŠŠŠ/(=."ęŹJ&ĹłęőúƒÝÝÝ)ۡo§V섐1I­ă_yĺéázS—V2 "d\Œ1Sjjje{{{vaa!ĘĘĘčat„IšÝnŘívTUU!55ľž˝˝˝ˆžcCĆC[3d”ŕ:Űoż=űĉ¨ŹŹ¤B ‰ÉdBee%Nœ8Űoż=T?B&@A„ Ă+2 ÍRˆËĺ˘BTBȔX­V¸\Ž@ýˆÁ`hfŒŃóČ0D€aý@%ľ´´0ťÝŽô°!`ˇŰŃŇŇŠŠŠ’Śţ#$‚‘Ű0‡ƒŠQ !\F8ŽQŰ5J‹(‚H cŒYgϞ}•śa!Ń2rťföěŮWŠ]|lŁ ƒcĆôôôˇœ(((˜S__OŰ0„¨˛Űí¨ŻŻgsœHOO›ŠYcߍ1Œą˘¸¸¸wăâââöěŮʊ¨nŒ˘ŹĘĘJűű­÷űý•J‰D­ˆÄƘqΜ9'ŢşuŤn¨pLéaBŠŠŠĐŇŇÂśnÝŞĂ`1ë Z‰´"cE:nZZZœĂá :×5Ѓ†ŽćŔďĎ´Ÿöçľ_Žů÷Ţ˝gt]aiié°_ƒ­?ýü˜×ÉMškŘ. üsV˘‰3Ćü{$68NŘl6´ľľőű|žu´:˘}ßSzD>Œ1cjjęaÖ-[ś ´´”NĂh\CWó`Đđ6Ł{ —úÚpЎ pŚăŤ¨Žeź×őőŻÇţűËSî̏OĂüYiĐĎH@–a0¨d%šy•¨ˆTĚZZZW^^~xΜ9Îööö5~żßŁô؈<(ˆh”T 2kÖŹ™'Nœ U ičjĆĽŢŤhô6‚FC×tô(=4ޤŔ2^ ŃĎH@VâÂ@PYb0c~ümR4Ŕh4˘ŹŹ EEExúé§ď‹‹‹ťÂŁÚ˘ ˘1Œ1cZZڿشnÝ:”••Ń*ˆ ‚GmǗ¸Ô{ßö]UzXŞŃ=Ѓ3_TnŸućÇ߆ܔť( ĚjľâË/żœfˇŰu{öě9œžž^ŃÖÖöÓꈜPŃƘ%!!ᣞžžÔǃŠQĹŃĐՌo3‡~ö6ŠÖ|Ű7ÚFţ{\žr'˛ f,I4#Ë`Śp"ŠZQQŠ‹‹m Ťcűý~—Ňc#|PшĄ…żřя~„ĘĘJZQąŽÔvœ ŹtP舞ąVP–§ÜX9ÉMYFŲ*%Ź)**šSSSó…Ńhü•Çăť"š…‚ˆŕc&ƒÁpĐëőćěŘąԘL}.őśĄśă,ÎtœEműYÚ^Q™‘áäöYˇ!7u–§,CnĘ2̏OSpt$˜Ńh„ÓéDYYž}öŮ_ĆŻ×ű¸ßďw+=62uD&Ë5™Lq‡‹Eé! Žx|ÔúA}Űw‡.ÇĄ‹ÇÜ &Ľ˙„VLTÂnˇĂjľÂfłĺœ?ž‰1FÇ|FADPz˝ţwś>ůä“TŞľgńqëg¨m?‹ĆŽ J‡p42˜,I\8,˜eX,8NŘíö¸={öÖëő;ťťť˙^éq‘đQ c̔˜˜X=mÚ4sEEl6›ŇCŠIÁŤľÖjîč,_c×4v]@EóĐĎHŔĂéšXž˛ §˙„VK˘L*dľZ­())Ůb0ęęę* ­ąPˆ´łpáBڊQŔĽŢ6|ÔZ‹[?ŁS`đą´Zňs”ayʝx(ý'x8=—jK˘ČfłÁbą0›Íö}ÚŞAH§bh+&ş¤đqčb5mšII…ŻŻű=–$.Äڌ %Q2rŤ†NՈƒ‚ˆĘ1̃Ąúć͛?¤­˜č đAxh캀×ÎýžBIoŐlۜ횥S5ÔMÝ(ˆ¨c̢×덓>řŕƒi´#ŠćăĐĹjÚv!܇’ĺ)w…Ş)‘ËĐVÍ´G}ô‡7oŢü/ĆX5@S/ "*Ĺłétşwß}w5(“Tó!ˆ DnŇöÍĎQ†ľjJ_‹_~ůĺ´˘˘˘”3gÎÔ2Ćśúý~‡Ňă"ŁQQ!éhî–-[PVVŚôp4çRo]<ŽƒŤŠÇQ”TčzűŹŰđxFÖfĘĘĘ`ąX°mŰśÍFŁńÇT7˘.DT‚1fIJJ:|ăƍĚO>ů„Q=]=¨hŽ˘Ő"Œ[kńqkm`•d“šjI8ęFňóóďNJJú‚1ś†ęFԁ‚ˆ 0ĆŹqqq˙ą`ÁÓé¤zşšQŃ\EľDXÁŤ$k3Ä&s!=-8B‹---Ějľšz{{?cŒý~żßŠô¸bÝ4Ľëc6'Ö­[G!„ƒƒŤąţôóX]łBьCcuÍ6Ź?ý<^ŹVz8B“ęFÖ­[§pbh& ˘IEŠŰˇoGiiŠŇĂ–tôśüü>Ú~!š&¸)?ż%‹6Đŕ)’ú˜L&źňĘ+TÄŞ0 " `Œ“““˙íťďž[KEŠS'Őź}ĄŠžőBbʡ}WńsW^ń{<ł°ęHڍ´´&“ ›6mښ’’2çÚľk˙ąF‘(cŒÎüőŻýÁ_|Aϋ™ „ ęčAů×űđö…* $S4TĊüüüÇ–1ƖS‰.މ˘Ą“1_$''˙MMM Œ ÓĽŢ6źzn7Vĺ_ďŁBČ)Ź8ţ ^=ˇ—z۔’P, jjjXrrňß ¨ĄÉ9Š(ˆD cĚ2sćĚÓ ,0}ůĺ—ÔŽ= ]=(?ż÷˙o¨hţ€!ăččAEó¸ďřCůů}袟•IX,X`š9sći #ŃCA$ c–¸¸¸Ď–,Y2‹NƄN Ň !$tŇ ’ĐI'j–,Y2+..î3 #ŃAADfCGĂžXˇnÎĺrQ ÁČB+ „LMđ– ’ĐF¸\.éxďtźW~Dd4ôŽ(..†ĂáPx4b8xąš!œęC‡Ăââb¨ 0"/ "2™;wîPQRRB!$ľgą˘úüÜUF„™tôŕçŽ2ʍ~ľg•Žę9”””@ĹМNd@Çwe ×ë÷ÝwßmĽ!“kčjĆŤ_íƙŽŻ” !1ăŰžŤŘpú,Oš/ßš™ZÇO@z`ŢŚM›ţUŻ×/ŁĆgüQá,>>~___ßz !ëčÁŤçvSvBtŚă+ŹŽŮ†ľâ奛ŠÉ8¤š|ÓŚM[ăăă ˝˝˝”‘śĐÖ GBBóvsV†B!*qčâqŹ8ţ ŢnŽRz(ŞełŮPQQžžžőńńńtŒ# "œP™\mÇYنňóâŢoŰŰŰqôčQƒ„ˆzP™9CČɓ'ŃŰۋşş:n׍–†ŽfRóhčjVz8a;zô(z{{ż?yň$ZZZ"ž.…‘ČQ “Ü!Dň—żü…ŰľŁĄüü>ŹŽŮ†ĆŽ J…"ŁĆŽ X]#ÖęH{{ű°ůrrr™™ÉĺúF"CA$ z˝ţwŃ!đÍ7ßpťžœ‚WA!ąC¤Ő‘‘ŞńńńxěąÇ¸žFpŃëőżăzqŁ ˘šsçţw9ÚśBpY2”ŰŰÍUXúZ!$F5v]ŔúÓ/¨şZcc㨭îUŤV!55•űkIaäťďžŰJĎŚ ő ÁГ˙5!dč\:€Á0Âk鐧ށlůÓŤTŒJ 4Bű¸ő3ěúńËŞë;rěŘąażOMMĹʕ+e{˝ >#˙ĘëőűýŮ^L#(ˆLb(„T”””ČBVŹX9sć–żůć$$$ ŽŽ€ ŕžűî“%͇âŁÖZ<çÚAŨ„aÎt|…ǟÁ›–gńpzŽŇĂ€as§äЧžó{[ZZpřđa¤ŚŚŽű=Ą˛Ůlpš\(//Ż`ŒÂČÄ(ˆL€1fPQ\\Œ˛˛2n×=věؘ!䊧žöCóţűďŤň—Ož<‰Í›7cɒ%ÜĆ4™Ž”ýT4ľ×$„ˆĽ{ [˙ô6™EɧřęČČڐœœœQófoo/Ž=:jĺ$Ň0RVVÇƒ={öT0Ć\~żßŃ5ŒjDĆÁłÄĹĹ}V\\ ‡ĂÁőÚ+VŹśĺ"…Ă~HF†ŕŻďŢ˝;j˝FşšąţôóB!!ŠhţëO?Żh!ëɓ'‡Í‘c¨žú`KĆ@Ad Œ1Ë̙3O/^źXÇ;„ƒ?vť™™™ĂBˆdäśKffć¸)^n/VSA*!$lR!Ť-âǚƒ T[ZZPVV†wŢygĚ|K–,ANN—ą8,^źX7sćĚÓFĆF[3#0ÌIII‡,X0Ëétrťî;ďź3lYP #ńńńŁž733íííXąbŰž‘)˝ŽŽ.âĺÉ<çÚAψ!„L™Ô"ţLÇYźiy6jŻ;r5D*Po&řű{ě1n!Dât:aľZg}óÍ7‡c?ôűýŽ/ 8 "AcƄ„„3łgĎ^ŕt:a4š\W*Lmooś˛1V€•+WbĺʕŁNĚŹXąbX7Ŕńśn"uЎ [ţô­‚B¸8tń8źÍŘőă—0?>MÖ×k5äąÇĂɓ'ÇŹťČŞUŤ°bĹ YĆd4át:q×]w-¸~ýúĆŘr #ˇĐÖLäää›>}ú>řŕƒiźC0öyöądffŽ{lˇ§çÖi•ń‚L$j;Îâ‘O¨C*!„ŻĆŽ xä“m¨í8+ëëHÉ¤ŚŚV“G†Šnäő×_Ÿ4„DZ“g4ńÁL›>}ú’““˙-˘‹i ‘!z˝ţw׎][[SSĂ,~Űx#ßź‘<‚şąąqŘőx/–Ÿß‡ §_ Łš„Ytô`ĂédkßŢŢ>jŽmoou„78€„ŇSäŘącxýő×#.`ľX,¨ŠŠa׎][KÝWoĄ­z…l­¨¨ĎkÖŹöC =ó Ü%ŔöövěŢ˝{Ř×x-#v ôŕ9×|ÜZËĺz„2‘ňŻ÷áR_^^ş™ëßÉ:RÇÇÇś`BYQnllÄáÇוVˇ#ŠÍłX,¨¨¨ŔŚM›ś2ĆÎPZcĚ  bűöí\–I233G%îpOť´´´ŕő×_śŹ8V ÉT\ęmĂúÓĎS!„DŐĄ‹Çąţôó¸ÔŰĆíš999ㆄ+VŕĹ_Äʕ+' !˝˝˝x˙ý÷QVV6,Ü,Y˛ŤV­Šxœ6› ۡo€ŠĄ{PL‹é‘Ą^!˙ąnÝ:”––Ęö:ŤV­śoŮŢގcǎ…´$8VUff&—6 öĄ­Bˆ2¤ş‘wďů%˛Í\Ž)­KŰ(#ONŚŽŽnT=‰´’"ÍŮŇĄHVFJKKávťą˙ţ˙`Œý$–žĹl :ŚŤăŐ5uźgĂHű‘Áű‹Gpy°ˇˇďźóΨâÖĚĚLŘíöˆÇzđb5~îâ×-–BŚ˘{ Ťkśá×;Ď(ŕrM)Œ,X° ä•ăöövźóÎ;ŁęI¤UiŽŮF!’0RVV—ËĽ‹őc˝1D Cő72yӕNÇäääŕąÇ•žWŹX1ěš˝˝˝8yň不"ńńńXšr%ZZZŞ+VŹŔc=ńi™WĎíŚ.А•hěŃ/O]řúüYił|÷ž7†ýţRo.őÝZ†?Ó>xz˘k GˆGÉyýÜU†ĆŽfźźt3—ë…SCwěŘ1=ztÔi›‘=EF†“'ObΜ9S~ˆžtŹ7333Ó`0Třє.$¸˜ "z˝ţwŒą~ňÉ'Œgn=d)xO2˛pu˛U‘ĚĚLźřâ‹x˙ý÷šuúŁ&eą#qFB h,1˜á"qF–$.”ýő—§Ü9áďqÇčżÓŘu]=ĐŇčm•.ÚBÔźŠćĐ5Đľćg---řĂţ0ŞČuĺʕXľjŐ°šyŹśďRwěHFÔÔÔ°űîťď‡z˝ţwÝÝÝŃÄü~żŇcˆ*éiş\ŠSß˙ý ťô=őÔSژüţÇ{LÖGRKşz°ţôóÔDƒ¤Ŕ‘e0cI˘óăӐ•¸ú(˙+!DbˇŰąk׎~ŸĎ—KĹŤ1DcF˝^˙_wß}w ĎgČHÚŰŰqôčŃQéZ\u=ň=փďxĄ“1bËJ4#7u– ýŞdč‹‚ČXžíťŠÚöłhějFműYŞA”~F×5ă/ŒD3„HŹV+ţüç?wtww˙MŹŻĆL1Ÿ'&&ţđË/żäÖž},RgżńZšKP'OžV/ňúëŻs_Ą")x,OY†Ü”eQŮ^‰„ZƒČHÝ=¨í8‹3g)˜FŠ0˛dɒQ'hä!ŕńxp×]wÝěęęúÂăńÄDńjLŁŃřĆ͛7Ÿűä“OŚńîœ:žĆĆF;vlÔY’šš:Ź];ďU‘ƒŤńęšßSQšÄ x8ý'ƒÁC…+“%ˆŒ$­˜œé8‹Z?Ł­•ÓĎHŔËKĆíxďxĆŞ‘D#„H\.îťďž›ÓŚM{ÓăńŠH Usrr­’ŚŚŚˆƒHď_}pPŐÉJ4cmFž›+ÜŞG,X’¸/-ý^Zú3|Űw]ŠĹĄ‹Ő´…Ł2ŽćđνńßÓÉrý‘[ć’ D5„ƒmŕN'Ţ{ď˝ýŒąĹ~żßŐD‘ŚWD Ă-\¸đűźš–Ej˘6<śfnţ3Ś}Ý=XúšD áC++"ăĄP˘Y‰fź{Ď/ĄŸ‘˜ëxŤ05X´Z-óx<°Z­¸páÂ˙çőz˙&Ş/Eš "z˝ţwÓŚMŰRSSâU*)HŤćB$F”ĄĽm—Ph=ˆŁíĺ‡ Ď0ŇŇ҂ţçöľĚĚL,X° đ´tšN6NĆĺr!??ßóćÍ]ZmvŚÉ ˘t]H¨ÚŰŰQWWqĘď’ÂHôHáăĄô\Ľ‡UąD‚}ÜZ%D^c… Ď0ź"\"%i˝^DsAd¨.¤éÉ'ŸŒs8JGv“ý R‘Ďüř4lZXˆÇ3Tý1[šÄj‘tôŕŕĹ㨸PEŤ$2˜(„Hx‡‘“'OF˝056› ď˝÷^żĎçÓ\˝ˆć‚ˆŃhüÜd2娼.DNĄţRá+VW?ĆëA$­’đJ‘đ #˝˝˝Ş !Ŕ­zˇŰ]§ľţ"š "FŁń Ż×ű‹/žřjŤ á-Ü< #‘Iœ‘€Ç3 °É\¨ŮÂÓŠ  2Úˇ}—”wd IDATWQŃ\…ƒŤŠ?É…B$r°ŞËĺÂřC †_iŠżČ4ĽŔ cĚâőzącÇ !cˆVwB­™Ÿ†’EpňÁˇńŇҟQ!“ş}ÖmxiéĎpňÁˇQ˛hƒěĎJŃšŠ„˜1í{¸ůW™FĽ‹;vě€×ëýcL37:Mʈ0Ì _˙čG?š#ÇsdÔ¤÷ŻžˆÎĐÓĘHhćǧĄäŽ X›ń ŇCQ5Z ÍĄ‹ÇQţő>Ş#™ÄTCH°HçHX­V|ţůçéééšC ĎŁŃDIOOťŻŻĎÖŇŇ´\rđb5ÍDüƒJad|ą@ÜnwŕŸ}>Z[[żˇZ­Łž˘ üĄ ==:Ý­›ƒÉdŠp¤b˘@2>!DšŰlćGeďŔŞ$ÇƒĚĚL˙ŹYł­­­Ď(=žH żĄ6tTwÓáÇ5]œÜś}ýé"ú•śi(ŒÜ D #mmmE˙¤Ź 2‘šššIż'..ééén…“‘żjÉڌą6ăA $#đ ! ]́šRŤaÄh4bϞ=l͚5›cˆ~¤WčƘ1..îĘşuëtZ>Ş;ÖSty˙ŕĆ*­iŁľľ'đĎĄÉlßž}Ô×&Zyĺ•W"~M)¨¤§§Ăh4ţ9xuEdHä›ËbĄ.Îfła˙ţýžţţţš"oŃDć̙sbÖŹY÷}ůĺ—Ó´ş2V‘P™:-)t¸Ýî@ŕđz˝˛˝žAd<ƒ!JL&“đá$V‰Üs˜ÖÈÇăÁ]wÝułŻŻď“żüĺ/÷+=žŠ6ˆHÝSOœ8ö’ą(.őśá‘OśB$F“8#›Ě…xfaĄpMČ<Ünw x´ľE÷ŚĽŚ 2–´´´@01™LÂmŐvôŕí U¨hފ‰cżŃšťô3đá}oiöô’ÓéÄý÷ßÜuUČ Â3ętşÖ-[śÄ••ióq÷]=Xúy4v]˜ô{)Œ„ćńŒźźôgÂŸĎ‡ŚŚŚ@řsľ#j"# †@(Yźxą0+&Ý=xőÜďqđbľŇC‘M´çŹ%‰ ńî=o QŸýpŮívěÚľŤßç󥋸E#d™3gΉ„„ŤËĺîSO¨Šů‡Bˆ„ÂČřrS–áĺ;7 ńşÖÖV455ĄŠŠ)ę+“-ˆŒ”––†Ĺ‹cńâŁY5k캀WżÚÚŽłJ…+ĽćŞ%‰ ńaţo§üšjćńx`ąXĐÓÓăq‹F¸S3C[2Öh6„<çÚVnՒĐiš[g$ŕ奛U_"ŚŚ&.EĽdlmmmhkkCMM âââĄdńâĹJmLKbß=żÄĄ‹ÇńęšÝšŘŽQňSc×<çځ7-ĎNůľŐĘh4Âápŕţűďˇ2ÊDۢ*ˆ ’ywëÖ­š­ yőÜnşx|J—ÂČ-Ϙ QrÇŐnĂPřPV?ęëëQ__ŻúP˛6ăA<œž‹ňŻ÷áíć*Ľ‡3ejXľ=tńxŕŠÖX­V”””`çΝď2Ƅ:E#Ô֌Ö—÷ ‰„~ŕ•’•hƛ?|V•Ű0nˇ.—KŘđ!úÖL(¤PbąXTŮǤąëžűb‡?—jš“~mąk˛Çˆ¨Î„YaŒYĄáĆeÁMxx\+ÖVFg$ äŽżĂ&óŁJeÇ—Ë—ËĽxą)™\đJ‰Á`€ĹbĹbQ͜ł$q!Žäż…ŠćPţő„ŘŽQ[€ŸťĘe0kîXďˆFg˙î÷űJ)Â<ô.!!ᢨ¨HéĄpwЎ ëOżŔőšő •(gđsS–áĂüߪ*„455a˙ţý(//GMM …y˝^ÔÔÔ źźű÷ďGSS“ŇC Řd~ć˙š)˔ʄÔB$ëOż Éž-EEE(,,DBBÂ˙Vz,Ą"ˆĆ7ŚOŸžŞĹŁş]=Řň§×" ăŃz‘öz÷ÝóKU<×çóÁét˘ŹŹ ď˝÷Ο?Żô'çϟÇ{g˛˛28Nř|>Ľ‡„Űg݆}÷ü//ÝŹĘcŠj!Ňľˇüé5!V•ÂUVV†éÓ§§Ć7”K(TDc&Ż×ű‹W^y…ŠqĎ6RS9!­†‘ŹDłjVAZ[[QYY‰_ýęW´úĄqŇ*ÉŻ~ő+TVV{( R¤Őľý|Ş9„H¤“4Zc2™đĘ+Ż0Ż×ű ƘIéńLFőA$55ľ2;;vť]éĄpW~~>n­•ýu´FJmŔ‘üˇ_qťÝp8Řľkęëë ‰žúúzěÚľ ‡cŘS‹•pűŹŰp$˙-”,Ú č8qBˆäăÖZ”Ÿß'űëD›ÝnGvv6RSSU”WŐA„1VÔŢޞ­Ĺ-™ÚŽł(˙:zo~-„‘ůńiř0˙ˇ(šCŮÉÖĺraçΝسgZZZ Q^KK öěك;wÂĺr):–’;6ŕĂüß*ÖÎ\´")˙zŸćǃ[4íííŮCýˇTKľA„1f4 okŽgČĽŢ6lůÓkQ]‘ĂČĂéšřđžˇ=–ëršPVV†ŞŞ*Őu=%ĘkkkCUUĘĘĘ $KâĂűŢÂĂéšQ}]QCˆd˟^Ó\ńŞŐjEqq1 Ăی1uýƒjƒˆÁ`xŢď÷ľ¸"Wqj(D #‰3đŚĺYěüńKŠ5'  T˙A&ăőz$ú Řůă—đŚĺ٨˛ŠB¤×Wâ˘ÜĘĘĘŕ÷űƒáyĽÇ2U‘ŕUľœßçEîâÔPˆF'ˇ7kŃîvťąsçN dJ¤@˛sçNĹjHÖf<ˆwďy# ?§b‡‰‹WFŁę WUD´Z zđbő”ۡóŚö0ňpz.޽痊lĹ´śśÂáp`Ϟ=´C"ÖÖֆ={öŔáp(rĘfđÉłż”eŤFK!DrčâqÍ=ůXí…ŤŞ "Œ1Ť Tşšńęšß+=ŒaÔFJmPd+Ćăń ˛˛ťví˘"TÂ]KK víڅĘĘJx<Ń} ˆ´UĂóTCˆäŐsżWݘ"T¸jUz,#Š.ˆčőúƒ………š*PíčÁs_ěPŹ.d"j #‰3°ëÇ/)r*ĆétbçΝt —ČŽžž;wî„ÓéŒúk—Üąť~üRÄu#Z!Ŕŕ؞űb‡ŚšY­VBŻ×Tz,#Š*ˆ¤ŚŚžĐÝݝ˘ľŐňŻ˙ x]ČDÔF¤z‡˘\éďvťQVV†šš!DGÄÔßߏšš”••E˝~äĄô܈ęF´B$]Pţő”WeeečîîNIMMĺűL‘Š&ˆ0Ì===ۡoߎʧ^NŐG­ľ¨hţ@éaLJÉ0’›˛,ęő ű÷ďǞ={¨•(ĆëőbϞ=ŘżTˇk¤ş‘pŸU+!DRŃü>ŠBÓÉh1™LŘž};zzzśŤé8Żj‚ˆÁ`x~ć̙3ľT Ú5Đ#TśaäńŒě‹pb Wmm-vîÜIς!ŞqţüyěÜšľľŃťéég$`ß=żÄă!}Ź…És.mmŃŘív̜9sښŽóŞ"ˆhő¸î–?˝ŞĘş‰D3Œźźt3~m‰^đ”NĂ;vŒśaˆęô÷÷ăŘącQ?]ók‹//Ý<á÷Äj¤ţ"Ż*= nÔxœWA$99ů7™™™š:ŽűvsÎt|Ľô0Ś$aäMËłQ}`Óé¤Ó0DŇéšhłn2?Š7-ώůgąB$g:žÂŰÍUJƒťÝŽĚĚL$''˙Féą*"Œ1Óľk×֖––*=nşšŁú9ČFg$ŕĂüßF­I™ÇăÁΝ;QSS•×#„—ššěÜš3jľ#k3ćůżv˘†BČ-ĺ_ďţ˙C°ŇŇR\ťvm­VE"ŠŠŠ•ůůů°ŮlJ…ľŐ ď0’8#ďŢóFԊRĽZjJFB‘——‡¤¤$Ľ‡1L[[[TkG‹Xß@⌠!#HGzľÂfł!??_MÎ "Ró2-­†”Ÿß§ęŁşáâFŢËűŻř—¨„ŸĎ‡ýű÷ W 2wî\˜Íf˜ÍfŐÝľnîÜš(((Ŕć͛‘——N§ô¤Ú‘ýű÷ĂçóÉţzKâđŠÁ{yż˘2Bc×”Ÿ{ľ;XiiŠ*šœ}OÉOMM-[ştŠfš—iaKf,R‰äÓQÂ÷faá÷nç<˛ŃÜn7öďß/LIJJB^^–.]:ęć×Ü܌S§NĄšY;šZ žŃétČÉÉÁŠS§ŃhçϟGYYÖ­['{‹ƒ… ‘ýŹj1„Hʿއ‡ććFý är°Z­ČĎĎÇšsçĘX”‡b+"Z\ ŃҲÝHrrr„9Ž;mmmQ #WűŻáRŸ¸?<şxľg•FĔZ‰ZŃÜjČWÚHŔ‘’ÚśGÚi".—K˜“1“ŠŤŤ(‚ŽŽ%¸@•›EN§Ó [ ihhĐäż×ţţ~8Y‹X&ÜhO´sOPbU$jAdŢźyŻie5äíć*Í÷ •Üώqš\¨ŞŞŇDk\‚ˇF6Ó –——řł+WŽŃßBírrr†­2im5$X?ŞŞŞd #ŇłiČ`o-<ĄWZ™7oŢkŃzͨƘéňĺËyvť=/'ŤŽMśqŸŠ7-ĎĘB>˙üsĄNĆ„ŞŽŽnاđąVEňňň_÷ů|řă˙ľńiURRҰăşcőoŃ˘ŞŞ*|ţůç˛]IâBźiyVśë‹¤üë}š(\ľŰí¸|ůr^´žĚ• ’œœü›ĚĚLEăĺdEރ^^şk3”ő5–-[ڊFMr^™;wn pUjëB8€+WŽ(2N-y\ˇŽŽNáEGzz:–-[&ëkŹÍx//Ý,ëkˆ@+…ŤEEEČĚĚDrrňo˘ńz˛ƘéÚľkkľPŇĐŐLŞĎ(Ŕ&󣲿N\\œjşFňÖÜÜ<ěF(=ůuăƍí˜ÎÎNüűż˙ť&k˘Íl6ŤˇŠŽŽţ¸n(ŇÓÓałŮ'űkm2?ŠÇ3&ŽyЇ.×ÄI˘ŇŇR\ťvmm4VEd"ƒakff&l6›Ü/%;­#E"7e~m‰Ţ›–ĂČŠS§7CN7ŹOČŠS§°{÷nZ ádäódba5$š!Dňk‹š)ňŽžˆ@ ÷ ›ÍƒÁƒÁ°Uîג5ˆ0Ìýýýv-„ÚŽł1ŮA5XV˘ť~üRÔ_WŤa¤łłsT7Ďşş:źőÖ[1ó‰=rrrF…<­S"„Hvýř%d%Ž]€+Ît|Ľ‰ăźvťýýývƘQÎב{E¤hć̙3ľP¤ŞĽÇ>OEâŒźůĂg{НVĂČČç›$%%ĹDe´čtşaŤ!# …ľHÉúĄš"֏őjážaˇŰ1sć̙d-đ”5ˆčőúߏYł†˛†)ŮźXoűŽ*= EÉ}B&Z #ÁGHÍfó¸ÇyIř‚[é<:­EJ‡ ¤žíť*üshŒF#ÖŹYĂôz˝ŹEŤ˛Ƙľťť;Eô"ŐXž ”,ڀ‡Ňs•m†‘‘MľVŻ^­ŕh´#))iŘjČšsç4˝Ú¤–"y(=%‹6(= Eiá94ĽĽĽčîîN‘łÁ™lADj`f2™äz‰¨¨hފéăş§ç˘äuM&Z ##ŸCÜý“LŮlVg“““ƒ7NÚV_Dj !’’;6ŕa•|ˆQB÷@*orf2™dop&KŃJłŽź}Aě7Q$˛Ͳ-Ż~ţůçuKŐZšrĺʰ“÷Ţ{oHOç%㍍ŤCyyů°ŻfłŰśmC^^žfţýň!ýýý˛5={ÓňlLŻž}ĄJřUšœÉDŇŇŇţ§˜ĹňjˆœĹŠ.— ~řaÄϏŃZ >)3˛Č’LĎçÑ#G°{÷îQÝlKJJBz˛šń !‡~řĄ,íŕc˝xU Ť"Rƒł´´´˙)Çőe "^Żw­†ˆí奛e)N•žÜz˘.…‘A>ŸoŘqޑÇNÉÔ]šr{÷îŁu":ŤWŻĆć͛…,ćB¤'őĘőlš%‰ cşóŞVEl6ź^Ż,űô܃cĚćóůâDďËŤ!§çĘŇž˝ľľGő5 #ˇŒ<ÎKŤ"|544ŒęÓ2wî\lܸO<ń„0ő#r„ÉŃŁGG}‡ľĆl˝ˆVEěv;|>_cĚĆűÚ܃HRRŇöââbˆ|d÷Ro[Ě>Řn~|š,u! #Ă9rŔŕ =qWRçڑő#}}} Ž*4r†Éţ,RoZžĹüř4î×Aů×űpЎMéaL™ŃhDqq1’’’śóž6× Âłtvvš„_ ‰á-™]?~‰{]ˆĎçCeeĺ„AƒÂČ-ÍÍÍ¨ŽŽUhIřęěěđ#G°wď^477ăÓO?U}7[šCHđ÷TVVr˙÷ĄŸ‘ HwfľýŢbłŮĐŮŮibŒYx^—kIKKۖ™™ ŤŐĘó˛QŐ5Ѓƒ1ú`ť’EdŠ Ůż?ÚÚ&˙$@aä–ŕçĐy577cďŢ˝Şov­"ikkĂţýű§üZăY’¸0fű‹źx\čZŤŐ*­năy]ŽAD EŞąZ’•h–Ľ_Hee%ZZZBţ~ #„Œí"iiiAeeĺ”_s<%wlˆÉ#˝ZŠá]´Ę-ˆhĄH5VOĘ$ÎHŔŽżĺż\ęršP__öߣ0BČ-J…I}}˝,'ivýíK1y¤Wô46›{Ń*ˇ b2™ţž°°Pč"ŐX] )šăďpűŹŰ¸^łľľ5pLwޟ‰uJ‡IUU÷âŐŰg݆’;ţŽë5E úވŃhDaa!L&Óßóş&— Â3šÝŐńäŚ,Ă&óŁ\Żéóůŕp8"ž…ËÔB$‡ƒ{ÝŇ&óŁČMYĆőš"ĐÂŞˆŰíţ[^Vš‘”””őƒAčNŞľ~sŤ!R÷TŢöďßQxFa„Ä"ľ…ézrŻĆb×Őî|Ôú™ŇØ˛˘˘" ¤¤¤Źçq=.Aäúőë˙(ňj”Ÿ˝ž!rlÉ8Î°ŠSCAa„Ä5†IKK œN'×kĆęč÷›Í†ëׯ˙#kEDc–îîČÁ‹ŐřśďŞŇĂˆŞŹD3÷-ˇŰššŽ×”P!ą@Í!DRSSˇŰÍőš›ĚĆÜ)šoűŽâŕĹęÉżQĽl6şťťSxô‰8ˆH˝C,ŽýM˘ęŔo†Šâ˝%ăóůdYś Fa„h™!D˛˙~îő"rlŤČ÷‹Ĺ­§HÄAÄăńŹšwHCW3Ît|Ľô0˘ęs!÷Će“uNĺ…ˆüŒF#L&, ŹV+ŹVë”ężŠŠŠßbąŔd2 }ŞNN"…éľx÷Y’¸Ϙ š^SíÎt|…†ŽćÉżQĽěv;<OÄu"Ěď÷Oý/3Vŕđ… `2™"‹"žsíŔĄ椚8#'|›k÷ÚÚZ;vŒŰőB!ÚÄ­FéééHOOŁŃƒÁÖ5JKK‡ý*Ż× ÇˇŰ ÇƒÖÖ֘ţď ę{yĺʕČÍĺ÷ ťîŹ8ţŒĐ'JÂľ6ăAYžď nˇ .€5~żĘÉô{‘ "99ůЌŒ aCH×@OL…xyéfŽ!Äăńp/^ …´2É.­ŒÄBŃét0™L0™LHOOGffŚ˘ă1 0 ŁĆŃŇ҂ÖÖV¸Ýn¸Ýn͡š9„ƒĹé‹/ćśŇĽŸ‘€——nĆsŽ\Ž'‚Că奛…<9d2™‹/>@™ ŇÓÓóˆČEŞ"7•™ŠÜ”eX›ń ×kň<Ş. #3™LXźx1L&ŇŇÄxâiff&233ą|ůrƒĎ;qťÝhjjâ^ Š4ŃCˆôúű÷ďÇÖ­[š]smƃ8tąľgš]Sí*šŤ„}ţŽÍfĂóĎ?˙H$טň֌śeVT?S§e>Ě˙-×Ú§Ó)Ű)™phaBçeńâŁ˙Eňď#TSݚ™Šţţ~455ţ'2­˝góóóš>ě´ąëŠůn×SťŰg݆“o+=Œ)áą=3ĺѡe>j­ŠňxF×ŇÚÚŞŠĐĘH´Ă‡Rââ␝ěělĄC‰ÖB0x¤wńâĹÜ Ŕ—$.ÄăBo ǡ}WńQk-NçWo-<śgŚ|jFôm‘M…+qF^^ú3Ž×ů$ěv;ŹVŤ'r´B$źç„——ţLČş‰ŠůždłŮĐÓÓ3ĺí™)ĆXQżNԖî—zŰđqk­ŇȚMćBî§dxwOĺ!ˆÉdÂşuëPRR‚üüü°Ošh‘Á`@~~>JJJ°nÝ:ŐŽŇj9„ƒ…ĆľľüćUýŒlŠĄăźˇÖâRo›Ňؒ˘˘"ô÷÷ë†J6Â6Ľ ’œœüTvvśjŕ'K'ećǧᙅü~˜•:%*­†‹Ĺ‚­[ˇ˘¸¸‹-Rz8ŞľhŃ"cëÖ­Şj˛¨ő"q:đx<ÜŽ÷ĚÂB̏ŁĐšQďMŇöLrrňSSůűS "7nÜx@äm™XŮw€’;6p] 9zô¨b§dBĽĽ0bąX`ˇŰQXX(ĚÉ5HKKCaa!ěvťâ$VB08Nž[4ú (šCĚÓ$S!ň˝ÉfłáƍLĺď†DcŻ×›$ęśL,ŠÎOăz\×ívăüůóÜŽ''ŃĂHpĄí—Š3 Š’X !’óçĎs=j˝6ăÁ˜Y‘ŠVETTTŻ×›4•gτDfϞ˝Eäm™~ôr¸x’ŕÝŇYn"†“É„­[ˇRáL $[ˇnÚÜ‹!DÂ{ވĽUQďQŇöĚěŮłˇ„űwĂ":îžçĹŁ)–:Šň^ q:đz˝ÜŽ-˘„ŁŃ›Í†ââbڂ‘QZZŠ‹‹ałŮd=eË!láĎł–,–VE]<.l‹{ŤŐ Nö陰‚cĚÔŢޞ!j}ČG‚&ÍŠŕů Âăńp­†6ľ‡ŤŐŠ’’ĹۮǒĚĚL”””pmÂ%‰ő"Š­­ĺZ¸KŤ"˘ŢŤl6ÚŰŰ3cŚpţ^¸+"E‰‰‰~Ľ‹żŚJäsÚác5DíŞ“Qc1™L°ŰíČĎĎçr=žüü|ŘívnŰ5Bnéďď§U‘)ő^eąX˜˜čViXAdŢźyŻYł†…52•¸Ôۆ3_)=Œ¨ŕůÉĄľľőőőÜŽ§$ľ„N‡UŤVĄ¸¸˜ę@TŔ`0 ¸¸ŤV­‚N§›ňu(„ŒV__Ďő˙KŹŹŠœéřJ؞"kÖŹaóćÍ{<œżVš|ůrž¨ő!˘V"‡‹÷jˆÚ:¨FJé0’žžŽ­[ˇęFÔcůňĺŘşuë”˙ťRĎ9$–VED˝gY­V\ž|9/œżr‘:ډzlWÔĽŽpńüÄŕvťUŮA5RJ…ŤŐŠ-[śĐ*ˆŠ lٲ%ŹÚ !kiiázœ7VVED˝gI!œ.Ť!‘´´´Głłł…xžĂH—0k IDATzŰĐŘuAéaȎVCBÍ0˘Óé`łŮ¨D ůůů°Ůl“nŐP ­Š„Żąë‚Ű3FŁŮŮŮHKK{4ÔżršqăF¨Ť!˘.q…‹gqš\hkď‡ Ń#ééé°Űít"F@™™™°ŰíţˇĽšśś6¸\.n×ă9׊™¨÷Ž˘˘"ܸqŁ Ôď)ˆHÇvE­u‰+‰3¸>SFÍϓáIÎ0bąX°e˖˜z*ŽÖÄĹĹa˖-Łş˛R Ď9噅…1ńd^Qď]VŤ5ŹcźĄŽˆXőzýMƒHŹlË<žQŔí™2.—KČćeS%G)**Baaě<9Të {ßBŚĆëőr[ŃĎHŔă!ŕ–¨Ű3VŤz˝ţ&k(ßRINN^ýŔLéyJui+\<—+Ť!Áx†‘ââbdggsQƒěěě@WV !SĂsná9穙¨÷°x`ZrrňęPž7¤pqăƍD\ ÄíŰއÓsqűŹŰ¸\+ÖVC‚ń #˘>‡‰LÎd2Q‰ĎU‘Űg݆‡Ósš\KÍD˝‡Y­ÖŸĆ;iaŒ™ź^o’ˆA¤k '&š˜­ĺ¸D)r+wx„BĆë!DÂsŽá9÷ŠŐ™ŽŻ„|öŒŐj•žĆkšě{CYąŠÚÖ]Ô~ýᘟ†‡8}*pťÝš?) #„7 !ˇ´ľľqë+ňPznLĺń^ÔîÝ:Ů÷NDć͛÷Řý÷ß/d[÷3g•‚ě6ŃIYP!źPk­Ç9P­D˝—Ý˙ýlŢźyMö}“‘îîî{DܖÄ-ň ÇăœÎÓˇśśj˛‹j$(ŒHQ[KK ˇ'źć@5ő^fľZŃÝÝ}Ďdß7aaŒ™şťťSDܖŠí8‹n÷ŐÂńpz.ˇ#ťą^2 #dŞ(„LŒ×œŁŸ‘ ů˘ŐîÔ ¸*bąXĐÝݝ2YČd+"a=wA-D­4ŻB-ŸĎ§™'ěʁ …ÉŐ××ĂçóqšV,­ŠxO ĘŽfLDfϞ˝RÔçcÔś‹—ĂÁłH•VC&Ga„„ŠBHčxÍ=ąP´*ę=-??łgĎ^9Ń÷LDVˆ¸Ň5ĐŁůnŞ<—"y>BËZ[[qĺĘĽ‡ATîʕ+BBÄsîŃúöLc×ańΞ={ĹDß3aimm]*b}ˆˆGÂĹk)˛ŠŠ)f˜…Ť¨¨ˆš•‘I™L&ˆú€Đhóz˝hjjâr­X؞ľN¤ľľuéDß3naŒY1ëCD=ꪏD3–$.är-Z ĹbĄśí$dŮŮŮŁ”GĆĆkZ’¸Y‰f.×R+‘ëD¤L1–‰VD,™™™0|G˘îĽ…ŠWň÷x<8ţ<—kiYzz:=ŔŽ„­°°pÔ™Éhçϟ‡Çăár-­ŻŠˆxo3ČĚĚ&(X7ˆĚ›7ďýĽŢ6|ŰwUéaČęáš|öBi5dr:6›MéaAŮl6čt:Ľ‡Ązźć"^sŁZ}ŰwUȧńZ,̛7oÜçΌDDmd&âZ8˛Í\pG&śnÝşˆrFb[\\Ö­[§ô0"b6›1wî\Y_ƒçƒđ´ž=#â=n˛ĆfcƘQÔFfZŻáľôčvťšŠjńSŸŐj•– ™˛ĚĚL!k풒’đÄO`ăƍxúé§eý÷z˝Üž?ŁőíďqAÍĆŹőoEDŘFf"C­Ű2999Řśm˛˛˛¸^W)éé龇QŸüü|aęEt:ňňň†ý<ët:<ńIJž.mτFÄ{ÜdÍĆ ""~ěčŃt}Čüř4nŰ2źŽĚƒŸœî˝÷ŢaŸ ’’’¸]_zh­şčt:á—Ó‰úŹ[ˇNő+‡999Řźy3 FŻ*˜ÍfäĺĺÉöÚźć¤ŰgÝŚéćfßö]˛ŸČDŤcQ UEÜ; ݆=MMM\;„fee ›`Íf3śmۆ‚‚noAAJJJd%VŤƒAö×!ąĹ`0¨v•Ůl6căƍX˝ző°¨ŽŽ´bż÷Ţ{e Sýýý܈֛›‰xŻ›¨`uĚ Ň×סLÄ ŇčmVz˛âŮȧS§Na÷îÝhnţď?//Kx0›Í°c6Ë[ˆf2™°|ůrY_ƒÄŽĺ˗ŤŞ)^RRVŻ^7ŽúŮŞŽŽĆîÝťqęÔ)üń0¸Z8Öj /ÔÜ,4"Ţë, úúú–őgc‘ÎÎN“Z“űDj;žTz˛Iœ‘Ŕ­‰ď  śľŢťw/8€ÎÎÎŔ׼‰kóćÍSÁߊS§"ëD¨#&‘›ZŢcyyyŘźy3rrr†}˝ĄĄo˝őN:X ihh@CC€ÁíŢ[Ż^sӒąHäôdr5ń^gľZŃŮŮiëĎFƘ€ŞR{¨Ît|ĽôdópúO¸\‡÷śĚHŇ$źœ sçÎĹƍńÄO„5‰ĺääŽÖŐՍZuቶdH4(˝E“••5ćÖéx&$ŐŐՁ–k‹”ďö Ÿ9SDź×I™BĘÁĆZ1˙%Q4t‰ˇTŽĺ)cŽh…MŽŐąœ:u ĺĺ娍ŤöuiĚ˲tŻ9xŘçóÉşb4é” ‰šüüü¨w­ďÀĎçÑ#GĆÜ^ ÖŮŮxčăŇĽ>:$"źć(^sŚZ‰vĎ ĘŚ‘6Vąˆ8!7¸gŽÜTą‚pk‚Űťwď¨ N*>š,,'''V>ýôÓ1?Ľń˘–ĺr;˘őžÓétX˝ző¸ŰŁŁ>0ŒçÜšskĘŐäŒ×ĹkÎT+ďyCŮbň‘yóćY¨*X: ŻnŞ­­­˛nˌ§šš{÷îő#GFՏŒW(—””X éěě”u5Äd2Qă2u™™™˛Żˆ˛ŽŽ.ä“0WŽ\Á˙řGüô§? t/ž¨xUŞ1‘›Űíć˛mĽĺ:‘FołpíěłłłQ__oŕ”ž6,ˆ¤ĽĽ Y¨*b—šP-á”vŐD€[ő# 0›Í\–fÇCŤ!DMx݊„KZůéO:îą\)°ČšJŒ×œĹkUŁÚŽ/Q‚ J#,FŁiiiĂ V‡‘7nÜ-â§D-oÍđXńx<đz˝Fáô5˜ŞÜ\ą>EíËÍÍU,ˆƒAc÷îÝČĘʂŮlť^šrÍÍÍQY ćőzáńx"nüŚĺ~""ŢűŹV+Ν;wwđ׆‘ţţţů"ވ|Ű'ŢŒP$ÎHŕŇ?D„Րh2™LtR†¨NZZL&“â?ŻÁϕQí™Űg݆Ä ččá4*őńŢg4Ńßß??řkĂNÍtww§ˆÖĚLŤ{żŠoĽ'6ľĄŐ˘VôŢŽ×ÜĽĺÓ3˘Ý- şťťS‚ż"Œ1€¨?˙ R".M…j‰ĎŢ&.…Za4ąhŃ"Ľ‡AȘ-Z$Ü,'^sŻšTDťJďo)sĂWDL¸7ב›ŚO̤Üń5ÔÖ?Di˘˝żIěĄ÷č-üú‰D>—Ş•h÷Ŕ ÷ˇIú‡aADŻ×ߌć€x¸Ô§Ý›ěüx>ϗ!ˇĐ$OԎޣĂń˜ĂxĚĽj%â=p(k˜¤ß "wß}÷XOăUľK˝âýGŞňˇxńb:˛KTĎ`0`ńâĹJC5xĚaRÁމxĘ&é÷ŕ!j‘†Ž JAźşĺъČ-4šQĐ{ő^s˜HHĂ!â=pd/‘@šqăĆÝ". vkđHdQĄ*w4šQĐ{őnADŁŤ"Ţ- nܸč%"ÜVL0ю-…ƒG7@ŸĎ'T#39-^źqqqJƒÄĹĹQâőzšŹjšĂŞč÷Â@iooĎmED‹ j$óă#o¸EŤ!ˇĐ¤NDCďŮ[řŹjˇ‰Ąh÷B‹Ĺ‚öööŔƒí†­ˆˆV#Ň ŘąĽpd%.ŒřDnĄIˆ†Ţłˇđ˜ËxĚŠj%Ú˝pd֘ˆŰĚLÄ˝ąP$ÎH€žC…ˇÇăá0ń™L&ږ!‰‹‹ ~lzLă1—ég$höäŒh÷‘Mͤ ŢůuĎO‡‚NĚđEŸ,‰¨č˝;ˆNÎLL´{áČŚfBŤŠx~:źR;‘AôŠ’ˆŠŢťƒxÍeZ]ý^XąŤŞVńz.B?—ëˆL§Óѓv‰°ŇŇŇ Ó锆âxÍeZ~ćŒh‚ťŤ‚ˆˆ]UĎt|Ľôd1Vä7Nę¨:ˆ>QŃŃ{x9ÇÜŞF"Ţ ƒťŤ >b–™EMâDtôć‡ćVušłgĎhçŚĂÁc“VDĽ§§+=B"BďáA<ć4­ÖˆbŢĽě1 t:ÝOŹVŤ˘ —čä&˛DĂçÝŁ-33Sé!zóŁĺšU´{˘Őj…N§ű @[3šE+"ôI’h˝—iNÓ2 "*ŁŐsîJ É›h˝—ůĄ9V}ŚŔ7 J$\gÚĎ*=YP~DëLČxč˝L˝D&#â=QĘÓ łłÓ$Z™őĄÓD;č˝LsšÖX­VtvvšÚšQĺŠË”‚fЧH˘ô^ć‡ćXőĄ ˘ATÔ5Č`nǑ1Ń{yÍmÚ$lŠířRé!ŁODkč=M&"ň=QŘ ˘UZmAm4i­Ą÷44ÇŞĎ4Ƙ  7šZđhAěóů8ŒDlô~&ZCďi>sľyWéýĚ3:cąX቎îҤM´‡ŢÓ4ˇiIPć0ŃÖ !„BCA„h}z$ZCďi˘U €Ŕ żßŻđP!˘)--ö+!„„Š1÷ӊ!„BCA„B!ŠĄ B!„ĹP!„Bˆbž§ôڊŠăQžÓé@?„¨¨?‡´"B!„ĹĐń]BȔŃń]BČTŃń]B!„(Ž‚!„BCA„B!ŠĄ B!„Ĺ{|wýéçqŚă+Ľ‡ÁÝť÷źĺ)wFt §Ó‰ššN#SQQ˛łł•!ÜÔ×ף˛˛Réa(*??VŤ5˘kœéř ëO?Ďg@*˛<ĺNź{ĎJcJhE„h’ÇăQz„pEďi˘UD!„˘˜iÜŕrš” á&==]é!(Ž>=­Ą÷4ÍmZ”9ÜÓü~ż 7šZ\ęm‹ř:ŽĂHÄFďg˘5ôžć3ˇń˜cIä¤÷łßďwÓ֌Ę\ęŁhŇ&ZCďi>hŽUaƒHnĘ]J¨MÚDkč=M&"ň=QŘ BĆg2™”‚*x˝^Ľ‡@ô^Ds›6QQ™3íg•‚fĐ'H˘ô^ć‡ćXő™IIIn§ÓŠđPOqqqJAqnˇ[é!½—iNÓ§Ó‰¤¤$70DŚOŸ.ÜşßňÔeJA]=\ŽCÇÜčS$Ńz/ó›ÓxÍąj#â=QĘ´5Ł2 ]ÍJA3Z[[•!\Đ{™šcՇ‚ˆFQQMŢD;č˝Lsš–MŸĎ÷™h5"Y‰fĽ‡ ›ĆŽ JA3ZZZ”!Ą÷0?Zž[Eť':Nř|žĎ€Ą ňÝwß ×á%qF‚ŇC =Lúô0ˆ>IŃŃ{x9MŤő!€˜÷D){Đ֌ Q b~č´˝‡ůĄšU¤ âţóŸ˙|SёLÁň”;•‚,x´ Ś‘A4‰ŃŃ{x9MŤíÝEźe7DşťťiuD%˝|ŞşéÜ=ŕóůĐÖŚÍɇh_[[|>ŸŇĂPŻšŒ×ÜJ"7”9܀ŕ[3óăӔ‚,¨—_ô‰’ˆŠŢťƒ¨‡ČÄDżVDŔĺr)7’)˜?KěůăáuΝ‚Č ŚŚ&Ľ‡@ȔĐ{wŻšLŤ=DDťe 70Dü~żŻ{Ÿ^Ŕ*áPt ô ›Cr7F#>ˇŰţţ~Ľ‡AHXúűűiEdšŹ{ Gł+"˘Ý ĽŹ!eicýĄ(˛ b›G‡óî´"r }˛$˘Ą÷ě-<ć2sŞZ‰v/™5A$55ľ^´­ĎM‡ŠÇ13 "ˇĐ¤NDCďŮ[xĚeZ>ş+Ú˝Đĺr!55ľ^ú˝ĐĹŞ˘u’ G#‡˝LNƒÁŔa4âkjj˘í"Œţţ~ "C t:]Ä×á1§Ş•č÷Â@™>}úŸE[ÄŰ U§cf´*r MěDô^˝…[ĄŞFîŠxtš\˜>}úŸĽß‚H[[Ű7˘Őˆ@VâBĽ‡ :9ĂMîDô^˝…NĚLLÄ{ ÇăA[[Ű7Ňf„ěŽ*úůéńt ôŕŰžŤ_‡:ŹŢŇÔԯ׍ô0™×ëĽ „ÇömßU͞˜ńÜUDDěŽ*Úůép\ę<ˆĐŠČp"n?’ŘBďŃářŞF>—Ş•ˆ÷ŔபŔˆ ˆ÷C°D°cKá¨íř2âkčt:¤Ľ‰÷F•‹hďo{č=zKZZ—BUsŠZ‰vŮĚ  "˘65››ŇC Żç"ĐŞČ-çϟWz„ŒéüůóÂÍÁrâ5wiů3˘ÝG63FßŐëő˘Ľqя-Mä˙oďýٚşó˝ßw´˜†€0­ {nUÔJ‡Î•j•82ęYu řăÔzt žgĐYϚϝN;ÓöOŰiĎô9ˇÁYë9ęYŤ†ąOľOŤ†ťnľÔ†R:x:tÂx휉ĄX„–  8şď°Ó~˜ߝý#Ÿ×Z, „˝żÓIö~ĺóëŰÔ{‘Éq¨Nd;v ożý6š››át:ŃÜ܌cǎĄŽŽŔ¨ŒlŰśmĘOg›6mBEE ø‹) (*BHĄŁ!óć͛ Áď-)Ââšĺ˝5¨Ř‰Ş€üî}n1.ő2ND8Žs=YČ­j8\XT| =O$--mÜíwżű?úLccŁ_FŇŇŇđđĂOxÎŞUŤ&|jÓh4!G,˘!<ÍÍÍđů|ţÇ|´qŰśmHKKtáŔn~ˆrŁ!€üî}ź[đŽÁ3a’jFFF‡/Î+җŠ˝Á¸ŔčÍ$dT$đ"ÖÚÚz×BÔĆĆFż¨ŹZľĘ˙÷ƒO<ńĄčG?Ž;†ĆĆFŚëv8čîVîöŕ„´éîî4’——ç˙€ŕt:qćĚ9rÍÍÍžWQQ!™úV×*V×N)"Ç{žÝnGFFFGđĎ'ˆČŹYłdY°š'Ă\Y¨°ĘqĆ"=!=/P*V­Z…]ťva׎]ă¤ĆçóĄŽŽœ2Â-ďż˙ž Ç%ˆť!ôk/0rćĚŁRĎ×mž§ř‚ÖŠŠ ŃëGX]Ť”\"Ç{žŰíĆŹYłž ţů=Á?čěěŹs8Ť‚.uË,D­N&óDôz=Ôj5†‡‡­ě[nŢźöß8N\ż~}Ň60FŽŤŤJ—Ë…öövdgg z‚¤˝˝]Đ4x`¤ąŽŽnBŰŽÓé„ÓéDAAÁ¸dZZ6mÚ„źźźq‘ËXĄVŤ™LTýęć׊Ž‘ă=Ďáp łłł.řç“mrç¨ŻŻÁ’Ř"G; ŠGEŽ_żî†%K–„ô7“楝N'Ž9‚3gÎ.!<6›-&ç!!_sľW>ŸoB*&ććfTWWűëśx víڅM›6Ĺ4]CѐАă=oĚ-&ä"'@ŤRCu"—.]0Z7]á›Á`@EEń \?Ţ~űm;v,ćĂÎÜn7ä(ŕ„<ŠŻŻtO™ŔZP˘Š>Ÿ8xđŕ„ÉÇ¨ŹŹ×'$Trťç8…+řwDDŽ3€<‹wBĺ\×ď™gѢEPŤŐLŽL`ÍG`nš'-- ŰśmĂŽ]ťĆÍ ŹsüťÝn§v^Bp< 2ƒÁŸęź~ýú´Ń`Śú08MČA„jľš™ˆ°şfJ9ŢëŚę˜&ˆ --Í%ÇÎ9N™ •[ƒh¸ĘäXBEEúűűý˝źź<˙KŁŃ`ŐŞU“^̛›qäČćÝ0‘B)Bh„~F.‚Ó-ĄÂ§GƒŁ)S}˜`ŤkSŰŔUĹî/Čó^gˇŰ‘––ćšěw“ŠHbbâEší ČsĘ\8œěˆě˘Œé™Ŕ ׏~ô#ŹZľ "$N§Ó?mUJ{_¸\.\¸pAěe ĺ… ‚G›N'|>Z[[Ł.4mllDuuő„ |z•uş†Őľ‰ŐľRŞČń^çp8˜˜8ižlRéěě|ňÉ'ž ç֋LŐ:(%(ECĐ)^"†3U =/;Ź`›–vAą‘ă˝nŹcćüdż›TD8ÚŰŰ\’0¤$$Ër[äPš6ÔÍl—a!Ł"SľüŐŐŐM:LIjř|>œ8qBěe ăĉ1ëóů|Ě#üPÁˇß~ýýýřä“O˜žƒŐ5髛_ăڐr‡ޛ8) Éb/#lÜbŇǔ"Čsüua†üL1Î]gcúůůůLŽ3ożýś˙˘ŰÚڊƒ˘ąą1fâhéęę˘.‚őőőčęę{LŕßĎŹ?P°ş&ąşFJ9Ţă\"tá8έŐj{嘞Y!ÐU8°Ę}ćää@§Ó19Ödř|>üîwż÷ JnŘívČ12HH‹öövY~¨ť,?Tčt:fťí*˝>DŽ÷8‡Ă­VŰËqܤ=ëSED Őj?•ă›GŽšłpŕ§Ź˛@č¨Čőë×%[*'Nœd- Sš/X]‹”>Mç=ÎnˇCŤŐ~:Őď§šŹ.HĘTt ŸôŒđů|°Z­b/ƒ)VŤU6éH1Ą´Lhܛ8 ’„ŰE](Ś+TŚŒŹĘq<9ćЁUč155 .dr,%ÓŐՅÚÚZą—AȌÚÚZĹԅÉ… ‘ššĘäXJOËČńŢćvť§-TŚŽăě€< V¨Hě%J뀓Ůp3ŠŠ„†Ăá@KK‹ŘË dBKK äQV× śŤŠOËČńŢĆ;ď“1]Dz˝ţ’ßLrĚĄ… ËáfB­* ›Í&Ë­ˆŘâršhBoˆčt:brźˇ9čőúKÓ=gZšqăFƒ#") ÉXœ’+ö2…ĺŔŠŠ„†^ŻdŹ5Ą,ć͛Çdűx€ĺľGéÇäĘr~ˆÝnǍ7Ś{ÎÝDäŹ\g)Č1—׆şńŁ7^aa!“FO(f IDATă(˝^“É$ŘDZB9¨Őj˜L&’‘`uíů ŤIŃCĚůŢÓęëëqăƍłÓ=gZŒ›É1—.ŹB‘˗/gr,%BB„ ÉČÝYž|94 “cĹCZFŽ÷´ť 2ă™VD8ŽsÉu°Yaú2heĆ ‡s]Mđ2Úa’˘"“CBD ÉČô°şćxo *>-ŁMH–m}ČŘ 3×tĎť[DDśƒÍ`˝^ů7×w:>dr˝^ěěl&ÇR $!D´ŒLNvv6ł˙&ŹŽRFŽ÷˛ť 2㚍ˆtvvžú裏86ˊ-r….Gݞ›oa4™KŹ ™Ëk Ëk T‘ë˝ěŁ>â:;;OÝíywö•Ó3ëe˜S –EŤ999Č̔ßÔ>֐„Ź!ů–ĚĚLfűĘÄC‘* Ď{™ĂáŔŔŔ€ €ýnĎ˝ŤˆpçŇétýrLϤ$$cEúRą—!8, ľâ˝V„$„ ’‘QX^câĄHuEúRŮśíętşţťŐ‡ĄED0sćĚór@ž•ĆárŽŤ‰éFxń:ŕŒ…„ ÓĐ3ărš˘Ú1ŢeD§Ó1ÝŕNéEŞ€|ďavť3gΜr™@B‘žžž3çϟżݲÄAŽE>árÔIľ"ŃŔJBŹV+jjjhźiiiAMM ŹV+ÉH„0­ ax͓2r˝‡?ţN__ߙPž’ˆ°{˝ŢrŒŠ,HĘTü”Ux§ŁŽY+oźEEXJżÉ™ÍfŁňDmm­l{WWÉH°Œ†xo â8HË,Nɕĺnťvť^ŻwB¨BŽă\rؒU,ögŕÖ ^§š°BBx>Ő ‹—ááa>|xÂv$#áĂňšňúŐZ 0úŕ%eäzď˛ŰíČČČčĽ>="‚™3gÖÉu#'š†śÂĺ$Ă~úüü|ĹwĐ)!<]]]°X,ü6؄Œhoo‡Ĺb™ö˙[’‘ĐČĚĚdşŻ Ëk”‘ë˝Ëfłać̙!‡ŹB‘îîîw[ZZŕvť#[™ˆÄKzćÚP7Ó7čƍ™KjÄBBx|>ŹV+äşoS Çńy ańx<8|řpXűk‘ŒLOvv6łš!Űkœ”‘ë=‹wŢB!d€ůóç7ĘľNDŽ!ŽpĄ¨Čôˆ%!<]]]8tč.\¸ńů a¸pá:ń˙Ż$#“CѐȐë=ËnˇcţüůáüMX"ŇŮŮůÎéÓ§e9î}ARf\ 7Ř~bĐëőŠŮ™Wl áńů|x˙ý÷QSSCŃ ŕńxPSSƒ÷ß˙ŽŠ˜é ™Čňĺ˙ţo‰—hȊôĽ˛LËŔéÓ§šÎÎÎwÂů›°D€MŽăŢů†şÂ…uTÄh4Ę~ҨT$$—Ë‹ĹBľ#"R__‹ĹÂlÉȡ¨ŐjŚ2ń ‘ë˝*`Ź{X5a‰ßĆkľZĂů3É Çyý‘Âň“CjjŞŹGżKQBąŰí¨ŽŽŚÎšŇŢގęęę°jAB…dd”ÂÂB¤ŚŚ2;^źDCůŢŤŹVkXmť<áFDŕóůޓkHJB2śd­{1Aˆ¨ˆ‡œI]BxÜnˇ*kww||ęƒîîn˙tT!;ă]Ft:EC"dKÖ:Yî-Œ~¨ňů|ď…űwa‹Č7ˇ´´Čv? šÎí֟ JKK™Ohä"!¸\.:tľľľT?ǃÚÚZ:t(f׎x–Ö׊xІČőĺršĐŇ҂7n÷oĂŽă:Ž_Žmźëő…¸7qŽŘˈ ŹŁ"999X¸p!łă ‰%$‡Ă‹ĹBB%ź€X,– ÓQcA<ĘČ… ™śëĆS4äŢÄš˛í–ąŮlünťażŃÂ`t7^šÖ‰ŔV™EBőo2ۃmœzáŞÜ%$@!Ą”Mčtww‹* Ä“Œ¨ŐjŚíşŢ[ƒq ‘ó˝Éjľ†źŰn0‰H__ßrNĎÄK0ašMjjޤ÷ĄQ’„âp8pčĐ!ÔÔÔŕʕ+b/G˛\šr5558tččHźČˆŃhdZ úúŐÚ¸‰†ň˝7ńi™žžž7"ůűˆD„ă8›Z­öÉ5=ł )?”iř+Ž:k™FE %9qUŠˆËĺ‰'P]]úúzJŰ`4ýR__ęęjœ8qB˛”.#ŮŮŮLťëźˇqÔ?;X˙P_(ŰŮ!6› jľÚÎ4Ő@"HNN~OÎéšöiGÂŔ­AźpéߘSjWăABqťÝ°Űí°X,xë­ˇĐŇŇWťüŁĽĽo˝ő, ěvť,öÁR˛Œ°ž&źpéßâb‡]9ߓŹV+’““Ăî–áQq\dƒRU*U)€ÓWŻ^eZ˜KV×íÁW7ż{1㽢ß2ÝüĎnˇKbWźIČt,Z´Č˙‹ZžŞŞŞq˙ Éđđ0._žě˙’3J{Í1MŮś \Ĺ#ő?cv<Šsoâ\4ż.ö2"Âĺr!77Ęb‘{zwaP$źđŸG˜Ďh4"3SÜP˘Ň.čŃrůňeŘl6źňĘ+¨ŠŠÁ… d]äÚÝݍ . ŚŚŻźň l6›ě%PVd$33“yÝëk•Ô‘ó˝(Ú´ EDŇÓÓßÉĘĘÚ"Ľ˘°p¸5ˆü÷{1ĺŐüýL ˘Ün7:$JZ€$$t4 rrr““˝^ϬƇuD¤˝˝]]]pš\pš\Qíý"äţVŤŐءoÓՓâIÇk̎'ߒíłüü|tttœěííÝé1î‰f}}}oôőőmqš\˛LĎđ“VYÎڐ:/\:‚őúBh˝čů.šłgĎ29^¨Čýk|>߄”†^݇^ŻGjj*rrrššłéšnˇ.— nˇ]]]qń˙C0|d$š×2ăľĚşKĆ{k/\ŠŻhˆœ'ŠňÝ2"ę–á‰JD8Žłi4ŸÍfӘÍćh%冒¸‘ąžüg—ü„Ů1 áršbÖVJ†ŠnţŠŠŠăžřŸ…ť sKK‹ż€Ôívű"žEŽ2˛páBć{PUńf\¨Ł÷ šÂ§e|>_T5׈𤌌ˇX,ŃF4ňR X‘žTěeĔםľh¸Ęô˜ĽĽĽ1)Ž$ >Ráp8`ˇŰaˇŰI-˜Ífó˙˝ĂáđG?ˆ‰Č­fD­V3ăŢ6pŻÇQť.ŹH_ŠźƒŘˈ‹Ĺ‚ÔÔÔăŃ'jéîî>ŘŢŢ.ŠáAá"眊Hyňls°ۡogzĚ`HB%#'Ůž};4 Óc˛ž&É9ß{ÚŰŰŃÝÝ}0ÚcE-"Ç9´ZmݜgŠlÍ*Ž›ýgxZœ8ę|—é1srrPTTÄô˜<$!D< )**b^xÔů.ZœL)uîMœ+ënŤŐ ­VŰÉŢ2ÁD-"0kÖŹ‘łˆ@ĺÂb/!ćTńż˜ĎQ1Ě§Ž’„ń„”e$;;›yŤîW7żFő˙‹é1ĺ€Üď9VŤłfÍúÇb""˝˝˝Ç=ODydа^˙łNš0pkPpčöíۙՋ„ńˆeD­V ’~}ňŻĹ]Ş6!ëő‰˝ŒˆąŮlđx<čí퍺>`$"ÇšrrrţCÎQ‘”„děɕoőr¤4ő^dž˘Ńh40™LQ‡$„ˆg¤&#&“‰y]ČQçťhę˝Čô˜r`On‰l[vŃhHNNÎpçbq<&".—ë_kkke]_n(‰ť¨ LŠFŻ×٤$rą# !éČHII ó4OźŚd´ ɲnŮuťÝ¨­­…ËĺúWVÇd&"ÇY5Í0EEäÇŔ­Aěý™7???ěŮIA"śŒ,_žůůůŸ{*öţNjq—’” Ńh4ĂÇYY“™ˆ€N§{SÎ3E€řŠ´8Qýś̏[ZZVń*IALD,ÉÎÎf>/\o]2€üŁ!ŔčěNÇôfÁTDř™"vťĺacJJB2ś2܋ENT_y“ů 3`´x5”ÍńâIBV­Z…]ťvÁ`ď0#9ĄŃh°jŐ*ą—ą–‘ĚĚLAŠSŰŽ˘ú ű=r@îŃťÝÎlvH LE„ă8GZZšKÎé(Ăô ĎŢĎ^„—q¸TŁŃÜuňj--MÂ'¤ŒlܸQ 9Ůń!Îu51?ŽPJmë"Uć"(Łh5^;hx^¸tDz‘üü|[oš TTT`ÓŚMЏiŠM]]_öřč“`)#<ňˆ âť–ÄcŤ. ŒhˆÍfă‹T˙Iˆă ""ÇšćϟßHQyÓ:ŕ¤^|đA’|>qđŕÁ ő#•••(((quňÇçóá“O>ń?.((PLĉ•Œ<řŕƒ Wő-O:^‹ËV]%DC, ćϟßČj’j0‚ˆtvv>[__—Ë%Ô)bB}š“s+/lÍ*Ć˝‰sĹ^†¨<éxMâŐpˆ F‡ńu!>Ÿo\t„ˆ§Ó9.ş¤¤¨ i¸*XZW.ܛ8[łäýúrťÝ8}ú4çőz.äyś‘‘‘š×ŠŔŤě{ ˘"dńj(ċ„ †qő E–Dô¤ĽĽ‹.)Ľ7ąe$ދSy”pϰX, h牠"Âqœ[­V[ä>S ӗaEúRą—!*­NQŚ"Ƌ„wS n;%"'pNKpáďĂ?Ź¸ÉśbĘČŢĎ^ŒëâTX‘ž…éËÄ^FÔX,¨Őj Çq‚Ś5„ŽˆŔăńjoo‡d䚼b/Atšz/âŽŘE¸âIB ƍ lç%"gŢźy&×^ż~}\;ŻŇ˘"€82ň ‡M˝cv>Š˘„{…Őj…ÇăÇă9$ôšŽă\sćĚ9)÷˘UČK1Č~ŻźÓQ‡ŁÎw?O-ŘńĺÄâ”\ě‘ůđ2°Űí‚0 &f"Âqœ=##ŁE ľ"€2Š‘X0pkú4Ž~%Ř9ňóóŁŢ—FŞŐjll¤v]Ö{ŠĆdďÎŤ„¨żŒruđ+<ţéÓqߌˣ”{BUU222Z„`LĚDzzzĚJ‰ŠŚ/ŁÂŐ1$fbŽzŽ çŕwęÍĚĚô<ą&0ât:)ˆŔvÜťĽšúűűÇÉHqqąŹŰy333Çí¨+sŐs° QYďÇHْľNíş|4¤§§ÇËóĆTDĄÂUäĽp|ĺËHž'Qđsń2’-řšbÁŞUŤĆmź6Uú€ˆŽP¤˘ššYűĐdggÇDB ůžD_ů˛":D˘A)Ş€8Ń Ć"(+*ďűĐđKÓh40™LXž|yĚÎ)?ü°˙qss3ľë2$°&”śÜŕQúyyy˛kç]ž|9L&SLk\´ Éq/#JŘO/ˆ "ÇŮ,XpE)Q‘­YĹq9qU ¤´´TÖ5?ü0ľë Hgg§˙űP¤Â`0ŒCŸĎ‡›7o ś>֔”” ´´T”sÇłŒŹH_*űýdxŞŞŞ°`Á‚+ąŽ†"ˆ\ťvmŸR˘"đjţţ¸Jш-!<ůůůŘťwŻěŠXƒ÷7ůä“O¨@•1­­­ăR-Űśm—ăIKKĂśm۰k×.ż:NTWW‹ŞHľZ˝{÷ Z” ń(#ڄdźš/˙ýd€oŁ!׎]Ű'ĆůEĽŐŠ,HĘĞ\ů~:ŠH^݇Ůl–UÝČŚM›üß÷÷÷Smˆ@źýöŰţď5 ***°mŰ6ŹZľ ŤV­BEEžxâ ×™:vě˜,Ä0;;fł9&ő Ąo2˛'ˇ ’”QŹ+Vm("(ŤV*îPülŠI_7RTT$öRîJpš€R2Âqýúuœ9sfÜĎňňňP\\Œâââq’ććfTWWËF ‹ŠŠb^ ń"#‹SrQšPƒ-ĹŹ áMD””ąíóTHUB1Ř˝{ˇ¤S5Áíşw›qADGssó¸ąîđŞę3gdQŤŐŘ˝{7ŒFŁŘK™’x%]ëŎ†Ŕ=bđGE>˛Űí’~c…J^Š•÷í@őoŠ˝ڰÁżŢÄ×Ă}ČMž—áĘ&’““łŮ ›Í†+WŽzŽpŃh4hnnöŞR4$68N8N¤ĽĽů[yoŢź)‹@.\ˆŇŇҘDAŽ~…šę9ˇĺó2ňř§żD뀲şÁ*ďŰĄÉâŁ!D‹†"FDeFE”–˘a!!Ţ[ƒxŹń)”5üŁ ăŕy4 śoߎ 6H*:âóůĐŘŘčßůUn7BšÓßßď—9ýˇWŤŐذaśoß i¸Š˛†ÄcOÁĹÔT%FF””’¤ Dčéé)­ŻŻ‡Őj{)ĚxőetѰ’ţS?>2………ءoŸäŚąR**™™™Řˇo cr>~[ƒhpâńOI22†6!YQ)ŤŐĘ׆ˆÓ÷€č"ÂqœkΜ9'•áS4r†ľ„đ ÜÄ#ő?ĂɎY,󎤌Śbßž}˛(d%ˆ@ŠŠŠ°oß>¤ŚŚĆä|';>Ä#ő?ˇw Éȡ()%ŒFCć̙s2V;ěN‡č"}}}?ooo‡Ĺb{)ĚŘc(‘í 3Ą$$'Żá¨ó݈.FŁ{÷î•U›/ŸdggcďŢ˝1­›;ę|O:^›ôw$#ŁƒË”°ł.ĹbA{{;úúú~.öZ‰ˆÇq.N÷ĎĎ?˙<çvťĹ^3˙9ŮĽhb!!pŕŔ¸\.ą—ĂŒőúB”{wE ái꽈Ç?ýe̊XŃڑíۡc÷îÝĐét1;/A˘Óé°{÷nlßž=fľ _”úK4ő^ ëďâMFĘ b˝>vr(4.— @rrňŽă$“~Œˆ@OOĎËZ­ś×lľĽ™9•÷ý˝¤[zŔžŃ ÜÓř Ť)â5D?w¤¨¨ˆŇ5DĚPŤŐ(**‚ŮlFNNNLĎýAW˙ôé(߫ʗ‘Ĺ)šxnI…ŘË`ŠŮl†VŤííééyYěľ")Ż×ťľśśV1ŁßąD[zĽ !<ˇą÷łEg4ąoß>,_ž<ćç&â‹ĺ˗cßž}˘ qŹţâMěýěĹq1‘ tQZŤ.0:źŹśś^ŻwŤŘk Fr"Â9SZT$/ŀç–üDěeŒCJHő•7ąďłcZ7ŒŚkJKKŠť†žŚ´´4Śi`ô}şďłQ}…ä+YFž[ňÉ­)ZĚfł$†—M†äDrÖŇҢ¨v^ؚUŒ-YëÄ^éJĎšŽŚ˜×đđÝ5ťwď–Ü04B~dffb÷îÝ1ď†ááëAÎ öT˘ŒlÉZ‡­YĹw˘Œ°X,hii‘Äđ˛É¤ˆ(ľ­Ŕť^DęÂĂ׍ÄjřY0999ءoJJJ¨ •N‡’’ěۡ/ću <';>ŒŞ$”$#‹SreŐé Rl× F’"ŒśóŞT*ˇŇR4přűϊV/" á¸5ˆ'݉’ŞáÉĎχŮl&!!B‚łŮŒüü|QÖŔ§bžtźu=H((AFFç…<+Ęš…Äl6CĽRšĽÔŽŒdE„ă8ˇÇăŮSSSٍÂUX”)Ę ^nČšŽ&<ňń˘¤jx…„R6D0™™™˘ 0šŠyäă'IĹL‡Üeäđ÷ŸĹ‚$e˝Żív;jjjŕńxöHŠ]7ɊpgSbá*Ś/‹é~4r–žkCÝx¤ţg˘tŐ’ŸŸ}űöa÷îÝTÔJ ;;ťwďĆž}űD`´+ć‘úŸáÚPˇ(痫ŒTޡ…éËbvžXP j{-Ó!i”[¸ • wŕ‡1–Ł ¤úʛŘT˙žşůľ¨ëČÉɁÉdÂŢ˝{Ší7Yž|9öîÝ “É$Z ĎW7żĆŚú'˜vĹDŠÜddKÖ:T.”÷&Ľ“!őŐ@$/"…ŤJš¸Ę#tńŞŇ$„§uŔ‰GęӍóŚBŻ×Ł´´O=őŠŠŠ¨ŽDÁčt:ኧžBiiŠ(]0Áuž‹Gę&š÷§dD‰CË€Ń ŞR/P Dň"nˇűéۡo÷(1E“2V %DńŞR%„gŕÖ ^¸t;>ýĽčŃĐh400›ÍxěąÇh/ąpáB<öŘc0›Í0Đh4b/ _Ýü;>ý%^¸t$&Šá"uá‹S•´ ŮlĆíۡ{Ün÷ÓbŻ%d!"088řwľľľ°Ů$ęŠˆI™8ž’íÄ]ĽKH M˝%áY´hśoߎĘĘJŠ’Č>úQYY‰íۡcѢEb/É wݘX#e9žňeŧ€ÍfCmm-˙N쵄ŠŠă8ą×2z˝ţő›7ošÚŰŰUąžL Ţé¨Ă/Ń×Âē„“—bŔŤˆ?Ťe2\.._žŒááaą—6Ď?˙ü„ŸUUUű7ź"ö¨Őj,Z´ůůů˘×}LFŰŔU<ůÇ×důž”Ň5é7ůfĹ -Fg†dggs‰‰‰ÖŽŽŽ=bŻ'Tî{áĐÝÝýjľúńŞŞ*‹WˇfŁmŔŐ'{Š˝ác _;˛ÇP‚ĘűvHjŸœœ˙ÍíňĺËţ/9J‰’ŕĺƒ˙’"Ţ[ƒ¨ţâMźîŹ{)ÁGF˘š6ń‘‘hŻMĺ†G)!Ŕč‚ááá၁{-á Ťˆ¨TŞR§?úč#Q6ŠO:^‹hšhźKH0) ÉxnI…dĆęO…\¤DI9ČĎɎ%[.b_Łśd­SÜäTťÝŽľk×@™ÔŰuƒ‘ˆŔwžó’““‡#ć›GŊGęÖđ.ąßŕRŚ0}ž[Z!ÉtM0]]]~)éîgÄTČ]D233ýâ!…n—ťŃ6p/üçÉׁ„‹XתĹ)šxŻčˇŸSʸÝnäççcppĐţÍ7ߏ{=á"ŤÔ OOOOٍ7şŞŞŞÔJLšv˙ IDATŃŔń•ŻŕńOŸIFHBڇ/fݚUŒç–üDRéš`ôz=ôz=ŒF#|>._ž —˗ˏÇ#öňd…N§ó§Ă-Z$‰N—PđŢÄ —ţ ďtԉ˝A#Mł8%ÇWJvÂyÔTUUĄťť{Řç󕉽–HeDˆÍľĄn<ňńÓVœ“„„GJB2Ę %ؓ["i!™ ˇŰí—’ŽŽŽ˜GL¤ÉĚ̄^Ż÷ˇܢĽŢ[ƒxýj-Ž:k‘†šąşvi’ńޚƒŠě䝒ᑭˆŁ)šÄÄÄ5úӟfČí˘*Óľż‘„D΂¤LTޡCňő#ÓáóůĐŐŐ哎Ž.AŁ&RNçĺää@Ż×Ë&â1';>DőoŠ6š],„ž†‰˝‘žĐ¸ÝnÜ˙ýwnŢźůąS2<˛•J•ŞVŤŻoßž]cľZĹ^Ž`L&#$!lP‚ÂËIWWÜnˇ˙{E°bˆˆZ­ö GjjŞ˙{9KG ń* u-Sş„€Éd‰'|ĂĂĂ󤟊ÝݐľˆßŚhNŸ>ŇRɏԏ˜Ŕ#$!ěQšLżEB𿡊Š"‹ksđżJ„düTńfT{eń ? u͇˙€'Ż‘„„@´{ÓÜşóWĹKˆĹbA}}=×+AB…DDxRSS_ńxčj{I„Äř Ť ű>{Ťëöŕő8™"áFFâABÜn7L&t:]ł’$P˜ˆ€ÇăŮzĺʕałŮ,öRĆąyófdggűˇˇˇă7ވ꘥ČI{Îu5aďg/b͇˙€ŁÎw)mÇxo â¨ó]Źůđ°÷łqŽ•ĄĘH¨đ4śL&#Ź%¤ššŮŮŮQŐî Ăá@QQwçΝĂ^Ż÷§bŻG+" Óéţ+77÷˙°Űí’Úw˘ĄĄaBZćWżúŐ¸ÔM$đoL’i—bŔ–ŹbʟW¨ŘWĽ‹ČW7żĆšëŁŃz?‰K Œ°–Ŕkňć͛ązőę ľ}bŕvťa4qőęŐżx<žż{=BĄhQŠT9ćňc=ŚŽŐř††´ľľĄ˝˝===ţŸ'%%M¨ ŒŠ$%%Ál6G-#CőáďAM‰ĄT)Q˘ˆ|H—źţ÷Şß0mѝěƒaRR6nÜ(ş˜L&źőÖ[Ă>ŸoÇq.Ń"0Š vő"===8räHT3B˛łła6›Ł~ᎃ'¤‡’Ň7JJťČÖcŰ'“@222üBk”^ˆâE/rçΝ'?ţřăBԋ ᥗ^‰”Í›7cÆ Q睎:źpéߨŁCâ¤$$c˝ţ!ŹH_†ÂŒe˛‹–ČUDžşů5šz.âBďEœëú=ľÚJmB2ž[ňćcŰŰŰŰńë_˙úŽĎËČČŔć͛QPPŔôüSáp8°f͚;3fĚxUi­ş“"ŠŠŠHIIyŕOúÓ Öő"gϞũS§ĆýlńâĹ(++›2Ő2444!zŇÓÓĂÔźŁRHĞź 3–ŠIú˛¨F]Çšˆˆ÷Ö šzGĹŁŠç"Ľ\d„ĐťčžńĆţa“Ŕ¨tLőĄrńâĹذa/^,ČZ€Ńşűďż˙ÎŔŔŔÝn÷ƒ‚HB舨TŞT­Vű_ßűŢ÷ŇYďGcąXĐÖÖćźxńbHeŽ ɈźáĹdńŘżR‹˜HUDřˆGۀ“ÄCĆ-!ŔčŔgžyĆ˙xńâĹX˝z5N:5Ľ`óć͂t؍F|ţůç˝^Ż÷o”2Âýn(ż{ ŽăÜ*•Şř… MfłY-äž4bä§"/ŀ÷ÖÄŢĎ^DŰŔUą—C„Ië€sÜM4%!…é˰Xg@aúýX4Wrrkžşů5Ž }ŚŢ?ĄÍăDSďEJľ(€Ĺ)š8üýgą )SĐóddd`őęŐţ¨H[[ đŇK/áěŮłx˙ý÷'LÁnnnFss3VŻ^72łŮŒ . ű|žâx‘ Ž""<*•Ęŕ(ËâŐŕĐŤ:– ÜÄăŸ>M2˘@R’‘—b@žÎ€Ĺ),HĘD^JnLŇ:ąŒˆxo ˘uŕ*Ž uŁmŔ‰VϨ¤‘t(Ĺ)š8žň¤Ä(5ŮÓӃ—^zÉ/IIIx饗””„žžœ:u ÍÍ͓ţ-Ť›€âÔrŽăŹH†ÄˆŁĂÎT*UŤâŐśśśqťţžˆĽĆ“Ž×p˛ăCą—AÄ^PR’ąXgŔ‚ÄL,HĘ}̨[‡ľˆ´ \ĹŔ­A\ęĆľ›ÝhóŒŠ Gü°%k^ÍßĎô˜CCCw˝×úO˝nkkĂoź1eş&š)Ů|q*ÇqG”:´l:âRD€ŃâUŽăž×ŢŢŽbQźzęÔ)œ={Ö˙˜U+.€q–΂.ÁQçťLŽEČ^T`EĆ2˙Ďyi dEúŇ ?ˆ\čýĎqyšđ˙žç"řEƒ Ę âš%LŮĐЀ†††ť^'ۂc˛A““ĽkVŻ^;wF´>ˇŰěělNĽR}/ĹŠÁÄ­ˆ¨TŞÔ´´´?~÷ťßÍa1yuhh‹e\'Lvv6ĘĘĘ"ްnoo÷ż‰ PQÁî JłFˆpqţč̄ŸM'"†ß$đŠ%ÁzF0~NH(ƒŁ"S5Śk˘•ŁŃˆ/żüŇŐßß˙@<Յ˘¸ÝwC…ă8wŮĺ˗},:\&›ŒĘď+cąXš1ŇĐЋł_˙ú×ţړććć -ÂѰ5ŤgŠJž=” eŁMHƙ˘ƒ‚J|óÍ7řć›oŚý›ŕ:śśśqő<¨¨¨ŔŻ~őŤˆ%-N˝|ů˛Żżżż,^%ˆcŽăĂĂĂ[SSĂ$ÇÍËH`×ĚâŋąsçÎťVU÷ôôŕěٳؿ?ŢxăqíŔzÇífW7ÔÜÜ ‹Ĺ‚gžygϞ4ߘ‘‘;wâľ×^Æ púôéqĎËÎΎɐ´ÂôeT7BsřzÂôewr˜Lˇ‹îdĄœŠ‰vzŞŰíĆŁ>zçöíŰîëëűoQLaˆŔqœ{pppō7ž4`%#ííí“Ÿß ČK/˝äOĺœ={vÂó…HÉLł¤LźWô[lÉZłsĄ\śd­Ă{Eżd\űd5!;wî×ý2ŒL%1ţŃ Ń´čßśé޸qăËÁÁÁńÜ!3q_ŹĚ؞4eƒƒƒŸĆDišÍ›7٧§gÜˆŕŒŒ ˙Xŕ@ÚÚÚ&´é ’™ŠWó÷cEú2źpéßhÓ<‚ ÂF›Œç–ü„ykn hhh@{{ű¸ÂÔď~÷ť°X,ţČ2/#…Ťíííă$$X66lŘŕ˙° FŁmmm7GFFâşMw*("2 Ç9FFFV^ž|ŮÇj?š;w";;{ŇĎĐĐĐ3UJf*śfăřʗ)UCDXŒîó˛ |ŰĂă%c˛fÁ‘‘ŔFśśś is~üB4˜L&\ž|Ů722˛2ŢŰt§‚Dd ĆfŒ]sڈşkB,V¤/ř˘ƒx5ż¤Çł‹MŕÄT’öˆ€×ëý)ż7 ‹]{•L^ŠÇWž‚7WžŒ{犽‚ˆ îMœ‹7WžŒă+_QÄL!1›Íţ˝chbŞ0ˆÄŘ>ĺŐŐŐTź…éËĐPü:~“oŚú‚mB2~“oFCńëT&“Éż‹.í#Ô5# ÇYU*jjjŽ TMlÍ*ĆzýC8ęŹĹëWkiţA0@›Œ=š%(7”P &DL&jjj œvс“lj'~ďp84vťŠŠŠb/KҤ$$Łrá”JHH" H@ÂÇívĂh4âňĺË>qç{MJ‡R31€ă8ÇđđđCmmm7F#ÜnˇŘK’ź4Ź{öŻ!ˆ0ŕ÷…iX÷:*î ^BÚÚÚn“„Ä‘Áqœcdddĺ—_~éş˙ţűď8ôú^HŢ[sĺ†GIHb üӍí C:‡÷ß˙/żüŇ522˛’$$vPj&†pçPŠTŒŒŒ\(**ú?ëëëUůůůb/K6,HĘÄsK*PyßßSʆ  Lt8qˇoßţŻÁÁÁÇQŘ:†PD$Ćpç\qĎ=÷œzŕ¨€5(eCŁP &zŹV+xŕÜsĎ=§HBā"""0öBߪŐj˙ľźź|ŸËĺBUU•Ř˒E­çş~ę+o⫛_‹˝,‚œ{ç˘ráŹ×?DňUUU8pŕfϞ}¨ˇˇ—f„ˆ‰ˆˆx˝ŢŸŞTŞ 8ęrš`ąX¨Ł&R’ą5Ť[łŠńNGNvÔáB,‚`ΊôĽŘ2öZ'"ÇívĂl6űŰs˝^ŻUä%Ĺ5$""3ÖŢë:qâÄ˙Gí˝ŃĂ Ië€Gľ8ŮńĄŘK"ˆ¨Ů’ľĺ†š‚Ę€ öÜżĺ8Î.öšâŞ‘Çه‡‡úňË/]ŮŮŮuÔDO^ŠŻćď‡că[¨źo'dÇ˝‰sQyß86ž…Wó÷“„0Ŕáp ;;›űňË/]cíšvą×DˆHŽăýýý¨TŞĎ׏Ys‡ŠXŮŕ/l-~‡ž˙,~¨/{I1-?ÔâĐ÷ŸEC1 ˛Äjľb͚5wT*ŐçýýýP{Žt ÔŒ„+b}/bu8°X,b/K1Ź×b˝ž׆şq˛ăCźÓQGĹ­„$¸7q.śfcKÖ:,HĘ{9ŠĂl6ŁşşłgĎ>âvťŠ(UbˆHžˆőđáLJÚfłQÝC$e˘ráT.܁s]Mř ë÷TKBˆÂ–ŹuřĄţ!ʧH ¸Ýn”––â… ĂöQQŞ4!‘(ü5ŸţyÝý÷ߟöîťďΠágěáŁ$Ď-ŠŔšŽßSÇ !8|ç ľŢ ‹ĂáŔŁ>zÇív÷ű|žbJĹH 36‰őof̘Qˇf͚<8Ăd2‰˝,EŘ|m¨纚p˛ŁmWĹ^Ą§äŽÉG!Ľ^b€ŐjĹOˆN§{Őív?-öšˆťC""Ün÷Ó*•Şé­ˇŢ:áp8ÔVŤ”ŞžÉ¤äƒŽßSú†˜”éKý5$ąĹápŔd2áʕ+ĂśťÝn›Řk"BƒDDFpgSŠT‹Ž^˝ZWTTd¨ŽŽVQŞ&vJÉŔ­Aœëú=.ô^ÄšŽ&Ú|/NŃ&$c˝ž+җQ͇ˆX­VTVVrœcő .ą×D„‰ˆĚ{ƒý ßâKŠqŹ)y@SďE|Đő{4ő\¤ŽÂYœ’‹ÂŒeřĄţ!Ś/{9qM`*föěه˝^/ľćĘ™2Öâ{–R5Ň 0}™˙Ś4pkpœ˜ĐŹysoâÜqâAQiœŠńz˝”Š‘)$"2†OŐ¸\Žwxŕ‚×^{ fłYěeĹ=)cáz~6ÄľĄn4ő^ąދ$&2€crIľŇĂbą`˙ţýĐétÍ>Ÿo+Ľbä ‰ˆĚ{>˜ššúĘţýűŸ˛Ůl hŇbAR&ś&eúwLĺ#&m'šz˙D…Ż"ł"}) ÓďÇb"‡PV__N÷ĎÔŁ HDÂXW͉?üá粳ł3jjjTĽĽĽb/‹˜„ŔˆI%vZœhő8Ń6ö/ɉ0ŹH_Š<‹S ČÓh#9ałŮ°{÷nîöíŰ=ÖťÝnPŚHDÄŘ´ű233˙߲˛˛ňÝťwS!ŤLČK™xSlpâÚĐ×ţČÉľĄŻ)­"÷&Îł¤šţHǂ¤š$2%° 533ÓÚÝÝý4 LYˆ(Œą7č•Jőî‰'ŽŸ?~Öď~÷ťFŁQěĽaÂËI`ä/(×nvăÚP7ZŽĆ] ą6!y)šX”‰‰™$ ÄnˇăÇ?ţńŻżţzŔă]]]TŞ@HDĘX!뼛7ož^ťv­ą˛˛UUUQ‚L뀡ŃęqÂ{kĐ/*d—îY‘žü˘ĄMHFžÎ€”„d’ …ăvťQUU…ęęjddd|<<<\FQĺB"˘`Ć޸kU*UéáÇOŘl6ľŐjEG” ƒžnžĹŔ­A´8ý/ô\÷űŚŢ?1Y /ÁŚß?ţyߎ5/Ĺ@ŢqŽÝn‡ÉdBww÷0€íß|ó EAŽŠă8ą×@ĕJ•š‘‘qş§§‡˘#AHŽ (ˆ˝§§‡˘ q‰HœĄRŠJŐjőqľZ­ŚÎ‚ ¤ß3<ĘăÇQ$Ž˜!öˆŘÂqœmxxx^bb˘ľŹŹ ĽĽĽpš\b/‹ ˆ8ÄĺrĄ´´eeeHLL´Ď# ‰?("ǨT*crrň˙ž9sfƁT4•• ˆXaąXđüóĎsˇoßîü;Žăěb݉ŠˆÄ1ÇŮoܸ1WĽRýf˙ţýČĎχÝn{YA(ťÝŽüü|ěßż*•ę77nܘKߐˆ“œűŐW_ľŹ]ť&“ n7ՈÁˇŰ “É„ľk×⍯žjK#Ú €D„ƒă8×7ß|“ Ěfłőgggs‹EěeĄ, ˛łł9›ÍÖ ě›ožÉ§ębÇŮ<Ň5ADKpĆăń¨•†D„˜Çqîŕt u×*|7Lp†ć‚“A"BLI@şfíůóç{sssQUUEő#AL ?”,77çϟď°–Ň0ÄÝ ö]"d222~988řüŹYłfQť/Ađí¸####ÉÉÉzzz^{M„< ˆ2===/ű|>=_?’““ŤŐ*ö˛‚ŤŐŠœœˆĎçӓ„á@"B„E`ýˆ×ë=Y^^ŁŃH­gŘívF”——ĂëőžՁB"BDÇqŽŢŢŢ­r/]şÔ˛víZ‚ˆxYťv-.]şÔ ˇˇˇw+Ձ‘B"BDE`A+ A(—I„ Q &ˆLŕ8ÎNBBĘcą‹˝6BˆL ’żüĺ/WHHBž Č_ţň—+ !‚D„Žăě‹@‚“E@:::‘€BA"BĘT)›Ś<„”°Ůl”‚!DD„ˆ Bňç?˙šąŹŹŒć„ŕ瀔••áĎţs#H@ˆC“U QPŠT9sćĚů}}}[t:Ěf3Ěf3RSSĹ^A(ˇŰ ‹Ĺ‹ĹÇƒ9sćœěëëű9uŔb@"BˆŠJĽĘŃétű†‡‡Íłf͚UVVŚŞŞŞBNNŽŘK#ĹáršPUU…Ó§Os####jľÚâńx‘€bB"BH•J•  TŤŐţŻ×›^TTłŮŒŇŇRą—F˛ÇfłÁbą žžZ­ś×ëőţ€Ś R€jDIŔqœ›ă8ëŔŔ@‚ęH, ířKaÂď„\˙100Áqœ•$„ !$‹JĽĘÉĚĚü<ĎŸĎ§Ţ˝{7L&ŒFŁŘK#ÉbˇŰaľZQSSF3ŹÓéŢěîîţ'JżR…D„*•Ę”––ö|Nvv6Ěf3L&ˇFŁVŤ‹íííHKKső÷÷ŕ8Î*öÚânˆ˛BĽRĺgff>ÁGIJJJ`2™¨–„ˆKl6ŹV+jkkŁ9Žsˆ˝6‚Bś¨T*SNNÎO].×˙ĽÓé`2™`2™ŸŸ/öŇB0ŹV+ŹV+<rrrţĂĺrý+E?šB"BČ•J•“žžţřČČČ˙íőzÓůÔMii)ľŠŔĺrů;_ÚŰŰĄŐj{g͚ő/˝˝˝ÇŠöƒ;$"„˘ŕS7nˇűńááaÍňĺËýŠ’BNđňaľZŃŇŇľZíKMM=NŠBiˆŠEĽR•Ι3gçŕŕŕ#$%„˜L>’““ßëëë{ƒă8Ú ‰P$$"D\@RBH’"Ţ!!â^JnßžýÇ“ś|ůrF*t%b_pjˇŰŃŇҝN×?sćĚó$DâTľgŘ)ęAw‡D„ "€“ůóçoöz˝+˝^o:!PL¨ĆD¸ÝîqâQ__Đjľ˝Z­öÓÎÎÎS ń ˆˆ !Œ‰IţěŮł7̞={uWW×ČÎÎöK EMäŸbq8p8hoočőúK7nÜh¸qăĆY‚ˆ‚•Je?ţüFMx9áĹ$''‡f™ˆ„Ëĺ‚ËĺšT:˘ç1*vQK …D„ b„JĽJ19šyóć˛ţţţţ÷EEEČĎĎGjj* c…ĂívĂápřÓ+––ćJLLźČKFĹĂ-ڂ "Ž !‘QŠTůr0*(Ĺ###ßíééÉâż|ůrżœ¤ŚŚúe…şvĆăp8ü’Á×t¸Ýnë,dddt̚5ëËÎÎÎ:Œ ‡‹fx„¸ˆ„D”TĆĚĚĚďŢž}ű{ĂĂĂ řhľÚ;ßűŢ÷fŠ /*J’^.xŃüŮçŸ~ÇëőÎŕŸŤŐj{Őjőľ™3g~ŢÝÝý%;7 AH‚!cĹąţ/^T §§gyđóyaáÍNVDËRdxiĆnˇOů8X0x222Z @4\ü„ü !… +ŔˇŇ‚Ůłggj4š‡řçÝž}[XŤKŇŇŇ\3gÎôđ}>ßďoܸŃ=öĐ5öd„b!!bR‚D&ZH$‚˜”˙{ELmA¨­€IENDŽB`‚rusty_paseto-0.7.1/assets/RustyPasetoV4LocalArchitecture.png000064400000000000000000001176531046102023000224510ustar 00000000000000‰PNG  IHDR""ő´Ł* pHYs  ŇÝ~ü IDATxœěÝ{t×}/úď&H|€2ś˜Šc=â1Z1-v˜XëZ‰(9>’˝,É˝ľÔso-ä´Iœ&ŠčÓ¸ő9M+Ę˝mdőܐŠ\G9r,ĘQ֑Z%-+‘[ӝH˘źzhЉt  D Îýƒ|ăą3{đűŹ•%‘";ćpć‹ßţí=L’$BČ\Œ1;;§Ăů$Iňq:!DGBôiNˆý˝¸¸¸Ňd2}Nţşh4j˛CĽĽĽ>ƒÁ”?‡Ăżš~ýúŕ̇ž™˙dŃ- "„(.dŘŘ+++WEŁŃ;Ŕď÷×ÍýzłŮ÷c°Z­p8\Ćëőzć}Ţăń,úń[o˝5 …r0GEEE †ˇßǍŔBa…Q!DŁcVN9hD"‘[BĄPšü5rŔˆ ‡VŤ•kP›dź^ďŹĎÍ ,fłůŞŃhü0.¨x$IňŞ3zBČR(ˆ˘˛™ŔaਊŠi_ĺ÷űWĘ˙^WWŤŐ §Ó z źČÁDţÓăń  §§'ö5äççżůňĺSź˜Ž˘P@!DEDÉƘ€Ӂべąą;â{3c!ĂétÂnˇĂnˇ/v8’ŸĎŸĎ '^Ż]]]ą/--őüöňĺËżÂt@ńJ’4.‰ÂBÂsb&t„BĄ{ä)›Í‡Ă‡ĂACeńĹëőÂëő˘żżŔôŮlţľN$Iň¨:XBtŠ‚!Ě4:Š‹‹,..Ţ000°¸:œNgěO˘}r0‘˙”ĂIUUŐůëׯŸš~ýúIL‡ŸŞ%D(ˆ’‚™ŕᏊŠŮ_íhllœ:ŹVŤŞă$|Č='r8‘§uâŞ&ŻđP0!$yDI€<ĘĘĘ6EŁŃ‚Á`iII‰t˙ý÷3Şvd§ř`rúôiixx˜Y,–!ƒÁđŤk׎BBA„E0Ú+++żF›ü~˙JłŮ<őŔä8ÎXՃ™J<~őŤ_M…BĄœŠŠŠ ĂŠÁÁÁW%IęT{Œ„hBfĚT=škjjžrůňĺ`zélss3äđAH˘äPŇŮŮ[B\SSsöňĺË/č¤j !Ó(ˆŹĆsď4™Lůýţ•%%%Җ-[˜ÓéDss3őx.:;;áńxpôčQixx˜UTT|‡qýúőh/’Í(ˆŹĂk.++{\îő¨ŤŤƒÓé„Ëĺ˘é’^Żđx<čééA\oɋ4…C˛ ’äđ122ňP$1ŐŐŐÁĺrĄšš™öđ Şňů|čěěDGGzzz`4ĂEEEż PB˛˘[>ˆh(”lDA„č cĚQYYůT x”ÂŮBĄÄjľţdppđyę)!zBA„1f///t||üOCĄPšÍfƒŰíŚđAtC%mmmčďď‡ŮlžšŸŸ˙ˇWŻ^ý ­ž!˘Ł B„ĹsŮíö?öů|hąXŕrš¨á”čžÜčÚŃс`0ťÝţo>Ÿď‡’$u¨=6BRAA„Ežz ƒ…Ăaăć͛cS/„dyęćŘąc0™L‹ĹňMÝŃP!B`ŒšJKK÷ Ů劗ËEű|‚é}J:::bS7ĽĽĽžĄĄĄg¨JBD@A„hcĚ^YYůrőŁĽĽ.—‹v8%d 8xđ`|•äżR/ Ń* "Dscʚšď_ž|šŞ„¤fn•df{ůďJ’äQ{l„ÄŁ B41fĐl6› …Ęc+_!é‘WÜtuuÁl6_ …B†éçÝÔ!DˆŞcv‹Ĺ˛+‰¸óóóóˇlŮÂZ[[iŮ-! đů|hmmĹŃŁGĽńńńqŁŃŘ ÷Ó´ Q˘ Ƙ˝ŹŹě׎]{ŘfłÁĺrÁívÓô !ĐÖֆŽŽô÷÷ŁŹŹěg׎]ű3 $D DHFÍí˙hmm…ËĺR{X„d­ŽŽ´śśR QMŽÚ ف1ć\ąb…ŔéŰnť­áčŃŁđů|BQ™Ëĺ‚ĎçĂŃŁGqŰmˇ58˝bĹ /cĚŠöŘHv  B@Ö­[Wwúôix<jB%Dcš››áńxpúôiŹ[ˇŽHH†P!Š`Œ9WŽ\ً9„ö!DۜNçź@˛rĺĘ^ $D)DWńO|âˇS!DLńäŸřÄí  QÂĹbS0@ŰB $„' "$-Œ1;Bôo‰@bWyhDpDHJcöňňň—źG„ěą@ yŻźźüe $$UDHRcVŤŐú€÷ĚfóĂííí@ÉBr ioo‡Ůl~Ŕ{VŤőš™Ç5’0 "$aß6™L’$}sďŢ˝´!$śÉŢ˝{!IŇ7M&Ó@EEšŐíŹJ–ĹsšÍć—CĄPůž={h+vBȂä­ăŸyćůáz_Ą]ZÉr(ˆE1Ćě~żżnóćÍhkkهŃB–ĺóůŕvťqěŘ1TTTôřýţfzŽ Y M͐yâű@nžůćşÓ§OŁłł“B!$!vť8}ú4nžůć:P˙Y2 cŹŮbąôÉ} ^Ż—Q !)q:đz˝ąţ‹ĹŇÇŁç;Y(ˆłö9ÚÜÜ\ÚßßĎÜnˇÚĂ"„č€ŰíF?knn.p”ö!ń(ˆ̝†éčč fTBWVŤóŚkÔQ‘,ĆsLÓ0„L™;]S\\ü1mŸÝ(ˆd!ƘľŞŞęGN755­čééĄiBHFšÝnôôô°ŚŚŚNWUUýˆšYł-ßÍ2ŒąfŁŃřŁŃh’ŽâĂąg}Ž$ŻkKjUѢ@ €ÖÖVěۡżßżE’¤€Úă"ĘŁ ’cÍ&“épeeĽąŁŁƒú@˛œ$I˜˜ˆĆ>ž˜˜œőďăăŃšß(--š÷šÖÖÖYĆŤýůŚ”ĆwsÁM¸ĽđŚŘÇk-ľ(É-ŹąÔ˘$݈‚ŒŽy<¸\. FÂáđvގč_ŽÚ ʑŤ œ;wî¤*H˜œŒbjJÂäd’$!•N˜8´ęŁąńQ\卫ż[ňëď.˙`}ů§aÎ+ÂZK-n)¸ ˇV*:N˘ š™ľľľŐ¸oßžŁ+VŹ ęˆÎQŃ)𤠠 ˙ôéÓTёÉÉ(˘ŃŠ™?ĽŘßłľş)•…˚’[qKa%֖Ôâć›°ÖRK•X­V´ľľĄššO<ńÄ}FŁń cŒzGtŠ‚ˆÎ0ĆŹ•••ŕŤŰˇoG[[UA8ĆÇŁ˜ššŠU7Hb.ż‡‹Ăďá_ÎÍúüš’[ąÖR‹5%ľXkŠĹúň;T!YŠÓéÄ;3ăvťM88ř_¨:˘/Dt„1ć(**ú—ąąąŠŁG‚VĈcr2:óż)LLD…™F•P€_Ć>'‡“ťËď Ę‰†ČĄ577ŁĽĽĹUTT´‰1öEI’źjđAAD'fv(üÖ]w݅ÎÎNŞ‚h˜$IŸŒU:(thƒN~öÁt81çamÉ­X_ţiÜ]qUMTÖÜÜ,oż˘ŤŤëmŤŐú߁ŔÓj‹¤‚ˆŕcv‹Ĺňr0Źßťw/hc2í‰F§011 4˝"†ĐÄ޸úťéޓw§?wwů§đ…ŞĎa}ĹT1QŐj…ÇăA[[žţőŻËjľ6ƒÁŻH’äS{l$uD&/ËľŰíƎŽ8ľ‡D0]ńˆD&(xčP,˜`şb˛žü|Ąj=֗ßAŤt2ČívĂétÂĺrŐ_ştŠ—1FË|FADPfłů‡vmŰśR5`||2öżÉɅ÷á úšÁżœ‹5ÂŽ)š_ŹúžP˝žŞ%ŕp8ŕńxŕvťř%6üňkřQß1ľ‡˘Yhii€v #ʢ ˘ęęę˙  }÷îÝB0>>Ű„!Ę MŒŕűç˙ N}úGŃŃсݝw@űĚ5(€‚ˆĚfóţ\ˇźÉÉ(††FŒĐ> „¨ŕŁąńŘŻż˙ţ}šŽY€üŔꁁ Ď”AA„łÂ—äĘhĹI’„áá1\ťvvB%DţuŕM×,ÂĺrĹ6>+,,|Iíńč Ž _{”BČŇFG#đűCô,B4FžŽy¨ëOpa¸OíáhŠFĆĆĆĽ0ÂN(„,o||׎]Çőëaę!DĂ.ż‡M]Oaß%şßĆŁ0˘ "PYš< Œ`r2Şöp! Ú÷îKT™ƒÂDŇD!diňr\š†!DLzގŒŽŽâ•W^Iű8Fř˘ ’ĽBˆßďçv,ľDŁSĄĺ¸„脪#gΜÁɓ'ńâ‹/Ś}, #üPI‘R!äĹ_ÄłĎ>+t ÓjBtHäęˆßďlj'L #ÚAA$J†3gÎ`ttÝÝÝ܎›)““Q\ťvvE%Dçö˝ű’pĎ­9qâFGGcŸ9sýýýi—ÂHú(ˆ$Ié"űýďĎíؙ WA¨•ěđĆŐßáĄ×žÂż œS{(ËňűýłŽŻP__›ÍĆĺřFŇCA$ fłů‡™!đţűďs;ž’⍠„ěšÁŽ˙>ţňüľ‡˛¤š Ş………Řşu+×׈#´kr(ˆ$¨şşú?+ącęB!—’ĄŇFG#˘%š„dťöžWńPןhrŞćâŋóŚş7n܈ŠŠ +=›&qDŔsÉώQ:„ĆţŽŐ0"I††Fhc2BHĚĹá÷đP—öŚjNž<9ë㊊ <řŕƒŠ˝žFţžÚ›˜\ľ u3'RűîÝť!6lŔŠ+beÄ÷ßEEEčîîĆŋŤV­Â}÷ݧHšOD$2AKr ! MNOŐ|ľöËřŢş'ŐÎŹk§ěńÇ_đkűűűqôčQTTT,ú5‰rš\đz˝Řˇo_;c ’$u¤u@Ł ˛Ƙ@{KK ×§čžü.űp¸áż)FäÇdČ***pć̙yŤg€ékčƍZÎë÷űÓZ­hľZńꍯć466ŢVVVö?|%ĺƒé őˆĚ0›Í?źvíÚĂ]]]Ěáŕ77ˇĄ4GP_źxqÖńx—GFÂF(„BÓňá˙đü?Š4ąúýţy×Xżß?/„ȍŤĎ>űlB!ääɓxöŮgÓn`u8čęęb׎]{˜v_˝‚Ś÷ ‘wMĺB`˖-ł>^虉đűý8p`ö6ʟʈ’$!ĽmÚ !q9ü{l?ű4Î˙7×ă.ˇ äܲ\UůâŋřŤżúŤŘ”Ő4‡#śű*mx6-ëƒcĚ  }Ϟ=\7,“Ůlśy‰;ŮŐ.ýýýxöŮgg•ę!IE4:…ĄĄD"i‹Bu}ržý6Žô˙+ˇcÖ××/ÚŔżaĂ|ç;ßI(€ŒŽŽâ•W^A[[ŰŹpłfÍlܸ1íqş\.ěŮłÚgîAY-Ť{Dfö ů_ۡoGkkŤbŻłqăĆYó–~ż'OžL¨$¸Pƒ•ÍfăňŔ&yšŠ!„¨áztßzg$HřOś/r9Ś\)–+sW.§ťť{޲ޚ˝$ň˘tV-śśśÂçóáđáĂ˙‹1öšlŢđ,kƒHÜ2]Ż]SűűűŹRČĺŔř’މ'°aÆE“ůčč(^|ńĹyk˛ŮlpťÝi5ÇđđXÚÇ!„t=ýÎóDŽăÉOňy"ŽFV­Z•pĺŘď÷ăĹ_œ×O"WYäkőÜé™tÂH[[ź^Ż)ۗőfmąX,§˘Ń¨×2]yuL}}=śnÝ:/}oذaÖsäůĆĹŞ"………xđÁŃßßkPݰaśnݚöj™ë×Ă´K*ä搓Ăyy7.ƒÁŢĚíOîynÖÇŽâĂąOg}Ă˙[ŔđĈjťoíxîҏp!؇śĎţ—ă%ÓCwňäIœ8qbŢj›š{ŠĚ !gΜÁŠ+R~ˆžźŹ×fłŮ,Ë)wĽt Áee1›Í?dŒ}ćľ×^ă˛L7~‰Ž6Zśe˖Yi{šŞˆÍfĂwžóźňĘ+ÜvúŁMʲc yy0Ɛ›kˆ… ůcĽÝ]ţŠ%?Ć'çĎĹá÷0<1 -ƒ}ą 2<12˙ˆŽź:ŕôoŔž?äF–Óßߏţçž×äúŕƒbăƍłŽÍ 5ŞĘťc§Ăjľ˘ŤŤ‹Ýwß}Ÿ1›Í? …BœÖIJ­?@~šn{{;—ćÔW^yeÉ]úüńY›˜Íýú­[ˇ*úHj™$I´UťNɁ#7׀ÜÜ 9Č͝ J“{Ť”ěą’…&Fpař=|8:ˆ‹Ă}¸죀˘S›njÄž?ü3ĹĎᅞ‚îvťç=Rcą˛ŘîŘŠčččŔWżúUřjś=­7Ť*"Œ1‡ÉdÚżsçNn+d6n܈˘˘˘ye=`zÎą­­mÖtÍÜĆŐ'N žž>­r–C;Ľęc ůůšČÍ5 ?߀œœœ´§PDaÎ+ÂÝĺŸšŠŹ|>öůĆ>ƇŁăÜŐwp1؇sWKáDpÇ?îţ Ř{ן*z~Ëýńa¤ťť{VÉDŚWŇx˝^źđ űłíi˝YSaŒYÍfóÜyçĺ<Ÿ!#óűý8qâĢ{„Äw]Ď=ązđ/´2FlӁ#šš9ČËËŐ\čČdE$}ŒsţßââpÎůK=(‚zhĹ}Ř{ן*>•8ˇ2"_“3Bâ9NźőÖ[WCĄĐdKójÖŤŐúfIIÉgŢyç^ۡ/DŢŮoîj™Ü5wËágŸ}–{U„Bˆxäŕ‘—7ýg&ŚWŇĄŐ 2WhbçŽţo\ý-Á¨FÖŹY3oŇ!>ýéOO ż˛˘y5+‚ˆŐj}njj꯽öZďSsńâEœˆ0Úĺ՜šŹîînźňĘ+óž9ł?˙ó?ç˛[*í˘mCŒĆ<˜LyY˝˘$QƒČ\‡ßĂĎ>8…8‡G—˙’q˙¸ćťpŢr§âa¤­­M•JČ\qÍŤ[$IęĚŘ Ť@×ÍŞŒ1ťÉd:źmŰ6UB0˝N}}=Μ9ƒ'N,Hz{{Ó"’$at”–çjMnŽ&SŒĆ<áŞŮ`MÉ­řîş?Âw×ý>ű˙rĺ~öÁ)šÂшďŐîĝ%k0::Ł1Oą)ËŞһjŐŞŒ†`şyŐăńŕ§?ýéaĆŘjI’|@éş"bąXţăÖ[oýŻMËŇ%obśĐ žS3´TW˛!|čĽ"˛ %ę{¨bž÷‰]ČÍ5 ´´HѲÔí2ľŐBź@ §Ó‰÷Ţ{ďƒÁ?Čč‹g>ŻŽ˜Ţ´ @mGG‡&BpcˇÔgŸ}vÖłbx÷‡0ĆPZZ$|Ů_DC (++FYY1 ş !Ůŕ悛đŐÚ/ăxăóřEăßăkľ›qKaĽÚĂĘ™ !ýýýKnVśaƌ‡`złłŽŽ¨š§é’.+"j÷…$Ęď÷Łťť[ąœ*#™#÷|yj%Łô^YĚżœ‹ő”ed*„Čâ+"ń=!gΜI{÷Ôté˝_DwAdŚ/¤wŰśmƙ$™Ő(Œ(Ç`ČAAA> ň5żĚV)ŮDdĄ‰źüÁ/ŃţŢ1jrĺčśÂUřÇľßCŠŃœ‘";sć Μ9“ńĆÔD¸\.üô§?„ĂaÝő‹č.ˆX­Ö7ív{˝VúB´€Â_ŮZýXHś‘xT%áC­"Ő\nô‹ř|žn˝í/˘ŤÉkŤŐú\0Ź×R_ˆPĎHúc(,4˘źÜ ‹ĽB™ç Uëą˙ł”Pܕ IDATßřŚákľ›Q’W¤ö„SeŹP5„Đdnô‹ƒÁzŤŐúÜňß!ÝƘ# ~kďŢ˝ČÔŚe"Ą0’ƒ!EE&TT˜Q\l˘ĆS˛Ź› nÂw×ýÎ|ţGŘ}űcÔܚ bC!ţű'˙‹Ş!Dëöî݋`0ř-Ƙnntş˜šaŒY‹ŠŠŢ˝ëŽťV(ń-‘$)­_PšŚIĚt1*žy’čhj&1?űŕ—Ř÷îKÔG˛ˆbC!ţqíwąś¤6í’î5RN§ožůćďGFF>ЇçŃčbCłĘĘĘżŤčěÔ]3ń,áđ8FGÇÓúE•+#F–íÄçóĹţ‡100űŘét&uŹř7UUU0™Ląív{Š#ÓĂ+?‡W~žÉ"x†ĄĄćëúw¸łł6›­˘˛˛ňď|Míń¤Kř 2łT÷ŤGŐu_HüśíCC#F8ˆ"‡Œš ‰,űýÉ‘ŽŽŽeżĆh4˘ŞŞ Ŕp2÷O=Ą@2ß÷jwr !““ŃŘľRŻżĎVŤd[ślů*cěUїô DcVŁŃř“íŰˇŁššYíá(fr2ŠP(<ëc #|č-€ČUŒŘß jˆD"čďď€ŘŸńF*UUU°Z­ążÇWWDDdÚ÷jwbsőý\Cˆ, #7× Űž¸ććf´´´ŕđáĂ?aŒU‹ˇÔňÝgžyFá%§˛˛ŤWŻĆęŐŤc ˛Zvqř=üĺďŕÜŐߪ=n>S˛˙tÇUŽUšš”•§üšZŕp8022âůýď/܍pyJćȑ#ş !ĂĂcI‡ŞŒĚǃŮlŇ|ˆ”Čý#űŢ} ?ę;ŚöpRr[á*üu˙7——ŤZľ ‡Ç‘“ĂP\ŹíişTČS4ű÷ďnPAdfă2Ł^wqœŢ°,˝›Rś‡‘Ü\JJ 49 ăóůŕőz)|hĚBĄÄáphjs^žťîđđĘ&|ăí˝¸0ܧöVl(Ä߯űśę!D6:AnnŽć+ĽŠhmmE{{ťŃjľ ľŃ™0A„1愎7.‹ß„‡Çą˛-Œ0ĆPTdDaĄQíĄĚŕőzáőzŠŮTńĄÄbąŔápŔáph暳ŚäVo|í}Żbßť˙ŹůéšbC!:žĹm•ˇh"„Ȇ‡ÇtšÇȜÎ~,I’Gí1%B˜ RTTô?›šštšq™źL—§l #ůůš0› 4ľ<ݎˇ^Ż—.]R{($EÁ`]]]čęęÂíˇß‡ĂĄ™Š›ŻÖ~_Ź^oź˝WłÍŹrqT˙ŚBˆLŻËz›››ąyófœ:uę¸Iíń$Bˆ bľZŸ3 zܸ,ŃeşŠĐ{ŃZ$ăÜšsTýĐĄK—.áŇĽKą*ÉúőëU_|sÁMx鞿Öluäo×ţ™fCˆ|l˝.ëmkkC]]]…Őj}.<­öx–Łů ÂłřÖŢ˝{55gËK*+d’Ą×0’›k€ĹR¨‰w38wî\l˙ ˘_ńU’şş:Ź_ż^őĽŔrudçż}_3˝#{×~MśĎj6„Čä)q‹ĽPą×PƒÝnÇ3Ď<ĂžţőŻ‹1ś_’$ŸÚcZŠćƒHEEEçÍ7ß ˇŰ­öP¸ #™PüuôFŠŠL(*Rż âóůŕńxćm?N˛ƒÜKbłŮŕt:U}ŁtsÁM8Ţř<ö˝űö]zIľqÓ!äËľ÷i>„Č"‘ ŒŒ„QT¤Ż•4nˇř裏:8ÔĎR4Dfö Š;räˆÚCán||##™[9Ą‡0b0äŔb)T˝ÁĚëőâÜšsšŰpŒ¨ŁżżDee%ÖŻ_‡C˝kţîO>†/V};˙ýűŞ<ˇĆ]ű¸P!D62A^^.ňó5}KLZ[[îż˙ţ:­ď-˘~]{Œ1ŤĹbůQKK‹îö ‰F§ Žfüu{€^2ä0’é0`4楏ŹXŐâőzŃÖֆcǎQ!ó âŘąchkkƒ×ëUmkJnĹ/î{_ŹZŸŃ×Ýi{˛v›p!D Ž"Ęřë*Éét˘ĽĽ‹ĺGŒ1m,ýZ€fƒˆĹbyZ’$ŤT•jNM„ha„1†’’X,…Ş5”ĹjB%Ë ƒŞs^ööťřÇ×Q’W¤řëí´=ŒoŢá6„ČŻŻĆDĽľľľA’$ŤĹbŃlÓŞ&ƒcĚ żőĚ3Ď0­ŹßçEéćÔDˆFäç稾ńĎçĂţýű)€”ȁd˙ţýđů|ŞŒáᕟÇOîykKj{•÷ŕ‚‡Ďýœ´Âjľâ™gžaÁ`đ[3‹?4G“A¤˘˘˘łŽŽNw ŞáđxĘۡóŚő0b4ćŠ2LŻ‚éččŔÁƒi †¤mppDGG2ţúkJnĹOîůkEŚj6Vރ˙÷łßFŽBˆLK×i^Ün7ęęęPQQĄÉ>ÍƘÓď÷×émJfr2ŠP(Źö0fŃj)*2Š2ĐŮى^xVÂîúűűń / łł@f"OŐěžý1nÇÔc‘…Ba͍)]mmmđűýu3ť”kŠć‚ˆŮl~yóćÍşjP•$ ĂĂcŞő…,EKa„1‹ĽP•Ľšű÷ď§˝@ˆâzzz°˙~x<žŒżöîO>†>űÝ´űFî*]‹çďú–.C íkvŞœN'6oŢ łŮü˛Úc™KSA¤˘˘âŰĄP¨\oՐ‘‘ˆ&ŮdZ#r?ˆŃ˜—ňRáóůĐÖÖ†ŽŽ.zɘH$‚ŽŽ.´ľľeźä UëÓꚍt-^şçŻ‘›“ú-‡Ůäd4Ł[,dB[[BĄPyEEšŐK<ÍƘudddϞ={tľƒj$2‘öu3AÍ0’ŸŸ›ń~@ €Ă‡ăŕÁƒÔˆJT qđŕA>|8ŁÓ5rßČúň;’úžľ%ľYBdŁŁ‘Œl:™)vť{öěÁČČČ--çŐLąX,OççççëŠAU.ď‰B0b2ĺĂjÍěłΝ;‡ýű÷Óéˆf\şt ű÷ďÇšsç2öšćź"źtĎ_ă++›úúľ%ľxĺ޿͚"ÓۍŰíF~~~ž––ój"ˆčušn  Ţ~!ŠĘd).6Ą¤¤ ĺ×I–źćäɓ4 C4'‰ŕäɓ_]óßn|oݓK~Bň ŠOŠB€éqúŮ_D‹Ëy5DĘĘĘ~`łŮtľ\wt4‚‰‰Iľ‡‘’L„‘’’‚Œ>5×ăńĐj"yuM&›YżZűeüăë ţ[6‡ŮÄĤSě‰rťÝ°Ůl(++űÚc4Dcök׎=ÜÚÚŞöP¸ŃC““Ra„1†˛˛âŒmR°˙~tuueäőἍŤ ű÷ďĎXďČĂ+?_4ţýŹ5kKjqäŢżÉę"Óú˘ƒdľśśâÚľkkĄ*˘zЍ¨člll„ËĺR{(ÜčeN‘wÉôsjä^ڔŒ$˘ĄĄĽĽĽjc–ÁÁÁŒöŽL7ą>‡’ź˘X)0¤^šÔKÄëů[ŽËĺBccŁ&69SőQƒ3ŤÔéŠ22˘Żpx>ľwjJ‚Á |ö ‡Ăčě쎾şşÓ=3CCCRyDŮŁşşMMM¸÷Ţ{ńú믣ťťá°66 ”{G|>š››a2)ű¸ú5%ˇâ膿C•Š‚BČÓŐî0ŠŠ”ýdJkkŤüt^§$IľĆĄjЍ¨h[ˇnn6/ÓÔĚBx…ƒAů•1>Ÿ‡Śľ´´ XˇnÝźL__Ξ=‹žž>•F—=ššŚWŽ˜L&Ô××ăěŮł*hžK—.Ą­­ ۡoW|‹ƒ[‹nNëűőBd##yŞ> œ§Ó‰ĆĆFœ?ž €C­q¨DôX ŃSŮn.aDiG˜^“É„ŚŚ&Ô××/ú5ľľľ¨­­ĹńăÇŃÝݝÁŃe—ľkעśöĆć^§NRq4K‹D"8xđ 5űNĎ!D6<<†˛˛bľ‡Á…Ş"Ş˝UCô6%ł­†‘p8ŒĂ‡ ł"ŚĄĄ÷Ţ{ďŹ Č•+Wpţüy@MM ÖŽ]űˇM›6áňĺ˸rĺJĆǚ äj0]…şpႊŁILWW|>śoߎřTM2˛!„úš˘ŃBUD• ˘ˇjH4:…ŃQ}=­q1Z #ňŢ "LŘL&<ňČ#łŢ}_¸p§Nš×R]]'žx"v“yä‘GđüóĎgtźŮ`nƒęńăÇUMrúűűŃÖÖ—Ë…ŞŞ*ľ‡“5!D6::“)?#}oJSť*˘ĘÁ[nšeż–K‹ÉŇË*™DńXMÃ×ëĹ /ź DŠŽŽĆîÝťc!$ăĐĄC8räȂMŠWŽ\Á‘#Gb—––ÎŞ’ô™L&Ü{ケĎž=+\ƒp$Á /źŻ×Ťę8˛-„úZE#WEnšĺ–ýjź~ƃcĚůá‡ŢŽ—jH8<.ěĆeéP;ŒtvvâŘącŞźv* f•Đ_ýőe›PçNÔÔÔ(6žlÔÔÔű™„Ăaźţúë*(uǎCg§:Ť0ł1„Č&&&ëŁŢÚڊ?üđö™‹ŒĘxЍ¨hÓK5D’$„BÚXâ§5ÂH8FGGzzz2öš<Ě őőő ÍíLJ•ęęjEƖjkkg5 żţúëšYŽ›Şžžtttdô˙G6‡Y(V˝:̃\Ѝ¨hËôkg4ˆ0Ü~ż_7˝!z9ӑÉ0"÷ƒˆŇ”:WüjŒŇŇŇ%WĚČD›*ECCCěďWŽ\ŃärÝTô÷÷gěY5BŚéé ikk+ü~]ŚŤ" "zކŒë§$—Žht SSĘ9„ˆźKęĐĐĐŹ0ŇÔÔ´ěNžń˙NĄ„úúza–ëŚbpp0#adjJB4:Ľčkˆ"Çř¸řSôjUE2DôV š~] 8]ňŽŠJvŽ{˝^aVĆ,§ťť{V ˆ_:şřUÚŘ,}&“iV5ä… şüď‰DĐŃŃĄhŤÁŁ™ŐsZ —{‚U‘Œ‘šššďëĽ2:ŞŻ‡ĽCégÇx˝^;vL!˜îq‰Ÿ˜ť™Vꆆ†Řż]šrEˆý-´Žžž~V•IoՐx‘HǎS4ŒäćPZZ´üfÉɨ.žĐ+WEjjjžŸŠ×ĚHaŒŮ/_žÜŕvť3ńrŠ’$I—۸§˘¤¤@Ńňć›o ľ2&QÝÝݳޅ/Tihhˆ}>ăç?˙yĆƧWĽĽĽł–ë.´‹;v ožůŚbÇĎÍ5 ¤¤@ąă‹dd$˘‹žAˇŰË—/7dęÉź "eee?°ŮlhnnÎÄË)ŠT§›`2ĺ+úwÜq‡&6jRB|U¤şş:Ö¸Z[[‹;vĚ !GŽĄ]U9˜ť\7[śÍŻŞŞÂwÜĄčk˜Lů(.—ŃtéĽqľšš6› eee?ČÄë)Dcök׎=Ź‡ŢÉÉ(5¨bú˘SX˜úS9e45łk$o}}}łn„MMMxňÉ'ącǎŘtĚĐĐ~üă벇!Ójkkgőۜ:uJř庉¨ŞŞ‚Ëĺ‚ѨüďkaĄQń7'"‡Çu1ußÚڊk׎=œ‰ŞˆâAÄbąě˛Ůlpš\Jż”âôtӕŸŸ›Ń2ŹžĂČŮłgc7C“É4kŸłgĎâŔT ádîód˛Ą’É"+))@~žŞu×=Ü+\., ,Ë.Ľ_KŃ ÂłF"ˇBČřřdVî /7׋Ľ0㯍×02444o7Ďîîn<˙üóYóŽ=ęëëç…<˝S#„Č,–BE{ÇD011Š‹ĺźnˇ‘HÄÍł*ů:JWDšóóóóőФ é㙊bŒĄ¤¤@ľĽzz #sŸoRZZš ”™b2™fUCć6 둚!P˙ZĄz¸g¸ÝnäçççP´ÁSŃ b6›°eËfľ*ŚgýĆ=JݐI„^ĂHüŇÚÚÚE—ó’äĹoĽ?wé´ŠBd´’fzŁGŃ{ ­V+ślŮÂĚfł˘MŤŠƘ3 •‹Ţ¤Ş—.čt™`4ćŠ= ú #s7ŐÚ´i“ŠŁŃŇŇŇYՐóçĎëşÚ¤•"3óPT”Ý+iô°Ę˛ľľĄP¨\É Î "ňfvť]Š—ČˆŃQ}Ź OŐôĹD6™ĂČÜçĐÄďţIRS[[;ŤĎŚžž;věXv[}i-„ČŠŠŒšyŁI’„ßäĚnˇ+žÁ™"AD/˜MŸDb—ÖŇĄdyőÍ7ßLkˇT˝…‘+WŽĚZÉqď˝÷&ôt^˛¸îîněۡoÖ×ÚÚZ<őÔShhhĐÍ_!$‰(śé™ŚuŐ4::.ü›YĽ78S$ˆTVVţ…60Ëćjˆ’ g^ŻżřĹ/Ň~~ŒŢÂHüJ™šM–$5ápǏǁćífť{÷€ŹeźBHGG~ń‹_(˛|ś7ŻęĄ*"opVYYůJ_‘  ŁjˆŘĚf“"ďbägÇ7ž¨KadZ8žľœwî˛S’ş+WŽŕĐĄC8räHŹOÄd2aÓŚMxňÉ'…lćBä'ő*őlšÜ\Ěf}T RĄ‡ŞˆËĺB0|L‰cs"Œ1W86ŠžwH6WCŒĆ—‡ŃŮŮšdĐ 0rC__N:5ŻŃ’đ544„ăǏăĐĄCčëëĂ믿ŽůÝl•!ń_ÓŮŮÉýżcL•Ý™ľBô{‹ËĺÂĐА1ćŕy\ŽA¤˛˛ň)›Í§ÓÉó°%I’đ'KŞŠŠ”é 9|ř0—ý: #7Ä?‡†(ŤŻŻ‡Ňüfg™ !˛ÁÁA>|8ĺ×ZLnŽ!k÷ťWÄétĘMŤOń<.× ˘‡&Őlí ™ž8đ߃ łłýýý =…BćËt‘ő÷÷Łłł3ĺ×\LQ‘1+—ôęĽW„wÓ*ˇ ˘‡&Ől])ŁTšÔëő˘§§'éďŁ0BČ j…YOO"+i”˜č+h\.÷ŚUnAÄnˇ˙ńć͛…nRÍÖjHQ‘‘{ŮŔŔ@l™nŞßOa„d;ľCˆěŘącܛW †Ííڜ ˘WEŹV+6oŢ ťÝţÇźŽÉĺîĂłű|ž?¤jˆxňósQXČ÷b‡ŃŃёöq(ŒlŚ•"ëččŕޡTXhD~~.×cŠ@UŸĎ÷‡źvZĺDĘËËľX,B契L}b¤‚1ł™˙RÝǧâQ!ŮHk!D>žÍŤfsöíş*I"‘ ľ‡‘˛ććfX,”——?Ęăx\‚ČřřřŸŠ\ •ű†(1%ăńx’jNM…’M´Bdýýýđx<\™­S4˘ßs\.ĆÇÇ˙”ǹҞ 1ĆĄP¨\ä  ˝ŃL*rs ܧd|>şşş¸SFa„d-‡YWW|>×cfß*šht á°¸í.— ĄP¨œÇž"iyď‡ƒëţ&56&n‰,UźwO ‡ĂŠ”măQ!z&B‘>|˜{żˆROúÖ2‘ď=‡ƒŰž"i‘@ đ¨Č{‡LNF111Šö02J‰wËíœĘ …ĺY­VŘív88N8Î”úżš››cßďp8`ˇŰ…^U§$‘BˆüZź÷Q˘JŤu“˜œŒŞ=Œ”šÝn´űDX: šŒąfGß{ď=ŘíötǢŠáá1ĄËcÉbŒĄ˘ÂĚľ9ěÜšs8yň$ˇă%B´ ˇUUUĄŞŞ*<ŹV+,KRÇhmmőg˘‚Á |>˛úç ęšüŕƒbýúő܎'IüţPV-0™ň…­ů|>Üzë­°E’¤”“iZëŚĘĘĘ_šrĽ°!D’¤Ź !`6›¸†@ Ŕ˝y-re$ ¸\Ɇ0b2™`ˇŰaˇŰQUU›ÍŚęx, ,Ëźqô÷÷c``>Ÿ>ŸO÷Ű܋B€éćôŐŤWsŤtMŻä3axXűä%ç~]Νݎşş:|đÁP'ˆŒŒŒ<$r“ŞČ›Ę¤"??&S>×cň\Ş›, #KłŰíX˝z5ěv;*++ŐNBl6l6îžűnÓĎ;ńů|čííĺŢ Š6ŃCˆüú‡ĆŽ]ť¸ÓdĘG8<ńńě™2űü—Ë…§Ÿ~úĄtŽ‘ňԌŚeŽ^ eŐj™˛˛bŽ˝!GąU2ÉĐÝ—ŐŤWÇţ—ΏDĽ:5“ŠH$‚ŢŢŢŘ˙Dڎsśąą‘ëĂN''ُví:ˇăiÁƒňrłÚĂH é™”+"˘OËD"YBLŚ|Ž!d``@! ĘHŚĂ‡ZŒF#ęęęPWW't(Ń[Ś—ôŽ^˝š[xnŽaŚ2’SçŃč"‘ yj%i<ŚgR^5#ú´L8,dÉóŽ<8q‚ëńҕmŤiŹV+œN'Ün7śmۆşş:]‡šäP˛mŰ6¸Ýn8N!Väč1„Čx_Dí›H•Č÷$—Ë…‘‘‘”§gR "ŒąćH$buKw9}f‹ÂB#÷U2źwOĺ!ˆÝnÇöíŰą{÷n466&˝ĘE, ą{÷nlßž]łUZ=‡`şŃřÜšs܎ÇËŞĺź"W雛›‰DL3-IK)ˆ”••=^WW§Ů_řĺdKš˜ž{,,ä× ŞÖ*™Dé5Œ8ěÚľ ---¸ýöŰŐŽfÝ~űíhiiÁŽ]ť4ľÉ˘ŢCˆĚăń p;^aa>÷ÇPh™¨÷&yzŚŹŹěńTž?ĽŸp4}€ŚeÄPTġrâÄ ŐVÉ$JOaÄápŔívcóćÍÂŹ|тĘĘJlŢźnˇ[ő@’-!˜'Ď)ĆXV=‡Fä{“ËĺB4} •ďM:ˆ0ĆÁ`°TÔi‘Ë_É2r¸.×őů|¸téˇă)Iô0@hú%u‹EŐ@’M!DvéŇ%ŽK­MŚěЊˆÜ6ĐÜ܌`0XšĘłg’ţéďyZ&ɞľéźßIđŢŇYi"†ťÝŽ]ťvQáL$ťvíĘŘľ+CˆŒ÷ľ"›Ş"˘ŢŁäé™âââÉ~oŇAÄd2=Äs˝x&eÓNŞźŤ!Á`Űń2E”0bľZáršĐŇŇBS0 ŞŹŹDKK \.—˘Ťl˛9„Ó[řóě%ËŚŞH8<.ě÷N§&“)éŐ3IýdcvżßżRÔţQK^Šŕů"pí†Ď4­‡§Ó‰ÝťwŤžíz6ąŮlŘ˝{7×M¸dŮBdçΝăÚ¸š]U1ďU.— ~ż%c̞Ě÷%1›KJJ$ľ›żR%ň#—“ĄD5Dë ŞËŃbąŰípťÝhllär<’źĆĆF¸ÝnnÓ5BnˆD"TI‘¨÷*‡Ă’’ @RM¤IýTkjjž˛eË!w˜‰F§01!ćÜ[˛xžs@OOˇăŠI+aÄd2aăƍhiiĄ> °X,hiiÁƍa2Ľžń…ůzzz¸ţɖŞČÄĤ°‹*ślŮÂjjjž’Ě÷$D._žÜ jˆ¨ĽŽdńކhmŐtŠFŞŞŞ°k׎ŘC݈vÜ}÷ÝŘľkWĘ?W ! ăy ÉŚŞˆ¨÷,§Ó‰Ë—/7$ó= ˙DĺÓD]ś+ňúědđ|Çŕóů4šƒjşÔ #N§;wî¤*ˆ†Y,ěÜš3ŠŢ !Këďď纜7[Ş"˘ŢłäŒJvuĺ IDATĚ.Ť ‘ĘĘĘ/×ŐŐ ń<‡š˘Ń)LNFՆ⨒¸L†“É—ËE˝ ill„ËĺZvކBHb¨*’źÉɨÓ3VŤuuu¨ŹŹür˘ß“đO36‰Z ľÄ•,ž!ÄëőbppŰń´(a¤ŞŞ nˇ›VÄČfłÁív/ůłĽ’˜ÁÁAx˝^nÇăy­Ó2Qď]ÍÍ͈FŁM‰~}BAD^ś+jˆ¨%ŽdL? ŠďJ™l dq8ŘšsgV=WoŒF#vîÜ9oWV !ÉăyM),Ěϊ'óŠzďr:I-ăM´"â4›ÍS"‘l™–)(ŕ÷‹éőz…Üź,UJ„‘ććflŢź™×‰Ę6oŢ›űŚ’š`0Č­*ÂCAţŤ"˘NĎ8N˜Íć)ÎDž>7‘/*++Ű´aĂ!'ĺD-m%‹ç/eśTCâÉa$ŒFŽ\š"ě#Čâęęę`ąXP]]M!$E‡Ű3 ň1:*öţF‰ˆD&PX(^UőČ9sćĚ&Ë}mBá"> b5wßţdyܚˇ˛­We„Bˆ~Ůív !iŕY1r`4ćq9––‰zs: ?wŮťcĚ KE "’$eĹ&f&ż_F‘ˇrçG!d!ŮBd<Ż1<Ż}Z511)äłgœN§ü4^űr_›ČŰh§¨ŰşgĂ´ Ďw>ŸO÷+eAa„đF!ä†ÁÁAnűŠđŹk™ˆ÷˛¸íޝË}í˛?Áššš­÷ßżíÉăăŮѤĘK6ö†,†Âá…BČ|<Ż5ŮĐ´*ę˝ěţűďg555[—űşeƒH(şGÄi@Ě™,^ż„şÜE5FHş(„,ŹżżŸŰ“l"˘Ţ˜N'BĄĐ=Ë}ݒA„1f…Bĺ"Nˌ‹9Ż– Ł1Ű’Ýlď Y …’* !KăuÍaŒéžiU’$Œ‹×ďčp8 …Ę—ëYŽ"âÔs´BÄZ˛x5j…ĂaÝ=#â=.ncł{={k-ěFf"ŚĹdhuZŚžžO=őÖŽ]Ëő¸jŠŞŞ‚¨{číillŚ_Äd2ĄĄĄaÖďłÉdÂ#<˘čëŇôLbDźÇ-ˇąŮ˘ADÄw‚’$ 9–(ƒ!‡Ű´ Ż%sŔô;§{ď˝wÖ;¨ŇŇRnǗ_#SU“É$|9hĎöíŰ5_9ŹŻŻÇ“O>‰ŚŚŚy˙V[[‹††Ĺ^›×5‰çuR‹˘Ń)!÷YŞauÁŸ–¨Ş"ŽąNݤßŰŰËu‡Đľk×ÎşŔÖÖÖ⊧žBSSˇ oSSvďŢ­č…Pćt:aąX’],‹fŤĚľľľŘąc6mÚ4ëMÄĐĐN:ۊýŢ{ďU,LE"naDďUďuK5Ź.DĆĆĆî1ˆč}7Už›˜ńtöěY8p}}}ł>ßĐĐŔ%<ÔÖÖĆÂNmmmZÇZŽÝnÇÝwß­čkěu÷ÝwkjSźŇŇRlÚ´ ;vě˜÷ťuęÔ)8pgϞĹĎţsÓŐ…Ş%źĐćf‰ń^çp8066vÇB˙ś`˛k5š/EÄ&žD1Ƹuƒó"ŔôśÖ‡‘#G044űź|ázňÉ'SńžłgĎŚ=֥Ў˜DiZ9Çđä“O˘žž~Öç/\¸€çŸgϞUB.\¸€ .˜žžá=ő*ăumĘÍ5p{2š‰xŻs:˛/ôoó‚cĚ@SŠ=Q"6ń$JŤÓ2sÉąřr.TWWcǎxä‘G’şˆŐ××ǖvwwĎŤşđDS2$Ԟ˘Yťví‚S§‹˝™:u*öwĽŚHiz&1"ŢëäL!gŒx UDěńß$ KUÉČĎ×n5d!gϞž}űĐÝÝ=ëóňE°ĄĄaŮšćř2p8V´bľZi• ɘĆĆÌďZ˝Ř›p8ŒăǏ/8˝ohh(öĐÇuë–|tHZx]Łx]3ľJ´{^\ڰĎýˇ…‚ˆCÄ ˛h?”dĺĺĺr9NŚ‚păwčĐĄy8šůtnY8^}}},Źźţúë žKăE+ĺr’=2uΙL&lÚ´iŃéŃĄĄĄyosţüůŘ1•ÚäŒ×5Š×5SŤDźçÍd‹ĺ+"555Mb6ŞęwŮ.ŻÝT–YL__:„ăǏĎëYŹQŽ´´4V R´bˇŰiă2’q6›MńĘłÜ0>7đ÷őőĹÂGuuuJý[\Ć8W$ÁŔŔ@ÚÇŃű.Ť"ŢófVÎĚëvžÇÇÇW‰ů ;ńŇa˘ňóĹŤ†,¤ťťçϟG}}ýŹe€ľľľ¨­­Eww7Ξ=‹ĄĄ!lÚ´)ö}ńsÓJظqŁ˘Ç'd17nÄţýűšˇśśvŢR\`:Ô?~}}}(--”†††„úŻjjj¸u!˝˝˝\6€ËĎϲrďyVŤăăăŤć~~ŢŰlżßżRÄ3"6ď$*/OŹţĽČ˝ˆuáËä͔6mÚ{‡Ö××7ďëxr8¨ŹŹTěř„,Ľ˛˛ź+Đ ó6 ‡Ă8uęžţůXŕˆŻ4&˛YYuuőŹ“•œ*ĺ7=ŁßŠˆˆ÷<§Ó żßżrîçgQWĚč5ńĘxTDÂá09Œ†ĄĄ!9r‡Š5ŔÓÓ5ńedĽŤ!"†n˘/źĎÁîîîY+Öşťťąoßž§7ă{ݚšš #ľľľxâ‰'b÷őő)DgýHŻj˛V‰vď[lĺĚܟ’5ţ‹EĄçmÝy­‡×B5d!}}}8pŕęëëç-'ěîîžRxs8´\—¨ÎbąŔápp{֊\ýXťv-N:ľäďP8Ƒ#GbľkjjBmm-.\¸€ĄĄ!”––Ć6Œ§ô~>Ŕô5+Ýj‘ź˙’h7ěDEŁSBőÁÄe‹YýsƒˆłŽŽ.ăáJŻ'Ŕ/Ńóz˛ĽRâűGšššbS%Q5„h…Óéäú Ęîîî„WÂ\šr?˙ůĎńĽ/})ś{ńRÍŤr‰Ň|>—i+=÷‰LNF…Ű/ĽŽŽ===Nůsłîr•••B6ŞŠ¸Ë\˘rsů<źIëA¸Ń?ráÂÔÖÖr)Í.†Ş!DKxWE’%W@žôĽ/-ş,W,JV)ăńşfńş†jŃřxEEj"9VŤ•••łVg‘h4z§ˆď§Śô;5Ăc-| @0ä0šĚHf_ƒT­_ż^Ńă’Źőë׍D€é qŕŔŹ]ťľľľąf×+WŽ ŻŻ/#UxÁ`@ íßôꟈˆ÷>§Ó‰óçĎß˙šY?ĄH$r‹ˆ˝öˆ0Ƹě"B5$“ěv;­”!šSYY ťÝŽúďküseÔĆczĆ`Čc ’$q•vˆxďłZ­ˆD"ˇÄnÖ]. •‹ś™™^çţ€ěéÉ4ކ­˘ss6^×.=Żžíčp8 …Ęă? "Œ1;€Œ?˙ ]"&ÂDńę†ćąKĄ^X­VÜ~űíjƒÝ~űíÂ]ƒ•ÄëÚ%Ňʒd‰v”Ďo9sł+"vÜ7×Qšhi0<Ú¤ľýCÔ&ÚůM˛Ł7đŰODżAD´{`Üům—˙2+ˆ˜Ífą˘€hTó~˛œ>ϗ!7ĐEžhŁłń¸†ń¸–j•ˆ÷Ŕ™Źa—?žDîźóNá~Z˘•ĽEŞü­^˝š–ěÍłX,X˝zľÚĂĐ ×0šaUDźÎd ťüqěN'ę"˘•ĽĹë Tš.îDtŽŢŔëŚ×çΈxœť—H,ˆDŁŃ;E, ęqI@ŞJ ‹;Ť7PĂęŇDź:DŁŃŘ^"ÂMĹÄ1 &ŠÇn€ápX¨Ě”´zőjFľ‡AHBŒF#…‘Á`KĂŞžwXý^űÉřýţ:Ń*"SSâ%ÁDńčĄjČ tQ'˘Ąsö×2×T­í^čp8ŕ÷űcś›ő“­GDô¸eD "7ĐEˆ†ÎŮx\Ëô:5ˆw/œ›5rq73qn,Œ1.ށ@€ĂhÄgˇŰiZ†Çh4Ć?6=Ťń¸–ńşŽj‘h÷š›šÉ; Ţúu×O'‚VĚđEď,‰¨čܝF+g–&Ú˝pîŚfBOš‰¸~:źR;‘iôŽ’ˆŠÎÝiźŽez­ˆˆ~/ŒUDDÜUUŻxÍeF".Ç™Éd˘'íaUVVÂd2Š= Őńş–éšOD4ńťŤĆ‚ˆˆťŞNLLŞ=E é§vÚQu˝Ł$˘ŁsxŸVőYń^żťŞpá#čy™YŚŃEœˆŽÎa~čÚŞM9P\\,\íZ´.ádđ˜Ç¤ŠČ´ŞŞ*ľ‡@HZčžĆ㚦×@Ě{˘œ=rŔd2}ÎétŞ: dMLˆľn:4ÉÍfS{„¤…Îa~ô|mížčt:a2™>ĐԌnQE„ŢIý s™ŽizFADcôœŘ3.ŢD/č\懮ąÚ“ŃhÔ˘ö@’%b—p"rrh^DŰ)˜ĹĐšĚďšĆëŤ5"Ţĺě‘CCCvŃzDČŇhZm@ôƒÎeşŚéÓéÄĐА ŠÍÉËËU{şAď"‰^ĐšĚ]cľ‡‚ˆQS×4‹E¸GBDçň4şśé“°Ad|\ŹĽJ$łč$Ń:§ÉRDž' DôJŻ[g]´‰ŢĐ9Í]cľ'‡1fč$× [‡Ăa#ĎDočœćsmŁmŢľA>ŸcöŘCg‡ŠC"<ŃŇ]şhýĄsšŽmz—9ě !„˘ "D—čÝ#Ń:§‰^ ť ş´´Hí!h–ÓémPGˆžÔŐŐĄŽŽNía///7ÝDËĄľ„*"„BQ B!„¨†‚!„BTCA„B!ŞśYľľľUí!’ő<ú}$D Dý=¤Š!„BTĂ8œ–$IĺĄBD#żő!D=Œ1¸Ÿ*"„BQ B!„¨†‚!„BTCA„B!ŞvůîĐĐ&&&ŐwĽĽEČËKďÇâńxĐŐŐĹiDbjnnŚçr]éééAgg§ÚĂPUcccÚĎњ˜˜ÄĐĐŸiH^^ްĎ`يŃĽ@  öáŠÎi˘WD!„˘š>đz˝ęŽ„pSUUĽöTGď‰ŢĐ9M×6=‰ËžI’|äZNĽ} “ÉÄa$bŁó™č Ó|Žm<Žą$}ňů,I’Śf4&Ľny ‹6Ń:§ů kŹöDňó jh]´‰ŢĐ9M–"ň=QŘ BgˇŰŐ‚&ƒAľ‡@t.OŁk›>QŃ=zIô‚Îe~čŤ=9PZZęóx<*…đd4Ő‚ę|>ŸÚC „ :—隌7ĽĽĽ>`&ˆ áę~éî>ŞUSS|Šh™˝‹$úAç2żkŻkŹÖˆxO”łMÍhĚädTí!čĆŔŔ€ÚC „ :—ůĄkŹöPŃ)jꢋ7Ń:—隌g9‡#ZH^ž¸K•–C‰Ÿţţ~ľ‡@HZčćGĎ×VŃápř7ŔLš~ýú şCJcLí!(F’ŇŸĂ¤wÓč$ĂÓx\Óx\[ľJÄ{˘œ=hjFƒh b~hľĂüĐľU›ä â{ë­ˇ„ű ‰Ř%œ[SEd]ĉččžĆ㚦×íÝEźÎdDBĄUG4‚×<&­ťÂá0…›y$088ˆp8Źö0TÇëZŚçŃĚd řԌÁ ôđĹk“ö™Fď(‰¨čܝĆëZŚ×ŃŠx˝^őF’ƒAźćœDLLđIíDŚőööŞ=BRBçî4^×2^×V­í^—5|ŔL‘$Ɉˇ{Ÿˆ]‰$‰KrˇZ­F#>ŸĎ‡H$˘ö0IJ$ĄŠČ ×2^×U-í^(g 9{ä,ô˘ČÍkÝt2xĚeREäzgIDCçě <Žezîí^87kĂHEEEhS399bĽŔdđXfFA亨ŃĐ9{k™ž—îŠv/ôz˝¨¨¨č‘?şĂE´˜ŒÉÉôiL&, ‡ŃˆŻˇˇ—Śgˆ0"‘‘‹&“)íăđ¸Śj•č÷ÂX1 o‰VěKŻ2"UEn  ;Ť7đş†éujFÄ{ ×ë…Á`xKţ8Dß­G? .†VÎđGw" :Wo 3Kń088řžüń˙ßŢűÇśuß÷Ţď#Jä)ФŹT˛ŃVŒ0̎‹[)SpçÎIĹ Ćľ‡ş5í՛ŰĹą<\8..0÷)ž§ĎrńDPlݽèüąŮţ㖪ƒŐy˛Úôćá‰ĂĄęhתŒj˙¸]U*iőŁ–DRéCŠÔ÷ůƒ>4Eý"ĹďáůÁĎ 0ŹÔáW6yÎë|~}‹S3şœŽŞ÷ţé`ŒqÉi҄Ő'Üżńx\íeÄŚÄăq‘"řLT]1lnj݁ĹSUŃătU˝őOWÂĘ ŹňFéG˘ž ×čjxœĂxœKľŠ݁ĹSUô÷&ĐcXŞ\2™ęC‰˘(˘ŁŁƒĂjŒŢ^ßDýAŻŃ'tttp)Tĺq.Ő*zť–3ŠDDŻCÍô–**XĺO,ÃÔ^AŹËƒtwV*TÝ˝]K‡™%íťvť}^o6Ž7Ź„L&Ëĺ8T'˛š;w‚XzmŽ†×š‹×šT‹čí‡aˇŰ狿śjď`‹ĹňŤX,śŁśËŞ“ŠÁĂjä‚ŐjW-éîîFww7vîÜ ˆF٘˜˜ŔÝťwUYL$Áěě,ĽŹM1;;K#ÝK BŐÍŃ[4ČGD,ËŻŠżśJDL&ÓGĄP¨§śËޞ†cŠ,/ga2™Ť:†Óé„Ăá¨YÇČΝ;ńŐŻ~ľ Ĺôőőazz˙ôO˙„éé隬g=îÜšƒ#GލöüQŠŇѐžž>LLL *ú<źp8\ö˜Y^6n4¤ĄA" …VÍJR3z%b6ë+4U źŚÖ**Ň×ׇ3gÎŹ+!2;wîÄË/żŒ˝{÷ÖdM뇊•—Đ ńx\Ń"UQqŕŔźúęŤŘż?—PĽáuÎ2ňDU=^űJgˆkGź‡ĆÇÇĄ7ô–#Ť=ՉtwwăđáĂŤžv÷î]\ż~—.]Âőë×111 bÜ(jRĘΝ;qňäIôőőq]o(âz<‚Ř.Jż8Pŕľ×^ăţ~â ՇlŻ}#TüľR‰Đ]žRy˛rÉfs\ň›{öěᰚÍŮżácI’péŇ%źűîťĂÄÄĆĆĆpéŇ%Üźy@^FŽ?žáݙ(Š8|ř0Μ9ƒîîîU'SPT„ĐJGCvîÜšF:Jß[Z„Ç9‹1F3˘Č-VĽ^VýŒąpɃu­°x˝ŇóD\.תÚ~đƒBôŁ”ŃŃтŒ¸\.<˙üókł˙ţ5wm˘(RT„0ľˆ†ČŒA’¤Âçr´ńřńăpš\ŠŽŁřÍ1n4ĐßľOv Ů5dÖčT{{ű§z<9755ný Âk%Ł"Ĺ'ąťwďnYˆ:::Z•ýű÷~žťťŻžúęščG4ĹĽK—0::ĘuÝápłłł\Iĺ2;;Ťh4dďŢ˝…„‰‰ \ż~/^ÄŘŘؚǝ9sF3ő#źÎUFÝ_Đç5/ Ą˝˝ýÓŇŻŻůMĚfó'ąXěsľY?ššL†­Žćeő{öěÁČČ—cmĆÔÔTY-œ$e) K’„>ř€ť€óŢ{ďáÔŠSŠŸ 6â˝÷ŢSôřĹѐëׯČKýőë×q÷î]ěßżżđž“ Zűúú0::şFVj /1rD¤ŠI_Ń _¨j6›?)ýú™ššş‡÷—~]ë46ę+WV ŮlŽË<‘ÎÎNX,¤ÓiN+{ÂŁG*ţ™‰‰ LOOŻ›Ăňaä›7oŽ %+A$Áää$şşş}‚(frrRŃ4xq¤ńć͛kÚv'&&011žžžUH—˅ÇcďŢ˝Ť"—ľÂbąp™¨šË­ş>D×źp8ŒŠŠŠ›Ľ__ď7 ×⮙7z˕U ŻhRé™ééé‚0|á _(ëgúúúÖÍKOLLŕâŋ¸~ýşâ" kň<!ŁäkޏöJ’¤MŁccc*ÔmÉtwwăäɓ8|řpMÓ5üŇ2ƍ†úźć=v‹5šČőD$PÁŞÖŕľi“’u"ü1€|Üf…oÝÝÝ8sć̚\4ŻヒK—.Ő|ŘY,ŤIڊ €ü YəMĹľĺD%IÂčč(Ţzë­5“űúúđÚkŻ­ęŠS~iăFCý]óŠœ"Rú˝5"˘×Î@ŸĹ;ĺ’N/s9Ξ={`ąX¸Ť”â:ŽâÜ´ŒËĺÂńăÇqňäÉUóC$IÂ͛7×= ֒P(Díź„âÄăqE;eşťť ŠÎéééŠj=6ş(ˆŚä B‹ĹÂMDx3ľˆŻuuĚëGDŕrš"zěœŃ㔹ráŮŻTT$Nz{÷î-œ°DQÄţýű×=‰áâŋŠŁVĽhĽQú5Vš(Mˇ”‹œ-Ślt3Á ^ç&^󗴊ŻuĄP.—+˛Ţ÷Ö‘ćććŸém^@ĄŞJ‘$~QĽ(>q}őŤ_Ĺţýűqć̙5’‰‰‰Â´U-í}‰Dđᇪ˝  |řᇊG›'&& IîŢ˝[uĄéčč(†††ÖÜ(ČéUŢé^ç&^çJ­˘Çk]8FssóĎÖűŢşńŠŠŠ[ápřŤĘ.‹?fłţÂU•N/ŁĽĽú˘19=ŁD÷ŒÜn+WᗠH4U˝5p+BĄöěهåöRĄtJFF~577s9žœ:ĂáÇ ힲěđ‚Ň2ĺŁÇkÝ㎙[ë}oŁţŸđä䤂KRAt7ňśršnť +٨ĺďć͛ëSŇ’$áňĺËj/ƒ0—/_ŽY˜$IÜ#ňPÁwß}Ńh|đ×çŕuNâyžÔ"&SAP{óŘ)ÖMľl("€>Ç_빈§x™~oo/—ălÄťďž[8éŢ˝{o˝őFGGkv"Ž–™™ę˘!¸122‚™™ľ—ÁůýĚű†‚×9ÉčŃ=^ăŠ\b]Y÷7bŒĹZ[[çĂáđÇŁĚĘÂl6A'׺m!I˰ZŤďzqťÝp8Šu‰H’„üŕhnnŽů@$^„B!¸ÝntFTĹää¤.o궂çM…ĂáŕśŰŽŃëCôX¨‡aˇŰç×íYß0aˇŰ˙Eo=Úb%ČSVy tTdzzZˇ"sůňeEjiˆú NSšŻ x‹Œ>MĐç5. Ánˇ˙ËFßßPDŹ*ł*1™ ]'č'=c$IB P{„N şIGŞ ĽeĘCŻ×ˇÍ UMD V•œţ§z4ĆJŕzt:Ř˝{7—c™™™\ťvMíe:ăÚľk†Š Q’ÝťwĂétr9–ŃÓ2zźśĹbąM UMD„1ôY°jąčď?Ť˛Ůˇđ#EEĘ#c||\íe:a||zŒ(ŤŻsĎó˘VŃăľMvŮ)ÖcÓOggçÇz|3éąÇşRx7Łyĺ ušőQ["‘Mč-‡ĂACĚ*@×śp8ŒÎÎΏ7{ĚŚ"˛´´t[At9yŽxćB)*RŠŒľ&ŒĹΝ;šlc_đ<÷˝>¤ąŃ¤Ëů!ĄPKKKˇ7{ĚV"rCŻłôhŽ•Ë­p{ăíۡËqŒLgg'Ű00‹$#eŔëܓN/zˆ ßkÚČČ–––nlö˜­Ęou;ŘLŻ˙i•Ŕ+)Š"zzz¸ˈłť"ô IDAT„•B2˛5===Ĺꡏ(-ŁUśd&łŠˆ0Ć"vť}^Żu"z cUB:˝Ěm‡IŠŠŹIą]HF6‡×9‡1fř´Œ şyc,˛ŮăślHÖë`3°XšÔ^‚⛚šş˛ŐăĘŃZ\\ô˜žŃŤEVϢUˇŰŽŽ.ÇŇ3$!oHFžĐŃŃÁm_™z(Rôy- ‡ĂX\\„śzě–"‹8ލÓ3‚ čr]Ľđ,ÔŞ÷Z’B)HFňđ<ÇÔC‘jS“>ëCĄGtŤú źˆL&Ó-=Š ĎIt•ÂóŽ ˇˇˇnœńt:MCĎ L$ŠjÄz—‡ĂÁuƒ;ŁŠú˝†…B!˜LŚ ÷—)Ś,YXX¸~ëÖ-]ĆżôŇÚT+Rź$$`xx˜ĆÁńńq #ŒlŞ Š˝^ĂnÝşľ˛°°p˝œÇ–ť_(‘H4č1*b25~Ę*Sňjĺ­ˇ¨O ‘79 ƒ´Qž¸víZalűĚĚ ÉČ6ŕ aŒŐ…ˆ46štšŰn(B"‘h@ő!@™"‹´ˇˇŞGQÔ§QVc ŠEE*E ‘ ‡Ă¸páBU,B]Ňé4.\¸°f;’‘ĘáyNIĽřÝxi˝^ťBĄÚŰŰ?-§>(?"“ÉtSŻ9é5´U)’ÄODz{{ ßA٤„ČĚĚĚŔď÷ËŰ`:brr~żÓ˙[’‘ňčččຯ Ďs–Ńëľ+ Âd2Ý,÷ńe‹Čěěě?Ž#‹moe*R/é™\n…ëôĐĄC܎Ľ5j!!2’$!@Żű6Ő####$iÓǑŒ”Ďs‰$eę˘eWŻi™X,†ńńqĚÎÎţcš?SöoÉ ĐíöÖz qUJ2É/ ŕvť 9mľ–RL(… ÇˇýꄞÄăq\¸pĄ˘ýľHF6§ŤŤ‹ŰÜ€ď9NËčőš%;‚ě ĺP‘níÚľkTŻu"z qU EE6G- ‘™™™Áůóçńá‡nűů eřđĂqţüům˙ż’ŒŹECś‡^ŻYĄPťví­äg*‘ŠŠŠ¸zőŞ.+„Lچşnđ˝cčěě4ĚÎźjKˆŒ$Ixď˝÷0< łRxGE<î'jEBЉD"đűýT;˘"###đűý܆БŒ<Ábąp픩§hˆ^ŻUEcÝ+Şá¨HDä6Ţ@ Pɏi˝†şśĎ;§ÓŠëŃďZ”bBĄ†††¨łŚ†LNNbhh¨˘Zr!Éłoß>8NnÇŤ—h ßkU ¨¨mWŚâ؏$I˙Ź×:A Šfľ—Q”ˆŠčqșÖ%D&‹ڞÎÎÎ*ö<őÎěěla:Ş’€ő.#‡ƒ˘!ŰDÍşÜ[ČßTI’ôϕţ\Ĺ"˛´´ta||\ˇűičun˙vŕ}áőzšOiô"!ĹD"œ?׎]ŁúŽÄăq\ťv çϟŻŮšŤže„÷š˘ž˘!úźFE"ŒciiéBĽ?[ąˆ0Ƈ#Ş×6^‹ĽIˇE@•Â;*âvťą{÷nnÇS=JH1áp~żŸ„¤JdńűýkŚŁÖ‚z”‘Ýťwsm×­§hˆÉÔ Ű´L0”wŰ­řś­+˛ÉdşĽ×:@żýŮŰ!™Ls…|čĐ!ÍŽę]BŠ)JٔĎě쏪RL=ɈĹbáÚŽËŤŤhˆžŻM@ ěÝvKٖˆ,,,ź­çôL˝Ô‰ů¨Ď=hœN§Ś÷Ą1’„‡qţüy ăÁƒj/Gł˘(ÂívĂívŁłł“[ĎŕŕિŤerr333ˆD"ˆD"Uíý˘ôţśX,8{ö,×UIĘ`qńˇă遧žjŐíłŢŢ^|úé§?šŸŸ˙úvQ•ˆ‚ŕpő—żüĽ.Ó3°¸ř¨ŽB€‚  ˝ÝÎőEçÎܸqƒŰńĘAď'p-ĐŮىÎÎN8N¸Ýn8ÎЧçnWDâń8bą"‘bąfffęú˙AŻŻĺƒr/P›KÔYmˆ­­Íj/c[D"<ýôÓptť…Ş@ŠŔéŤŐ\W""÷塴ˆÜŽšoß>D"‘šľ•ęůÄ­%6şř;ÎUäŻUş óřřxĄ€4‹­úCu•$„¨'´,#]]]Ü[usš•şŁ Ł÷kN €ŮlţkÇâ""óóó?ŒÇăĐëČw ?ŕLŻíSŰ%?q•8ôĉ܆i‘„őˆeÄbą(’~M$Ő]Ş ş`ä‹Tăń8ćççČăx\D„1qťÝ˙Şç¨ˆ şŽ^Ţ.™L–{ŠFE T}’˘žŃšŒ pŻ IĽŇČdęŤQČwĘčůĆ7Ŕív˙+c,ÂăxÜ #"‘Čß]ťvM×­yVŤE×/Ží˘DŠŚłłGŽŠęçIBˆzG+2räČîižzMÉäoző›–‰Ĺb¸ví"‘Čßń:&7aŒDQLSTD0Ə§¸ˇˇˇˇâŮIAŁśŒôôô ˇˇwŰĎ˝ńxŞîR2€1˘!˘(Śc^ÇäÚ*âp8ţŢď÷ó99ŠëIŤ@}GEcX\Tfâaoo/žň•ŻÔľ„366†ĄĄĄUwîÝÝÝ8sć >lˆ‹ŚÚÜźył {rôÉ𔑯|ĺ+ŠHˆ’ç=`„hH0”‹T˙\‰ă+""ŒąČŽ]ťF)*˘o˛Ůœb;b>÷Üs$!EH’„ŃŃQźőÖ[kęG^{í5ôőőЏ:ý#I>řŕƒÂç}}}†‰8ń’‘çž{Žăޞ°¸ř¨.[ueŒ ńűýŘľk×(ŻIŞĽ(V9355őßFFF‰D”zŠšPĎQ _/˘ľé‡F“b˘Ń(.]ş´*• Šâ*9!śÇčč(˘ŃhásŁDE>2˘ÉdşnëBcDC"‘FFF055őߔzĹD„1˛Űí󃃃J=EMv{}‡Ć“II3'#KˆŒËĺžđ…Âç7oŢ\u%śĎőë× wwwŞ0Xk2’ż‰Šßş°ŰEÝßČÂnˇĎ3ĆBJ=‡˘˝D‰DâŰWŻ^eznĺQÔwŰ´^­ ňĂÎäşI’VEGˆę˜˜˜X]2RTЎŒ(™ÖŐ &Sƒîç†Äb1\˝z•%‰o+ů­b„kE @<G<?Żôs)."ŒąH[[ۏô^´ &Ýçüx I¤RƟÜęIBJg[Ü˝{—:e8ĐÝݍ—_~šń˜˜˜ŔĽK—pńâĹU˘W*FĄ–2’JĽ™ČŹ7DьĆF“Ú˨šÁÁA´ľľýHŠ–ÝbjRš°°đm# 8ŒQ̓Ľ%Iń“ÎĎ~öłş !,.PĽhHőěÜšǏ/üťŢźy—.]*ŢŘŘXaÄ>CFE€źŒüěg?Sô9$)ƒĽ%Şe2J—Ľ<ŔlaaAŃ"U™šˆˆQœůZ˝nˆWŠŇ4Ď=÷\aź‘qš\Ť˘!|đľërŕŤ_ýę* ]ó˜ŇiśFwäČņ•Ô!SŒÍfŒšSJ0+Ľf=Šň€łP(TŤ§T ŤŐbˆĐ˘Ń¤˘2"ďMSÍV­S,!Ńh” T9°wďŢÂäԉ‰‰u%DţŢÝťw ŸˇNë‹Ĺ˘ŘŢ12ŮlŃhRąăë‰ĆF“@(R|€Y)5ĆX¨˝˝}Üľ"ĐŇbŒ“Uľ0ƍ&ÝÚťˇˇˇę}i´JéP­ŃŃQj×ĺ@q˝GąhŹGéîźFˆŠČűÇ()!šÜ ˘ŃdݡéĘĺš088ˆöööq%˜•RÓ)]sss>ŁDEĚćF*\}ŒÉԀ†eÑňN˝Š>O­)ކLLLP4„Ĺí¸[Ľš˘Ńč*9pŕ€ŽŰy;::Ví¨Ť BÝz”Eł!ÚuĺhČÜÜœŻ–Ď[ÓW‘Ѣ"T¸šGş\śšü;Č2ŇŐŐĽřsՂýű÷ŻÚxmŁôQĺHĹŘؘ!öĄéęꪉ„ůz9—ËV÷ijٍęDC€‹`ʍˆ‘^€ŰĄ–"#Š"ĐÓÓSłçTQńüóĎ>Łv]ŽwÔӖ[:JďŢ˝şkçíééÁŔŔ@Mk\HFŒsCŞV4PADcĄĎ~öłŒEs]N\UCBŠńz˝şî¨yţůçŠ]WAŚŚŚ —#ÝÝÝŤÄP’$\ž|“““j/§,>\ř8RmˆBźűîť8sć €|*ě̙3¸{÷n!Zň…/|a•œH’„>ř@7˙]]]8qâ„fڍeQşĽ_+X­Ćٕ]Ž yřđaHçW-§đ¸VäýP(ÇŁÖ2¸ał‰H§ł†~jMBdäşŮęľLiš€R2Ę1==ëׯŻż˝{÷Žj—–ÓŐNÇýýýšB*Uť”‰ÇăÁ+Żźb˜ńđ„qéęęÂ+ŻźRÓşšT*ĹĹő¸‘Œä—ag]żßÉÉI,,,|[íľĆXÄáp|ďÍ7ßdąXLíĺpĂé´ę.ES ‘YZ’6<ů)źWÍÁƒu70>‹ŹŮ^12‹‹°´´yŰr=ˈ p:­j/ƒąX ožů&s8ßÓB4Јˆ@<˙ËL&“1RTDo9ĹZJˆŒ$e‹Őv+qšvd÷îÝ5{N‚ŘŒÝťw×´ČżWcądŮiŇz•ŁÔüÉřý~d2™L<˙Kľ×"ŁaŒĹl6ŰšsçÎ!‰¨˝nX,MşéŠ!!2™Lść•őN§'NœŔŠS§ŕp8jöźQŒĂáŔŠS§pâĉšŐ‚O¤"“ÉnëçęEFŹV ,–&ľ—ÁH$‚sçÎÁfłcŒi&ý €šššż°Űíó>ŸŞ-ÍÜŃz‘“š"#ŸŕŇéĺmŻa;ČsGúűű)]CÔ ‹Ĺ‚ţţ~ř|žš'K§—šźW.#&´´§U|>ěvűüÜÜÜ_¨˝–b4%"H$ž~íÚ5ĂlˆN<žRe œÇăÁŮłgŃÓÓSóç&ꋞžœ={V•!ŽÉdńxŞęT¨ŃeDoiőr…B¸ví‰Ä×Ő^K)šĆX¨˝˝}ÜhQ‘ĆFěvmŮľ–$¤˜dRâr˛Ź§Ó Ż×KÝ5„"ČÝ0^ݎŚi XňůíĽcdąŰEÍ­ŠZ|>ÚŰŰÇc!ľ×RŠćDČ97T;/ˆ˘˘hV{´+!2ՆŤAîŽ9uę”憥úŁŁŁ§NŞy7ŒŒ’iO#ʈ–ÎÓźđűý×Äđ˛őФˆľČW`Ťý†Óş„ČČ'šZ ?+ĹívăěŮł8rä´ăp8päČœ={VľMę$)Sł÷Šd¤ąŃd¸”ŒŰuKѤˆův^AbFKрáŢ|˝HHńs-.>R%U#ÓŰŰ ŸĎGBB”…, >Ÿ˝˝˝ŞŹANĹ,.>ŞÉűĆ2"ăĚ ‘ńů|!ŚĽvÝR4;ú“1áO‡‡‡Ż bw^“ЇąX˛ŚĎŤ7 )&^ĆÂB‡UľUoo/z{{‡qçÎĚÎÎŞ˛B›ttt`ßž}ŞÉ‡L6›C<žB.ˇRóçF“şÝ(Ďá°*ž;x­ …B€?ŐRťn)š`ŒŸzęŠqŸĎׇŐ^WĚćFŘl–šuˆčYBdrš,,,ÁfUÝËG’H$‚P(„ÉÉIŐÖB¨OWW<jé—b’É4ׂÔJŃŤŒŘl˜Íšžn š@őáÇAľ×˛š˙—Ÿ››óÎÍÍýŇď÷Ăhi›MD6ť˘řě #HH1ɤ„tzYő;ˇŰĚĚĚŕΝ;Wm-DíéééÁž}űT)@-%—[A<žŇÄűSo2"ŠfŘlÚęhä\  @“ŞĹh^Dc§Óů˝7ß|ó˙ôz˝‚î:xŇÚڌhtEą7›Ń$D&›Í=ŽŽXTŸ\ŰŮŮ Ż×‹C‡áΝ;‡ĂˆÇ㪎‰P‡ĂŢŢ^ěۡ˘¨‹W*•F2™V­†j=ô"#ZŤŔƒH$"¨ţU,‹¨˝ž­Đźˆ@,űNKK˟ú|ž§‚AMG˜*F.*Ý~›F•Ć––$d2YŘíÍŞçwEQ„ÇăÇăÁýű÷‡ńŕÁU×Dđa÷îÝčííŞ={Ô^J\n‰ÄŁŠÇ´× ­Ëˆ|îŐâ Éjńů|ČĺrsKKKßQ{-ĺ  €d2ůG׎]{? ÂëŐ|¤Š"Lڏ\6,,,q;ŚŃ%¤˜L&Ť™čˆĚž={°gĎÄb1„ĂaŠ’č9úŃŰŰ[ód[ĄĹ(ČzhYF\.›ę7/J qíÚ5ř#ľ×R.şĆX¨łłóű§N˜œœ´vb¨š}qń—cՋ„ČČŃIZÖÄŹ§ÓYˆ’D"„ĂaÜżétíÇŘ[cąX°gĎôööj˘ř´”l6‡ĹĹGşy_ڔ-#x‹ĹpęÔ)ÖŃŃ˜™™ Š˝žrэˆŔěěěľX,ߍ6uČMeł+HĽś‘ŞG )FŽąZ-°Ů,š ťşÝîÂĹíţýű…?$%ę"ˇüG‹0ƐLŚŤ:7¨‰–dÄjľnrŞĚŕŕ ŇétzqqńżŞ˝–JЕˆ<ž-ňĄĄĄŤ^Ż×PłEdZZDŹŹ°mM­w )&•JăŃŁ ěvQ“'â‹IIíу|ČHR‰„¤ů4ĚVhAFDŃl¸ueBĄ†††ŕZž˛ş 0[$400ŕ ‡ĂšËÝň ľľŮlŽ˘7IČZ䊏’´Œ–ínbU|1œ™™)H LăKGGGáßZ -ˇ[‘Íć ĹŘFAM1âřv™X,†´ˇˇ‡´>3d=t'"077wtiiifppĐbÄ €ŠŢh$!›#łŠ˘vť¨ŠtM)čěě„Çă$I¸˙>"‘"‘ťVˆĂá(¤ĂöěŮŁ™vŰ­`Œ!‘TŰcIiԐůiT1;;›–$é¨ÚkŮş‘Ç)šFNєŰÖKR>’”A:˝ ŤŐŤŐŹi!ňíŔrאżë‘Ľdff†"&%ttt łłł z‹–2ƐJeJiżŚZj)#FnÓVĽdNč-%#ŁKž¤h^~ůĺ/˙ô§?mĐŰI§äśŢ6“" М|Ń_ţnÓfÓWњÓé\%&’$affŚ &333u5q8…č‘ŰíFgg§n"ë!I$“éšďŁ&ľůűFlÓň7'/żüňJ{{űő˜’‘Ń­ˆůĹb™öů|b P{9Š ËFЌ„TG.ˇ‚ĹĹGH&ÓşQWuâOädffąXŹđą^‹`-KA8œNgác=KG1ő( Ĺ()#jďć[ |>~ó›ßdŇé´.S22şš‹fxxřŞ×ë5Ü 3y MVač-IDATąOĽR¸xń"RŠTMžŸ$¤zň3ňwˇńx éô˛ÚK"4F:˝Œx<…ůůD]ĚQ’‘ľČÓSÇX,űŽÚëቡDâńřן~řa:‘H0Ę>2ĺP7"ä÷Ł‘$éěĐĐ×agĽVŹľ^t“ŠAńť ˘60ƐN/#™”& r‹%ą´$A’2X^Î2ĆĂňr’”ÁҒ„X,YŽh4‰dRB:˝lČß˝ŢŁšľŠ:th•p\šrŠT ÄoźžžžuîöíŰřîwżËĽĺ7`hh’$5Ň>2ĺPW"Œą@KKËyžĹŤĽ/Ň÷Ţ{O3}č2‚  ­­…śľ7 rýƒ9‰FóhYPäŠ,)ZŽdłš‚lČY8>\D4š,D:2c W˝#Šf´ľľp­Úę|,wÇ?ţ˝÷Ţ+|ď̙3đů|ëŚbRŠŽ\šRxüv‹S- lű@:Ĺđíťát:ÂűÉÉIGńę•+WpăƍÂçźZq'o"^ýěKKRŠ4—cúŚąŃ„††ü żŠéIɘÉ$Źš-ţžĚfíťËËŮUŸçr+Čĺؚﯬ0MËQ;ŹV ZZDŽÇź}ű6nßž˝ĺů8•Já7ŢX%-ögśŚáŕƍkn6_xáźôŇKŰZ_,CWWáŁX,öÜś˘sę.""Ç˜LŚIÇĂeňęĄC‡Ö ,óűýk Y+AŢďő×_çşA^K‹ˆÖÖfnÇ#ôK6›ď"Éd˛H&ĽÂ9˛Rü§RJ~qńѪ琟—$„ň3B”yrľßďß42"1+ćęŐŤkWšŽŠVB<L&Ód<Ż›âÔRę6"‚ ôZ,–˙uâÄ ‘GÍH*•‚ßď_ӖűĚ3Ďॗ^*ťÂúöíŰ[#1\3žhC)˘>óǚŻmůÍoâ Żˆ0JuƔ++gzőzQ‘—^ziĂ„ÉÉÉŞŚa ŕňĺËR:ţR˝Ő…Sˇ _źšN§˙`xx˜ËdHů…^ü˘-WBćććpăƍBôc˝H ď ň¨Ł† 5Qę´ ‘W‘ Wף Äđđ0ŇéôÔł„u4Gd#c!ANŸ;wîűnˇťę1đVŤ/˝ô:„{÷îmŮĘ{ďŢ=Œ•-ˇoßćÚ,wÔ$ůŽ ‚ ˆZ ŠfŘí"÷Ą}ە™ƒâÇ?ţqĄűQ.\ĺ8wîœfŒ…¸X§ÔuDDFî¤9}ú4xuҔöŚ“JĽpűömźńĆđűýëJHé&y2ň'Âe}ĹČă“m6 ÷cA”błYنb˝˝cŽ;Vqä˘4*răĆ n;¤‡Ăaœ>}őÚ!ł$"I$ßjkkűQ?SjOššš9\šreÓA8VŤǎƒĎçĂÇW}o;o¨J°ŮD8|wś$‚N§ 6ߢT`ă ěŽ\šRąDźđ kŇéëŽVJ8F?kkkűQ"‘řVŐ4uŸš)faaá?Űlś˙đľŻ}íˇxîI#§^6ë )Ýĺą´ÂťŤŤ 䲞Í0›ŃÖւxŻvœťź‡L.—ű÷ĹĹĹ˙\ŐÁ EDŠ`ŒĹ’Éäď.--}ÂŤ­ČWVo$!íííx饗đÝď~ˇ !7nÜXóx%R2a25Đđ3‚ ¸!)Ť…„ČuzĹ3C6ęh\ďçeúúú űUÓ˘ 5ATŒź_ŒŇ‘UY>ä]ĎeÉXoşuid¤¸Q@ŢěŽyüB5 ŕţýűR&“ů˝zoÓÝŠˆlc,,—†‡‡˙ @՛äÉo–ÍF ŻWŔZ˔ĚFČ'”d2MŁá ‚Ř%Fľoţ|ůók)˛Œl)ŽR—Śsx000€ááa¨ëe[A‘MxüÂ9=<<źî‹źR6{‘ËÓT‹Q+%ł‚  ĽE„ĂaĽŽ‚ ÖE8֚JČVl‘夽˝˝P ŸĎ'KČi’Í!قÇ}ާ‡††ŞŽŠl„ÜÖ[ŒÚ)™°XšĐŢn_w4‚ ꗦŚF´ˇŰaą4Š˝”5l$#2O=ő×ç ňŕzpB"RŒą@ggç9}ú´"2rńâĹ5šÉŁGr^Č{C´´đŸŠH„žŁĽ.—śgńÜ}3NŸ>ÎÎÎ˙BR$"e2==ýˇňôUž2˛ŢÄžƒr*ŐjĄBV‚¨cäú1ŤUS™7’‘ő Uˇƒ,!---ç§§§˙śęÖ $"H$žŐÜÜüCž2RZ"ˇőęyÓ*Oő…ÍfŃ妙ëÉČ /źPu¤D–ćććŇÔÔĘ АT*őMž2Rú†P˘rťŘl˘.OJATƓ›í¤VJąŒT;Ź X-!ŠTꛜ–Y7ˆlž2RÜÖŤ—”ĚFGG´œ+&˘rAĐmd=d! Qj}Ř&ŠTę›VŤ§OŸţďß.]]]8věúúúx-OUlśü$ĹĹĹGX^ÎŞ˝‚ Ş¤ŠŠ­­ÍŠŒhW“jÇ#„đD¤ xĘHé”U˝c25Ŕĺ˛A’24"ž tJ-F´ë’~ˆT O1"˘h†Ĺ҄DB‚$eÔ^Ae"ŠfŘíÔ˘ż$!|!áÉČć‚€ÖÖfˆb––$dł9ľ—DÄ46šĐŇ"ÂlŚËĂz„đ‡^iœ Ůłšmm-HĽŇH&ӔŽ! !Łęe&ˆ„(‰GHFĘĂjľ šŮLé‚Đ”†Ů’ĺ0V ´HĽRßTbŤŃÓ5mm-´o A¨DSS>JŮÚÚL˛ ĹSIBřC"˘‰Dâ[ňŢ4ŸŻ°w MLUşUˆéééż!544ôýX,Fё-0›ąc‡Ú} BA¨ˇ20<< §§§§*/ǰˆ(c, †‡‡ż€d¤ ävßT*T*CBBVŤV+M=.—b Ą]t•…bá óřüěĺ˗ĽŢŢ^Äb1ľ—¤yňŐű"ÚŰí4.ž Ş@î„Éż—¨ľbąz{{qůňe Ŕł$!ĘC"Rcát:ýĽ{÷î=ňx<$#eBBBۃd{Äb1x<ÜťwďQ:ţc,Źöšę‘Á g2™ßűä“O"_üâWÂaz}—‹,$mm-Z&ˆM(ޘޤ2Âá0žřĹ/Ž|ňÉ'‘L&ó{$!ľƒD¤†0ĆÂŃhôم……ďďďg$#•a25 Ľ…"$QJi„:Đ*#ŁżżŸ-,,ü{4}–$¤śĐŤľĆ0ĆbÉdňwŻ<űěłTŔş (eCy(S=@Ď>ű,Ż$“ÉßeŒQîźĆˆ¨c,6??˙uyđŮŕŕ ÚKŇ%ĹBbÄ-Ę b#LŚ´ś6“€TÉŕŕ`aPŮüüü×IBԁÎÜ*ňx8ÎésçÎa``€ŠXˇ‰ E3věČ Mj%ŒJSS#Z[›ąc‡˘h&Ů&ąX 8wîœŚAeęBgl•yů$ŇŐŐEEŹhl4ĄľľO=Ő ›ÍBiBw˜L °Ů,xęŠV´ś6“„p ŁŤŤ‹}ňÉ'‘Çíš!ľ×DˆhšŁF„žüĺ/ŻP+ä:’;ěp8ʰXšÔ^AlŠĹ҇Ê;¨ţƒ'@_ţň—WAřˆ:c´‰ˆ†`ŒĹbąŘsŒą‹´aVŸŕ)JBh9úAÂŹ ňĆuŒą‹ĎąT§!čLŹAä"Ö .¤i+ň'ý'QڌP Q4݊~óEŽšpáBT”ŞYčUŻQcI’ö}ôŃGó4‰U9,–ŚB- uÜľ@î|‘_sýPyRęG}4/IŇ>Ú3FťˆhĆX8‘HüÖâââżQ݈˛Č-Ŕ.— ;vŘŃŇ"Rq ÁĆFZZňQ8—ËF­ˇ #׃,..ţ["‘ř-ŞŃ6$"GŽihhřď§OŸŚy#5Ŕdj€Őšß݃¤„Ř.Ĺň!ď“DŠe‘烜>} ˙ęAô˝+tB,ű€ŁďźóNÚăń€R5ľa=)Ąô ąMM$*‡áńxđÎ;ď¤}|Î$t˝Ctc,(IҞ_ţň—żčďďg”ŞŠ-˛”¸\śB~ŸBěőœŇ“k>\.ɇ ô÷÷ł_ţň—ż$ic,¨öšˆňĄw‹Î`ŒEâńřo­ŹŹ\ Tz”^€œÎüˆR8ƧąŃŤŐ§“„TmŠS1+++âńřo1Ć"jŻ‹¨ Š1ë”D"ń-AnźóÎ;—Ăá°% ˇˇWíeŐ-fs#Ěćüۉ1†L&‹t:‹ĺĺ,rš•WGTƒÉÔ€ŚŚFX,ů˙cm‡100€¤œH$Ń)Ń1rŞ&‰Œ=űěłđűýj/‰@>Z"ˇďŘa/lĆ'Šf Ů듩Ąí*ţ˙łXšHB4‚ßďÇłĎ>‹H$2FŠýCó8 ůœÓéüË×_ý˙ ƒƒ´qž†0™`2™!ŠůĎĺˆI6›C&“ĂňrVÝÖ9MM0›Mhl4QÄCăÄb1x˝^ŒŒŒŔáp| Rݞ„ÇoČgň“Ÿ<ěęębÁ Ý h9bbł‰pšlřĚghkkAkk3ŹV uĺ(HSS#ŹV Z[›ŃÖւĎ|Ć—Ë›M¤ˆ‡Ć ƒčęęb?ůÉOx–$Ä8ˆĆX8™LţvsssŕčŃŁTČŞ#ME3ZZVˉĂa…Í–—J딏\×ałYŕpXWIGK‹Q4SaąN R=Šććć@2™ümPf,čÖË`<Ţó§‚ üăĺ˗xëÖ-ó~đƒÇŁö҈ iļ ňѓ'_ĎfsČĺV˙Í 3ĆÔ[Ź ‚€ĆFÓăÔדI0ŒC(ÂË/żźň›ßü&ŕ333ę5 $"…1açŁGŽžř⋞×^{ ƒƒƒT;bŠĽ”l6‡•VYTčŽENQɢ!‹GCƒ@˛apbą144„ööö§ÓéŁ4!Ő¸ˆ˜ÇoÜAđ^¸pár0´PtĸČhš•x=cX^Î>/”L&Wú#ŰbŁZłŮ´á㚚LT§Qç„B! `vv6 ŕÄÇ) bp„z çÖ+‚ 8ŰŰŰŻÎÍÍQt„ ÍQ ÍÍÍQ¤N Š3AđZ,–Z,Ëđđ°ŕőzŐ^AuN0ÄŠS§X:Ď7h.H}Aeřuc,˜N§wʝ5^Ż‘HDíeQ‡D"x˝ŢBGL:ŢIRPD¤ŽÁcłŮţ_“ÉÔ~îÜ9ÁçóŠ˝$‚ ężß7ß|“ĺrššd2ůGŒąÚk"ԁ""u c,´´´ôAţęő×_Goo/BĄÚË"ÂŔ„B!ôööâő×_‡ ľ´´ô’ú†D„§˛>ýë_˙züĹ_¤AhApGLöâ‹/â׿ţő8€§i:*ˆaŒE>|Ř ŕh0Œvuu1ÚD řý~<Ţz" ŕčÇ{ď“E$"ÄjcÁx<ŢMé‚ ŞĽ4 ÇťŠ•(…D„Xc,VšŽĄî‚ ĘEî†)MĂĐ\b=HDˆ )JןxëÖ­ů§Ÿ~ƒƒƒT?BÄşČCɞ~úiÜşukŔ‹”†!ś‚Úw‰˛ioo˙ż“Éä›fłŮLížA#ˇăf2™ŒÍf;777÷j݉Đ!Ęfnnî/$Ię”ëGÜn7€ÚË"BEÜnwĄD’¤N’˘HDˆŠ(ŽI$?:}ú4<´D …ŕńxpúôi$‰ę@ˆmB"Bl ĆXd~~ţëžţřăÇ_|ńE‚¨dyńĹńńǏxz~~ţëTBl˘*Š ZIH¸Ź# TˆJpD„ŕc,DBBĆc Š˝6ˆ\)’_üâHHBŸ Č/~ń‹ !‚D„PĆXčÓO?ÝА„ŽX/ň駟î!!”‚D„P”R6Á My&- )C¨‰QŠ…äç?˙ůčŃŁGi AhyČŃŁGńóŸ˙|$ DĄÉŞ„*‚ŕnkkű čp8ŕóůŕóůŕt:Ő^AžX,żßżßx<Žśśś-,,|›:`5 !TEˇĂá8›N§}fłŮ|ôčQappnˇ[íĽ„áˆD"ÄŐŤWY&“ÉX,<?OB¨ ‰Ą ApđÚíö˙‘H$vô÷÷ĂçóÁëőŞ˝4‚Đ=Á`~ż###°Űíó‰Dâۂ4•ĐT#BhĆXŒ1X\\lGI‰ßď§ ˘BäpKë?Űc’B+PD„Đ,‚ ¸;::ţŸx<ţMI’,§NÂŔŔ<ÚK#Í …0<< QÓ‡ăďggg˙œŇ/„V!!t .—ëÍh4ęîęę‚ĎçĂŔŔˇňя@ żßÉÉI¸\ŽH4=Ç ¨˝6‚Ř BW‚ĐŰŃŃńŞ%9r䨖„¨K‚Á Ž]ťVýx‹1V{mQ.$"„naŔív+‰üG‡Ă  ˇˇWíĽ„b„ĂaÄăq¸ÝîD"GŃBݐˆşG÷Ž;ž‘ÉdţD"ąCNÝx˝^j& A$)tžLNNÂnˇĎ›Í濞ŸŸ˙!Ő~z‡D„0rę&‹}#N‹===…Ô I Ą'dů‡Ĺb‘œNç)őB °‚ŕmkk{)™L~…¤„ĐëɇÍfű煅…ˇc´AaHHDˆş€¤„Đ*$D˝C"BÔ˛”äršßÇ㮞žx<*t%j†\p …0>>‡Ă5™LˇH>ˆz„D„¨kAčmiiyEĹŻĚÍÍ}Žľľ•=zTđx<đz˝4§„ŕB,C0D(ÂŐŤWŮââ˘ĐŢŢţŠ$I˙ź´´tj>ˆz†D„ #‚€w׎]_ŸššÚ===đz˝đx<4ѕ¨ˆP(„P(„`0ˆńńqŔŽ]ťF§ŚŚţů}^"Ş. 4‰Al€ ގŽŽŻĺršsssŸłŰí+ż˙űżß K ĽqˆbÂápA>nÝşľ’H$Š˘7(ĺBëC"Beđ8Zâikk;,ז´śś˛_|QĽ„"&őE(*ČÇűďżĎ…˘ZëBő ˆ­!!ˆm ‹ÉŽ]ťŽ%‰ßK$; żżĹbB5&Ć ‹­‘‘€ÝnŸˇŰí˙255u$ą-HD‚Ĺ¤ˇĽĽĺ`KKË 333_€ŽŽŽ‚”PÔD?Č)–p8Œp8ŒÉÉI@ggçÇKKKˇ—––n“xDőˆ„B‚ŕĐťk׎ß/ŽšČr"‹‰ŰíŚY&*‰D‰D֕Ž˘hÇ-äĽ#¤ęb  ˆDÁ  ĺäŃŁG˙!şĺď÷÷÷َˇN§“…3Ĺ‹Ň épš\‘ćććŸÉҁźxÄT[0AÔ$"Ą2‚ ôp#/(2™Ěççćć>'ż§§§ 'N§ł +Ôľłšp8\ šŚ#‹Zg ˝˝ýSłŮüÉÔÔÔMä…#B3<B]HDBŁ<'OGGÇçsšÜď¤ÓéĎĘ)°Űí+żó;żÓP,&˛¨IVdšEŁřk}ôŃJ"‘hkˇŰç-ËŻL&ÓGłłłŸˆ‘p„6!!ň¸8śđG˜››ë)}ź,,ňçĽEłëŃňYJ …B~^*2íííăP$ů„ţ !ƒR$+ŔiAKKK‡(Š_’—ËĺĹľ*ľÄĺrEL&S\ţ\’¤˙ľ´´4űřÓČă?IA‚ ÖĽDdŞ…D‚ ˆuů˙:éQmÄEŚvIENDŽB`‚rusty_paseto-0.7.1/assets/paseto_logo.png000064400000000000000000001425061046102023000167250ustar 00000000000000‰PNG  IHDRäď…iŹ: pHYsĂĂÇo¨dtEXtSoftwarewww.inkscape.org›î< IDATxœě˝y|TUš˙˙9ˇö˝*UIH*!$bˇjˇŇŽC‹ˆ¨¨íL㊀ĘŘŘӎ==ӓ_ĎŻ{Śgůöˇľ+‘U0L¨$EŔ}AŮAK–ĘZIm÷|˙€Š!¨¤nŐ­[uŢŻW^P•{ĎyRËýÜç9Ďóƒ3ŞŞŞ”ŠyžŤT*sĺrů8š\žĂq\:ĽÔ “É”„ĽBĄ <ĎË9ŽSČd2PJeĄ)Ľ„B9Žƒ×땷śś>GэĆ˙PŤŐź\.§”Ň`0ä€äyžň<ä8. ÁóźŸçůŕĹ{yžopĘëőĺyţkżßżßd2źçž{:Ĺ}ĹŒäA.ś F˘ĐĐĐ`ěîT*oP*•?"„ČĺrŁ\.WČd2!D.“ÉäjľšSŤŐDĄP…Bš\…BŽă†=§ŰíFkk+!dôčќÉd ˘w J)@‘ßďGčÇçóń˝˝˝üŽ]ťř`0čçyŢ űxž÷oý~˙ű}}}x˝ŢfϞ}~؆3ŒďÁ™Á ĆŽŽŽŰu:ÝTš\~“\.ˇ* !DŁP(dYYY2­VKT*T*!b›|U!P(P(.Ńpîâ(ţÂçóĺ÷ööNéëëŁ===üŽ]ť‚@ /x‚Á Űçó}ÚŰŰťŃh4n›:uę™Xý †Ôa‚Ě` ÁśmŰÔÍÍÍwčtş9ŽťEŠTš …NĽR)ssseZ­–h4Čd2ąM9JĽJĽ&“‰]üQ0ň3}úôąíd0B0AfÄëÖ­+ŐëőżQŠTŁRSSU6›­3E§Óa̘1r枞ž‡Ďœ93sÓŚMnÇóW“ÉôϡÝv[ŸŘ62’&Č Ń¨­­}@ĽRýN­VNKKÓX­VÂÂьX ÓéPPP  ”Śuvv.<ţü3›6mjńx<˙3cÌ?ł„0†0„SÖŹY3VŤŐžĄŐj'Řl6Mjj*á`I]ÂA)Ess3miiéóx<‡:::f?účŁűÄś‹‘<0™Ö­[WŞÓé~k0ŇłłłŹE%#Ţ „„Â4>ŸďşÓ§OńÎ;ď¸{zzÝwß}ĺĚkfDć!3˘FUU•]Ż×/UŠT75J›ššJŘş°°09şPJávťéšsçz{zzvtt<ö裏~%ś]ŒÄ„yČ ÁŠŠŠ™ŤŐjË CZVVó†’…‚””’’’˘őz˝?†ta‚Ě555ōfľN§—››ËÂҌ¤DŁŃ ¨¨Há÷ű'|űíˇ‡6mÚtşŁŁăąYłf˝/śm éÁ™1,ŞŞŞî2›ÍNƒÁ“››+S(b›Ä`ˆŽBĄ@~~ž< ćž>}zÇć͛[şşş<đŔŐbŰƐLaáršžTŤŐżOIIIĎĘĘâŘú0ƒń}d2rrrdYYYégϞ}kăƍ‹=Oĺ<đKąmcÄ?LW¤žžţJĽr^ffځ•-1áÁq233IFF†ĄĽĽeá–-[Jűúú–L›6íbŰƈ_˜›Ă’ššš…›7ov3ćW“&M2ŚĽĽ11f0†I(lâĉ†‚‚‚çślŮŇ]WW÷ąí’O=őTŚŘ6Äv…e\BMMÍCZ­öľ´´´”ĚĚL&Âqk "=ÚÚÚčˇß~ŰŮŰŰűŤéÓ§WŠmO<ňÔSOärů1BČ<§ÓšNl{bó€ÚÚÚ[7oŢ|"//ď­I“&Yív;c# ¤¤¤‰'šňňňœ­555ˆmSź!“ÉžJ)­*++›-ś=ą‚­!'9˗֦¤¤ŒÍÉɑąd-#úB0jÔ(’–––ŇÔÔ´rÆ /ű|žGf̘ąMlŰÄŚźźœkjjzöâCĽô+Ä´)V$œ ?ýôÓŮ~ż˙.BˆŔyNWý§?ýŠMlťâŐŤW1™Lufłš //O.—'ÜGÁˆ{8ŽCVV—™™™zňäÉwĎľśśN}ěąÇöˆm›X455Ý  ô˜ňż"šSć*ěp8đŸ@`ÔŔPkOOĎk‡ăe›Ä˛/^Řśm›şˇˇwłŮlžœ——§`uÄ †řp‡źź<Înˇgœ8qâłúúúOO:uGYY™GlŰb Ľôš×đÓmmmӞX"ůřäŹYł”‡c7€eF]ć°<‡ăĺXُÔÖÖţ ĎóÍăǏ˙ńŘąc™3q†BĄ@AA|„ 7ś­_żţ%ąmŠ%‡c!dJč1Ľô˙VWWűÄ´)–HZg͚Ľ7›Í'L ó”gKKK˙;š6Ĺ#k׎˝aÓŚMgGýť‰'ęYżi#žŃh4())Q<ťyóf÷Űoż}‡Ř6ň_âťęŸNBHReĄKZÍfóg„ôOő(÷ů|–ŠŠ !ä1§ö˲˛˛âŘY)UUUúĆĆĆ]vťýý‰'Śgdd°´iCB\Źa6çäälܰaĂÁÚÚÚôŤŸ%Mć͛— ?ăœňJEEE‡ˆ&ĹÉŽ!ϛ7ďxž/đÔaˇŰ=q`xĂétްjŢźy <Ď˙.ÖňźŕśŘZ[\.×_t:ÝScǎհ éÂqrrräéééăŽ;öË媛>}ú,ąížçŸşXy)ĽIˇÄ(IŮáph)ĽĎ…BÎfff–\n­áľ×^ť@Sč1Ľôú˜) .—ëžĆĆĆÖącÇ>=aÂ&Ć F‚ RŠP\\Ź.**zh˖-]ożýöĎÄśI(,X P6ŕŠĺgIJG,$)ČĘ)ĽýŢ=Çq[^^¸Ň ”Ňßx¨‰še"át:ľ ť322ÖOš4)Ĺjľ˛đ4ƒ‘€X,L˜0AŸýćƍż¨ŞŞ’|RH__ßĎđ]R.O)VŠÓüůóóg͚%޲Ř"IA&„<8ŕá‘E‹]ľfňvč˙”Ň`T  —Ë5ť  ŕ\qqń¤ÜÜ\Ö܃ÁHp!ČÍÍ厚ćškSSS›kkkŸ˝úYń !dŕŚoWVV~ÎyóćÍËs8Î`0x8%%eĽĂátéˆ$א)Ľ™ţż#œs!Jičáip8wSJ˙^&“Ů‚Á`3!äŻRŞUv:Úźźź)))“rss9Öę’ÁH.Ôj5JJJÔ§OŸţsCCĂÂ`08yúôé-bŰ5ćΝ{;€kCyž˙ŸŤ3gΜ\™LöĎ<Ď?…‹:F)}˜˘}â‰'f-[śŹ/zGI 2!DW™LÖć9O äý‡Ł€•žçqQĚf;ŽBČcN§ss4lŠşşş§T*ŐËEEEZ&á"đ cŘívÎfłĺ9räŰÚÚÚ?̘1ăßĹś)\8ŽëϢ”nýő×?źÜąsćĚɕË忥”>`°7ĚSJ˝2™,Ŕ‰čX]$)ȂřîŽč†0Žçxžw„Bî˝Âą6JicYYŮ N§óŞwjąféŇĽćôôô÷ÓÓӋ˛łł™WĚ`0\Hú*))QŸ;wŽźĄĄaŽßďŸ>Üëńx÷Ţ{ď›bŰÂápü@hYđ[cpÁzŔóř~TłŔ˙™¨5ʒôqańţVVV)Ć ߒËĺ˙J)ŐBxŽă\´hQÍĺŽŻŽŽö͙3çN™LśwŔÓ? Š 744Ź3™L÷°R&‰!“É.ů,źb1PŘĂa `ƒAţ˙ƒ U=˜°(•JL˜0AsňäÉĽuuusî˝÷^Ńť^ŹXŞő€ß]|np]u+€?ű|ž——-[W^žĐHŐC‡Ă‘ŕsžçńú믿Î9ĽĽĽgC^2ĽôTeeevTDUU•Ýh4~’ŸŸ?Ębą$ü{$U!Ëĺß_™,şý "ń…$$Ö!q v pĹ= éěě¤GmqťÝ7=účŁÇĞٴ´ôABȚŤv–Rúż …âľW_}upžOTxúé§łƒÁŕÁ‹­—cŠT=ä°šÚČÎ9Ç5SJÓ€ÓB󚚚§Fă˙Œ7NĂśGŒBâ;đ'ÚÂďB.{2PœC?,ü->FŁ‘Lœ81őȑ#6nÜř˙O:U”ň(ŽăžťÂçá€˙öů|‹Ł]Oěp8ĆBnĽ”Npk ú]iiŠ˝˛˛2Ś•6 /Č#Rjđ˙ÖÍÉmÚ´iŤÍfť%''‡Ĺ§EFĄP0ń€PŤT*źéHűý~řýţţd3Fl‘Ëĺ?~źňěٳ卍ł:::Žřá‡c˝çp€› üb}M)}I­VżöňË/{Ł1éüůóóyž˙1ĽôGîwšBČ—––Ú*++_Œ†-CÎŤ‰„ ´´tyee哸Pwf͚ĽˇX,¸˜ŠOyÓétţz˝%%%ú#GŽěŹŻŻ˙żÓŚM{!Vs;Î7ËĘĘ>r:GqąÜ)ĘËËĺ§OŸžŽr+€)MMM?ć8Îr•Ľ’ťźK)Í&„źľdÉ’ŚĄÎŠšsçŢČqÜ& čF)ýˇĘĘĘß d갑Œ*Ů¸tá˙áž;wî܇.Š1 '„ּ̰‹ńŞhˆqCCĂĆôôô?31Žă Ńh`2™`ąX Őj“BŒ—Ę 0 °Z­0 lyB!;vŹÂnˇżP__ŮíĹ ˘˘â Nś-S*•a;˜eee×rWKŰsţIL1$$ȋ/>A)ÝzL)UVVö@8çBţWôń<˙s§ÓůXf~††ㆠNܝ——'™×;!„@­VĂd2!%%:Ž]|ýB RŠŘMš€Řívލ¨hň† ÎTUUĽˆmOˆŠŠŠ!„ Ź.ây~×üůóóŻvŽĂá˜@)}7JéËĎ_ᴘ )Đëő?Ç/™Rú§pÎŤŹŹźŸň€cłé…ŧł~Ÿ™™ixýő×mź^UU5IŠTž.))ÉfÉ[#G&“A§Ó!%%z˝ž‰0ăŞČd2hľZX,˜ÍćţŇ*Ćđ1™L˜0aÂ(‹Ĺr˛śśö:ąí át:˙8H”ó‚Áඋ{,IYYŮXŽç,ŠŹŹ|.ZvɉDYYŮ&Jé]ĄÇ„ťNçćጱpáBťßďWţĺ/9.ź…pš\wëőúÚââb5 QŸP(R­V3[C<ĎĂëő˘ˇˇ—Ő8J)<čďěěœ;}úôbŰÂáp<ŕe|§gg)ĽwNčr89vČ =G)]aˇŰŸ,//‹„ä”B­V˙|—ű—áŽń§?ýét4ŸĄĄá‹ĽîškŽab ŞŠŠ)XZZę"„8***ŽZŒŢÓÓó„FŁ9ƒ‹~0t¸6Ęć^• 6ÔgffNÍĚĚdnq˜(•JhľZČĺ’(‰ż*mmm8räŽ;†Żżţ§OŸFss3şşşânÇ#žçáńxŕńx.ůÁČd2˜Íf¤ŚŚ"556›­˙˙&† L&ƒ^݇VŤE__z{{ăę=ŠWrrr8•JőȆ ň~úӟŢ$ś=@+N e Ďó9Ž; hŔĄÜn÷#ŐŐŐq%ƀˆňÓO?=5ÔăҰ9O)ÝÉq\ŠÓéúčŁńĺĺĺqąvYYŮtJi5tŢŔŽŢŢŢŠoźńFOŹí Ńܒ@ đřOs„)”ŇŻÇ2™lţĺBф'œĂw‚î09Jć^–ŞŞ*}ccăWEEEƒáę'$1Ą˛%F#Š 6Ďó8qâ>˙üs8pǏǙ3gX# ===8~ü8Ž˙.%C&“Ájľ"77999°ŰíHMM•D؛ŇߎÓëőÂăń°]¨Ž€Ĺb!ăƍ+Đ´téŇÂ'Ÿ|˛]l›œN§Ť´´t!dpŐĚîxc@ä5ä‹[ţŽRę „\Ž'é9Žă~őÚkŻ˝1řĽĽĽ5„ĄÇ”ҢĄz•F‹ÚÚÚt­V{hܸqfÖóňB ŐjĄVŤ%qQƒŘˇovíڅ={öŕäɓ˘ˇhŒW9\Ôj5ňňňŸŸŃŁGcÔ¨Q’ř,`Â^ŻěěěěœřŕƒžÓ‡ĂĄ°Ŕ­ƒ~ĺ%„<ât:kE0+,âćQZZú4!äײ.sH'!ä%§ÓůŻĄ'žxâ łRŠlÁĹE|J黕••?‰žľ@MMMąN§űäškŽŃ&k&đŐyÄZ­6Ž/ž<Ďă›ožÁ‡~ˆO?ý_}ő<Řf]‚Ôy0*• YYY3f ‘‘ן€ óŐđűý8pŕ@ďůóço|ěąÇöˆaĂ… 5§ŽRzűeńB~ćt:×ĹÔ°0‰ťo€ĂḍRúBČd mŸ‡ňf[[ۂęęj_YYY5ĽôĄ‹żŁěg˘iăʕ+'ŚĽĽ}TRR˘–ęh´ qź†Ś=śn݊íۡăĐĄCq'ŔƒI4AŒN§Caa!ƍ‡‚‚‚¸näŃ×׏ÇĂ֘‡€çyěßżßŰŮŮ9ĺţűď˙(–s/X°@ĺőzkLđô~BČo(ĽË„ž~JiI,ŁŠáw‚bá…öîîî—9Ž›N)j­ŰO)­ăy~ĄL&;†ďźä†ĘĘĘiC/UUU“ŹVë×\s ă!ˆçd­śś6źóÎ;ŘącŽ9"Š=v]Âq˛łłQRR‚ââb ü›â…Pň—ÇăaYك ”bßž}ŢŽŽŽŸÎ˜1c[,ćœ5k–ĚbąŹ0kŔÓGŔ”%K–49ŽŘŔ ŕ7aď…KâVC”——Ť›ššţ`..m" €ď2ęxN—ú§?ýŠMh[ŞŤŤoNIIŮRRRÂ~ BŠTB§ÓŝŸ8qëׯÇ'Ÿ|‚ŚŚ&Éz5É$ȃÉČČŔřńăQRR‚´´4ąÍšJ)<OÔ˛ëĽ Ďó8x𠯧§çŢiÓŚ Ť“âHp8 JéJBH(Zz\.—OyőŐWż SZZ:‰ă¸żu:Œś=#%îy ‡ă1BČżSJŻÖ‰×˙ IDAT@źş˘˘âa!çnhh˜˘RŠ6•””(™G¨Ž3žÖŃ;;;Q__-[śŕřńă qĄLfAHZZJJJpÝu×ÁbąˆmN?<ĎŁ§§‡eŢ€çy:tČ×ÝÝ=cÚ´iŁ=ßE/y1€;d2ٔE‹}í9…FRÝ***VXYZZz€—.ˇÎLtW’ÚÚÚŰ´Zí†ńăÇ31žH(s:^CƒAlŰś őőőŘż?¸(‰dĚůóçąuëVlŰś 999())Áľ×^ ­V+Ş]ÇÁ`0@­VŁťť›%~áÂk2~üxĺĄC‡jkjjţîţűď_ÍůŞŤŤƒĺĺĺO={6cѢE§Ł9W´”‡<˜'žxÂŹRŠţH)}ß…ŹżŹ¨¨˜$Ô.—kşÁ`XS\\ʌ÷,ĐXĄRŠ Óéâ"aëË/żDuu5>˙üsôő…Ý}Ur0ůňČĺrâ†n@AAA\dk÷öö˛őĺ‹PJqčĐ!ssó>řŕJąí‰g$ĺ!fٲeíĘĚw8˙J)˝š˛˛ň§W;/\ęęę0 ŤÇ݈‡/šŘČĺrčőzŃŰ\ú|>lذëׯÇ7ß|Ă.zIN ŔpŕŔF\ýő˜*•Š…ąqáfrܸq Žă–š\.y<íoHZŔWTTüBřöŰo˙Ěd2-/**Jz1ć8Žżą‡˜œ>}+WŽÄŽ;ĐÓˇÍv"ŇŮى­[ˇâÝwßĹřńăqĂ 7 ??_Ży`ť§§'Š—Q!(,,T|őŐW‹].˜(M˘˛ ÔÖÖŢeąX–11?|˜­Í1Â"Ômmßž}HKKĂäɓqÝu׉˛›˜BĄ€ŮlNú06!EEEňC‡˝îršÜÓ§Ow‰mSźÁyuuu%z˝~}QQQRŻs˝^/ÚvˆÁ`őőő¨ŽŽFSSSŇ^Ä‘sţüy¸\.lٲ×_=nşé&ˆŃw>´˝hhçŻdĽ¨¨HqŕŔęÚÚÚťg̘1˛ž&ČXťvmŽZ­ţxܸqI]gŹRŠDŰ×ëőÂĺraÝşu8{ölĚçg$.;věŔűᅬ’’ÜvŰm°Z­1ľ!´•e2{˄Œ?^ľ˙ţĆŐŤWßđ裏îŰŚx ňE\.—MĽRíżćšk4ńÖÜ"Vˆéwuuá­ˇŢB}}=:::b>?#yřâ‹/đĺ—_˘°°ˇß~;ěv{LmyËÝÝݒę'ÇáškŽQƒÁ׎]{ŘRÄ Lqa E•JuäškŽŃ‰A,jľ:.ć^ąŰíƲe˰uëV–¨Ĺˆ)”R>|_}ő qçw"###fóËd2˜Lڤő–9ŽCII‰nďŢ˝{–.]š[7ŠMrŞĎŞŞŞ”&“éëńăǛĹZ/ąźbÇƒęęjÔÔÔ0˜!*ƒ…ůŽťî¨QŁb62Ż-Ëĺr)ĽÇœNgvYYY|ďňe’wĄĽ”3ÇŠŠŠRĹ.éƒPög,ĹŘëő˘ŚŚO=ő–/_ÎĘ7„„ů•W^ÁęŐŤŃÚÚłšCŢrźtž‹%*• ăǏOÉÍÍýşźź<ŠÄ¤ţă7nÜx¸¨¨(KĚbĄŐjcÚnĐçóaóćÍxë­ˇpúôé¤ Ď1¤ĂĹ݊pđŕAüŕ?Ŕíˇß“ŹlBt: şşş’ę;˘ŃhPTT”ÎóüQybŰ#I+Č Œ;vŒĺbjVŤÍ (Ľx˙ý÷ątéR|óÍ7’Ým‰‘|ƒA|ňÉ'řâ‹/pË-ˇŕ–[n‰É÷FŠTÂbą ŤŤ+Šžôz= s7lŘđńOúÓżŰ1HJAŽŻŻ%++k˛ÉdJŞBăX—3:u •••ř裏ŕóůb2'ƒ!4~ż[ˇnŧŸ~Šťîş “&MŠúwˆă¸ţ„ŻdJv4™LČÎΞžžž~ĺ´iÓ۞X“tkČ.—kŽÍfsddd$B`0`0b"ƝXşt)ž{î9ěÜš“‰1#!čěěÄÚľkát:qęÔŠ˜ĚŠŃh`2™âb#—X‘žžNl6ŰĂőőőωmKŹIžw@UUՍFă+ůůůIÝiŤTŞŤ!!Obá…řë_˙ ˇŰő9ŒXsęÔ)8NŹYł&&Ţk(ů2žö6ůůů2ƒÁđ_ŐŐŐ7‹mK,IaŞŞŞJąX,;ƍ—4ľM …ƒ!&w×Äoź={öŔăIęĘF@)Ĺ_|C‡áîťďĆ 7ÜŐčSčĆş§§˝˝˝Q›'ž(**Rú|žÍľľľů3fĚ8'ś=ą )<äňňršÉdú޸¸X›,ĄľZ“PWww7Ţ|óMüŰżý>üđC&̤˘ŻŻëׯÇ믿Žććć¨Ď§Óé ×ëŁ>O<ŔqŠ‹‹ľ*•ę`UUUR8RIá!Ož,]şű÷ďgëȤćĉxĺ•Wpë­ˇbʔ)ˆfű]ľZ ™L†ŽŽŽ„ŻZP((**˛>|x€Bąí‰6 ď.ş\.W^^ŢŘd¨5…ľ˘-Ćnˇ•••řĂţ€Ýťw31f0pĄGöÖ­[ąhѢ¨'}…Ö•“ĄŐŻ^ŻG^^^Á† 6‹mK´IhAŽ­­ýWťÝ~ÍfKřŒęX|AyžÇűᅬßýîwl7&ă2œ={hllŒjq,6ĹĆjľ’ĚĚĚŰ].׉mK4IXAŽ­­˝Őjľţ6;;;a˙ĆJĽFŁ1ŞëĹííí¨ŹŹÄ˘E‹°gĎx˝Ţ¨ÍĹ`HžçąsçNźúęŤhjjŠÚ<Ą’Ćdˆfffr‹eáşu댈mK´HHąjhh0ętş†Â„çh4Ć¨fxîۡüăą~ýzÖö’ÁÍÍÍp:ŘşukTż7&)’˝ĆŽ+7 őUUU)bŰ R°8Žű˛¨¨Hë­cN§‹j3zŻ×‹7˘žž_ýuÂ'0Ń  bëÖ­řć›ođĐCÁh4FežP˛WgggÂŢ4s‡qăĆéöďß˙9°çuÂyČ Ťóňňry÷ŚP˜*šb|ęÔ)źôŇKX˝z5Ž=ĘĘÁˆŻżţ/˝ôöěŮľ9 EÂwöRŤŐČĎĎĎŠŻŻŻŰĄI¨w­ŚŚćŤŐú ŐjMXטŁŃľDžçąeËüůĎƻヒs璢ŸÁˆ }}}¨ŞŞÂŰożľęš\“ÉŐŇ+ąąX,ÄjľŢťnÝş'ĹśEHFkkkӍFă˛ŃŁG'dř.Ť2Z-ôzzz°|ůrźőÖ[Ř˝{7kňÁ`D‰Ď>ű Żžú*Ο?•ńe2Y¡ŰĚĎϗ™LŚWW­Z•-ś-B‘0‚ŹRŠöŒ7N¨ëĆĄ/X´ĘšNœ8—^z ,DÍ`Ā––8Něßż?*㇢i‰*ʄŒ?^’’˛›RšZ–D}}ýťЉúÁ“ÉdQ]úä“OPQQ?ţ8jwě ăűx˝^Ź^˝őőőQš NtQV(3fŒuăƍďŠm‹H^].×ó?NÔ˝Ł)Ɓ@ëÖ­ĂŞUŤ°{÷nttt>ƒÁ¸2”R|đÁXşt)şťť?$ʉÚ:Řd2!==ýćşşşßˆmK¤HZWŹX1Ú`0üG˘6˙%gDCŒ[[[ńÚkŻaë֭ءoúúúŸƒÁ`„ĎńăÇńꍯâŰoż|ěDĺěělNŻ×˙vőęŐcÄś%$-diii&ä',šb|ěŘ1,Z´ťwďĆÁƒŸƒÁ` ŸÎÎN,^ź_~ůeTƏf…†Ř)ÍfóbŰ ’äşşşŞÜÜ\["Ž„Ä8 jŸţ9–/_Ž}űöářńă Ű@€Á*@kÖŹ‰Zw/ƒÁ˘,—Ë‘ŸŸo۰aC•ŘśŒI ňÚľkoHIIš?7ö -ƔRlÝşkÖŹÁţýűYňƒDŽžŻëÖ­C0|üDe‹ĹBĚfóĚuëÖýXl[F‚ä™RʙLŚMcÌI<×s}WşăŢ´iöďߡŰ-čř #:ěŢ˝K–,ź'Ďó ťT5fĚš^ŻŻ///—\O É rCCÎѣG›ľ5\0DGG‡`%˖-ĂÇŒ˝{÷˘ŤŤKq Fl8qâœN'ZZZçytttDĹóŽ!(,,4Ţx㍒+…’”Ş­[ˇîÔÔÔŁŐœ],‡§…喖źöÚk8xđ öî݋ŢŢވĆc0âĐÚڊĘĘJœ:u*˘q.'ƉÖPI§ÓÁfłÝčršf‹mËpŒ ;N­Ńh|#///Ą´Ęd2X,–ď•#D*ĘgΜAee%Nž<‰}űöE­o.ƒÁˆ ===X˛d Ž=:˘ó/'ĆJĽ)))Që(999œFŁY´téRłŘś„‹d9''çłÂÂBm"ÝÉqŁŃŘ˙ŻP˘|úôi,Y˛§OŸĆž}űŕ÷ű…4›Á`ˆ„ĎçĂoź ëź+‰qh?uŁŃ˜PRBPTT¤ÍČČřPl[ÂE‚\[[űŤĚĚĚÂhn7k†ú!ĘǏǒ%KpćĚ:tˆő¤f$ ŁGĆíˇßžŮÁĂ! bőęŐŘ˝{wXÇ_MŒC tľZĚĚĚąRéâ÷ŻüŇĽKÍFŁńw™™™qoëp0 C†ˆ"ĺÇcůňĺ8wîcFBAÁÝwߍ˙řÇxöŮg‘šš*śI˘Âó<Ö­[‡>¸rŒ+‰ąÁ`řŢń2™ŹßcN2228N÷/R]ǽȼ§§ď,((P‹m‡čőú+ś°‰(ďŮł+WŽD[[ăËŔq äryB]p’I“&!==ĐŰۋÖÖV‘-J)°sçÎ!51žÜw@.—)ÖRŚ  @=jÔ¨÷ÄśăjÄľ ť\ŽéiiiăŐęÄŃcN‡pţžáˆňţýűQ]] ˇŰƒ21!Z­6› ééé°ŮlHMMEzz:ĚfsB­™%* …SŚLéźiÓ&öżĽřěłĎ.y~¤b<ř¸DA­V#55uœËĺzDl[ŽDÜ 2Ľ”Óh4ÍÎÎN˜+ŚFŁÁpÖÁ CX˘\PPFƒƒ&lmáH¸ř%„ÉdúŢös„h4Řlś„m¸Ÿ(ÜtÓMýkß|ó͈łŒ•ÜÜ\”””ô?'+T*´Z­ śŠINN§RŠ^ç†!q+Č őyyy†DI0P(ĐétĂ:çr;´ ĺ'Nŕȑ#ĚŰťˆ\.GJJ ,K˙k ŃÓӃŽŽtww÷_Ź8ŽƒĹbI¸’DAŤŐ⦛npÁÜźyłČĹ999˜={v˘Űp×ŒŻ†VŤ +˘'!;vŹ~ňäÉőbŰr9âRíVŻ^]bąXî0™Lb›"ĄD‰‘r5Q6 Đjľ°Z­IííB`0`łŮžwjnnFgg'<şşşĐÜÜÜżĺ$Çq0›ă>ß#)šíśŰúßË/żügΜ٢ř!''?ţxŘb<Ňź N—07Źz˝&“éöŞŞŞŠmËPÄĽ [,–­ůůů ŃŤ:äĺFšDt%QNOONj/žƒÁ´˘ Oëőúţ×Úăń ššç{ýÁ)ĽhooďďçŤP(ĆHl6Žťî:€ßďÇťďJŽbԈ•ß]Ă%Z9fĚšĹb‰K/9î^áşşş˙“mK”đŤÁ`,”|%QśŰířőŻ ˝^Ÿt˘ŹRŠ. Oűý~´´´\ľTŒRŠÎÎÎţljTçžÜu×]ý"đá‡˘ŁŁCd‹âƒXŠqˆPr"T'p‡ěěě4—ËőŞŘś &ŽŮĺrŮ ĂüÔÔTéżë¸ęZŻ$ĘŮŮŮřŐŻ~F“T˘ěőz/i ęőzĂîNćőzű/dÉňzIÜÜ\Œ;Ŕ…–‘ďż˙žČĹbˆqš\˝^/ŘxbbľZ‰Á`xŞśś6]l[W‚ŹP(v3&!â†*•JPk §w%QÎĎĎÇ /źFƒ”””¤™ÎÎÎţ°ôpןBaëD ÉIPďžű.ź^݈Ĺššš#c!KĄžŽ‰IAAJĽRíێÄÍČĺrMˇŮlcĄ-žĐw’MMMxůĺ—ŃÖÖÖ˙ܕDš  żřĹ/ RŠ’F”ý~˙žąĄ5Żp …şYmk|0qâDddd¸°ËQ¸-"™áfS‡Ä¸ľľ/żü2Ξ=+˜-шü‰JĽ‚ÍfËŻŻŻŸ!ś-!âFU*Őňěě츹g¤•ÄÂívcŊhnnĆâŋ/ĺ+Ő)cá…I%ĘÝÝÝý˘ŞRŠÂJŇ uîŔvĊär9~ň“Ÿô?ŢźysŇ×Ö'L=đÚÓÚڊŋŁšš˗/t ^Ż×'DD);;›S(KĹś#D\ź˘ożývyVV–9Ţ`ƒÁ ŘŐëőâÍ7ßDww7 ŁŁăQžZrII žyć™ţşÜDežçŃŐŐŐ˙řj7FƒËzzz˘jăęÜxă•;~óÍ7řꍯDśH\†ťf"$ĆĄ¤ĹŽŽ.ŹXą˘żÔ/R8ŽKˆN^źĚőőőŰ šźź\n2™ţ1š4`˘ ąrĺJœ;wî’ç‹2pĺđő~đ<űěłI#ʏ§ßӕÉd—]:P*•°ŮlýŢńŔóâ ÓépóÍ7¸˙Î;ďˆl‘¸Œ4k°‡8wŞ[šQ( ŃÉËfł•JőěśmŰDĎ_]ŻżţzW^^ŢđZXĹ!rš\°'Ľ5558věؐżŽ(_ýő(--MQžR‚Çq0™L°Z­ýkÇ˝˝˝ßťx1bϔ)Sú—öîÝ‹ŚŚ&‘-ĄĹ8ÄW_}…őë× f§VŤý^[Z)’ŸŸŻíééŠŰQšśś6Ýd2Ý&őĐG¨C”PëĆŰśmťj"ËpEůG?úć̙™L–đ˘|š/­V‹ÔÔÔţ§Pr{{ű÷‡0b‹Őjío°mŰ6‘-h‰qˆO?ýťv —\,ä2X ˜LŚŰŞŞŞěbÚ!ꍨVŤˇäççK>­ZŻ× ÖücďŢ˝a_Œ:::°dɒ°Eů–[nÁĎţsp—đ˘<8Á+´ÉDčÂáőzŃÜÜĚ֍ă„;ďźł˙;ôŃG%mh‹qˆĆĆF:tH›e=9??_i06‰iƒh‚\SS3Ůl6“ş(¨ŐjUŞŐÜ܌šššaykíííĂň”ďźóN<öŘc /ʃźBaë`0ˆööv´ľľ%}önꐛ›‹˘˘"Öňß{/ ącŕBt¨şşÍÍ͂خP($_ŸŹT*aľZ‹jkkoËŃYŤŐŽĎÍ͕tL™L6ěœ.‡×ëĹʕ+GÔa¨đő•J˘ŚNŠ™3g&ź(NÔňů|hnnFoo݈V1BÁwŢŮ˙x÷î݂eK‰HK›F’É5g(aŠÜÜ\™Z­Žk~QšśśöŮQŁFĽJ˝_ľPëƔRŹ]ť6˘ťŐá–D͜9÷Ýw_‹rGGGÄAĄP°-*㌔”¤¤¤ô?žůć›q˙ý÷#Qvz ĄJ›FBss3ÖŽ]+X…Đí:c ÇqČĚĚLŤ­­ý•(ó‹1ŠN§ű}FF†tß5\Hęnpűöí8pŕ@Äă 7Ń롇Â=÷ܓТFÜÁ‹}Z[[ń—żüď˝÷!˜8q"ž}öYL:5áwŕŠe˜úr8p@°$/™L&ůR¨QŁFN÷/bĚsAŽ­­-ˇŰí’βÄéčŃŁ‚f”W”yäÜqÇ -Ę]]]ý¸p;x1bGoo/ślقW^y{öěĽ2™ ó7ƒ ŕG?ú‘äCĄCbbóćÍ8räˆ ci4ɗBŮívƒËĺú]Źçš ëőúçm6›¤˝cĄúTˇľľá­ˇŢź‡r(űÚív÷?w9QîěěÄěŮłqŰmˇő‹˛ÔżLƒĄ”öw;ŽŢÁ‹!¨ŠŠÁâŋqâÄ .îwÜqĘĘĘP\\œ0ď[<‰qhţ5kÖ ˝˝]ńîK.El6Ńh4żˆőź1äőë×˙>33SŇűw ŞćyŐŐŐQK0jooÇ믿–§ÜŮىÇ7ß|38ŽKČ­ĂíŕĹŸŚŚ&ŹXąŐŐŐý7•VŤ=ôfϞĚĚL‘-ŒŒxă===¨ŽŽÄA2áU,ěvťžŽŽî÷ąœ3Ś‚ŹÓéHŮ;2T˝eË|űíˇ‚Œu9†žîęę‚ĂáŔäɓ6|=0Á+2BJ)<ˆW_}őőőýyššš=z´Č֍œxă'NœŔöíŰK­VK:ÚfłŮˆZ­^Ë9c&Čľľľ°Űí’vK„ňŞžůćěÜšSąŽĆpEšŹŹ ×]w]BŠňŕŻDhfčƒA|öŮgxĺ•WđŢ{ďÁívăă?ŰŹďbbŰśm8~ü¸ cI=ëÚnˇëׯ_˙ąš/f‚Ź×럵Z­’}g„ U÷ööb͚51Ý{w¨Ž^—ŤSöxáD™RŠ––´ˇˇÇôڈ! RěŽ6Ň:cˇŰe˖‰˛ńI(ÁNÔjľ¤—‡˛˛˛ô6lřŻXĚAÖétóĽěët:AÂ.ü1öîÝ+€E#c°(—_÷ööâůçŸGQQQ‰2bFŹi˜ÚívŻR"ÖěßżŸţš cI9‰ŇjľĽR9/sE]ëęęţ[ĘŢąJĽDˆš››ąaĂ,ŠŒööv,]ş4Ź’¨žž>,\¸ůůů [Ĺ`D‹ěěěŻWVVŠ*Ć!ęęęĐÚÚń8rš\Ňő˙3Ž˙;ÚóD]5Íź””IzDŽAÖ?BűÇËڗŰíť÷ľßďÇ /ź€ÜÜ܄-‰b0„&33łgĎ‘g,V˜z(|>Ÿ`­5u:dˇi´Z­DŁŃDÝKŽęŤSSSóϙ™™’]Ń×jľ‚|€>řŕƒţFńÂPkĘWĺ_ýęW°Űí žf0„&33O>ůdމ”ÂÔCqňäIA2ۅrpÄÂnˇëjkkŁÚR3Ş‚Ź×ë)ŐľcĄšÜn7Ţyç,žáˆ2ĽżţőŻ‘™™ÉD™Á¸ ‰&Ć!/‰¨•J%Ůe/ŤŐJôz}TťwEMkkk§Z­VłTkЄHB ”˘śśö’-ăáŠň /ꀴ´4&Ę Ć F*ĆĄď`źŠ1p!t=Ü˝Ú/‡”źRRR,555ÓŁ5~ÔYĽRUdddHrÁ@¨ť¸O?ýGŔ˘čr9QüƒAČĺrźř⋰ŮlL”Œ‹D"Ƌ/Žk1ńő×_ ’u-diŹÉĚĚä´Zí_˘5~TsŊŁÍfs†÷žjŁŤŤ X†S§ŹT*ń /Ŕl63Qf$=É Ć!6lŘ H™VŤ•d/Žă`02׏Y36*ăGcĐÔÔÔ5YYY’ŹWŤŐ‚$rŐÖÖ˘ŻŻO‹bG¨$*Ô¸äráë@ ­V‹_|FŁ‘•D1’–ŹŹ,<őÔSĂăśśś¸)m}}}pš\C‘ěžÉŮŮŮrN÷V4Ć\N§VŻ×‡ŇýĽ„P’C‡áĐĄCX{Ün÷%ťD]I”őz=^xá…ţrVĹH&233ńřă÷××Ç3^ştŠd[ˇŽkŽGÂpD9++ Ď=÷ eFÂÁÄřRü~ż •$ …B’Ë\VŤ•hľÚ…BŽ)˜ Ż\šr˘Ĺb‘d#!îЎ;†`Müq9Q,ś@šššxć™g “ɘ(3†Ë­'Ť‡ŘˇoŽ9ń8R\K&„Ŕbą˜W­Z5Y¨1d‹Ĺ˛"33Sr)s …"bÁƒ¨ŤŤȢř$”č5°$ęruĘxć™gŔqe†äŞ´)œ5ăśś6źţúë +Ć!"ާZęmŽŽ IDAT¨(eŹÉĚĚä,ËkB'ˆ SJ9FS(ŋŽwfŸ~ú)š››°&ž •D…ž.,,Dii)e†¤Ş´)Ü0őŔšţDŚššŸ}öYÄăHŃKV(PŤŐE”RA´TAŢ~űí_5JrĽNršza?uOŸ]Z;-híHAk§çÝihjIG (šU'Á`b,,_~ů%ŚL™›Í6â1 ärš¤œžÔÔTrúôéßX>Ň1Fü-ŹŞŞJŃëőŠRKćÂ;~÷ÝwÁó#őX—pE9 âţűďĎóŘśm[RОŒăĄűĄTř ”ű @ęœZu/´ę^¤§œżäyžçĐґ‚Ś– œn…głqśmx^Zß푎+ &ĆaÂóŁQZ_RľZńŽN;v숸M\˘Ž(ó<Gy<Ďăƒ>H8Q–ËPŤźP+ú THëďQ)|(Ę9Š˘œŁ¸Ŕyˇ ‡NŒĹáoÇâäš,P*mď9##ƒ‰q ƒŘľkŚOŸŃ8&âRŞXb4ĄŃhîéů#Jęrš\6ƒÁ -5"Ţ︝ťŸţš@Ö$6á&zń<G}?řÁH?ŃK. ¨ëBzJ3Ň,-0jť$'ĆC‘fiÁ­“>@éôř§ż{ S'żƒTsŤŘfˆŒŒŒ!{S31–Ď>ű,␳NTŹŃjľ–+V¤äÜ 2Çq˙•žž.Š+ŚRŠ„LŮŢď˝÷^Bxną"\QŚ”böě٘4ié‰2!€FŐŤŠ i–fč5=q‰EŃkşńă‰ášYŻáé™KpĂřݒšé`b;@ÄÝť„XfŒ5ééé ‹Ĺň#9wD‚ŹRŠŚ †‘œ*‘&sőöö˛şăŽ(ŔěŮłQ\\ @˘,—`Ňu"=ĺ<,†v¨‰ľv8dÚÎ`ƏđÂcĆ}?Ţř˝„ąx‚‰qěů裏ŕńx"CˆDÜXIŘz؂ěršl:Î4’ÉÄB&“E|a˙ŕƒŕőz˛(ší|ÓŃŃŕň˘ĚqJKK1nܸţÇń(Ę yCŇ,-ĐińX!1mÝÇÄ8žŕy>âj!r‚b‰ÍfSřýţ…áś ŤŐęŸKŠwu¤Yy<ĎăÓO?Đ"ĆĺpťÝXślY˙ÝóĺDŮbąŕŮgŸ…Ét!PŠ(´Ý°šÚşłVźŁS÷ŕçűŚÝ´iÄk˙™™xꊧF$Ƌ/fb#>ýôӈöZç.“ÉľZ=;ÜăĂV,•J•*Ľ´óHĂŐǎ‹8źÂŸÖÖְꔭV+ćϟ˝^`ä˘,—`3ľÂ eĺlń!ŔM%Ÿ`ţý‹‡Ýíkpńp=cVÚ;şťťqäȑˆĆ’ sľZvŚuX ťvíÚL&“d*ł9ŽƒRŮΐl‰ŘăvťĂ _5 Ď<ó t:€á‹˛V݋Ts+riô_N&ŇSšQvß2ü°č˰ŽgajéńŮgŸEtžBĄˆx_‚Xb0´ŤV­šÎąa ˛JĽúw›Í&÷8R1îëëĂĄC‡˛†1Â匌 ̛7Ż?Ž(´Ý0ë;XuŁTř1óÖ:Lťi8îňďcirřđáˆ-Ez%ŠŠŠœŃhüˇpŽ KdŐjőĽTiHăË/żdť:‰H¸˘œůóç÷żßWeB(RŒn˘–7•|‚Ÿ˙í[P)žß”…‰ątáy{öě‰h )…­Őj5”Jĺőá{UAŢśm›ZŁŃH&›‹qć- W‹O¸‰^ššš˜;wn˙óC‰˛Œăa3ľAÍşmIŽąYÇŕ¸oĹ%ž˜KŸHĂÖrš\RakFcÚśmŰU˝ÚŤ rGGÇ|ŤŐ*™ř@¤wNçϟÇéÓâ7Äg\HôZźxńUEyěŘą(--R”ňll˝XҤ§œGٌĽ°§žąwtt$­ŰlśţrÁxáÜšshjŠlG0)…­­VŤ˛ŁŁcÎՎťŞ ŤŐęyńöf^‰HߤHďÜÂŽ(âńÇď/u ‰˛ŮĐÉJšƒś÷ßú.ž|ň‰‰q2–6) L™2‡÷Ţ;˘íyŁJ¤‘H)…­- 4ÍłW;.AΐJš!$"AŚ”FźśÁžpEš¤¤O<ńÄ%˘,S!”Ν4chZ:ł`.܎˙Çޛ‡GUĺűŢß˝kHUR•ŞL’0$Ě Dć!„AQ† @TTZO{Zűœűž{Ÿ÷úŢűž÷žˇűاOŰŇÚÚ*bŰęąťą°DQE™çĄćšöýٍ"C%ŠŞľŤö^Uëó<<ŠTíúĽ†ýŮëˇ~ëˇŇÓ•őLĆC3iŇ$ěŮłeeeP*•(..Ć´iÓ¤Ťß}÷üţŘ÷§)m­P(––V0Üý†4íoź1I§ÓĽ‹V|!˝bŞŤŤƒĹb)†˜D*ĺ3f`˖-˝¤ŹϤL5ć"d”œ„6#°ó“ńŕdggăŢ{ďĹÝwßj äÖ[o•Uš×nˇăęŐŤDǐÓß3éééożýö”Ąî3¤őzýÉÍÍĽăäoΚsçDŠ„"•ňÍ7ߌťîş+tbfRŚ&ăČPŤŐXž|9vďލńăLJnżző*>űě3öłK–,‘*ݐžsiJ[çĺĺ)ŇŇŇ~>Ô}†˛RŠ\œŻ‘;¤ŐՂ ŕ… "FĈ‘JyŢźyظqcčgžWWO‚×GU;ö”†É82&Mš„]ťvańâĹĄŽÍfĂ;ýű÷ăčŃŁ¨ŻŻ̟?šššR†Ű‡óçϧ­i™RÍČČ@ZZÚĘĄî3ä_˘VŤ´4ňVŠTDMÇëëëŃÓÓ#bDŒxŠ”/^Œ 6„~ćjđęÉLĘŔd<<ŮŮŮزe îžűnF×7pxöŮgqöěY‚Ađá‡Âď÷CĄPŕśŰn“8ňëX­VÔÖÖƒ–´őľ>ÜŮCÝgP!żőÖ[ŮÚ`sX }SΟ?/R$ŒDŠ”—-[†[o˝5ô3“˛üa2•J…ĺ˗c׎]˜0aBčöÚÚZüţ÷żÇÁƒátöÝšŹĽĽ%´‚¤¸¸“&MJhĚCAşĹ-M{$k4íŸ˙ü繃ý~P!ŤTŞŮŮŮĘř„%>¤o 2}tvvb˙ž˝°ô\0¸”oťí6Ź\y=SĤ,_ÚMąÉŘŇýţ¸˙™¤—ń”)S°gĎ,^źJeŕôlłŮpŕŔźúęŤhk|cŽŁGÂápĘĘĘdłášsçˆŇÖ´Œ ''GÁóü? öűA…ŹŃhîŁeťE…BATţŢŘؘô_äd„ă€e7ž _ăXMCKyíÚľ(//ý̤,?:ĚEЍ^ĆćŽŕk*Ă-ł^˛÷5ÍÓӕ••ާ8€ďžű‚0ôßîp8Bű5 ůůůq‹7ŹV+ęęęb~źÝ…^݇VŤ­ě÷C iYăEz…Ä6’ “%Ľ'1˝řŒşvx–ŔŇx“ňúőëąhѢĐĎLĘň!Ö4ľĽű<üÍe0ę:1Ąč –Í:žđŘă‰JĽByyů€ôt}}}¨ßţŠ+".lę.))7X.]şDôxZ„|m;ĆAŤęž‹‚ đFż°Ä…ôÍ ÝŸ“‘xJ jąröŃĐĎF];|K‡”2ÇqŘ´ićϟşIYzHděkZ ŁŽ3tŸňYŸaŇčˉ >ŽLž<ťwďƒ%Kú¤§ßyçźňĘ+řüóĎś#˝éŚ›":fďÂŐ`!˜ =ӔśVŤŐéĎ?˙|ŘĺKa…üÖ[o­ÉĚ̤â EšŽ°ŰíIÓťzΜ9X°`AܖČ%c˘Oˇbsůđ|ßy§HĽ\YYŮçƤ,ćBŃd vőÚ\~ٙtݘXľjîşëŽ>ŐÓ§NęS=}âĉЎhĺĺĺˆd‰ŞNw}œE2o+6---0™L1?žŚĺOFŁQ™››ť1ÜďÂţz˝ţ'YYYň˜ńŇĺNUUUĂνЀZ­Ć-ˇÜ‚UŤVá‰'ž@iiЍÇ×étxüńÇC­ř¤BÁű°eŐۃnŁhÔľĂ۸tČB/žçqß}÷ać̙×ocRN8‘ń1ŚŠĘ8ˆ6͉ťWüJ…7ěďiŕ… ĄóR}}=^|ńE|řá‡}ާ˝^/<Đjľ¸ĺ–[†=îěŮłC˙ŞL ŞŤŤ‰OKÚÚ`0pZ­vG¸ß…˛JĽ*Ľe˙c–ްjŐŞĐVŤŐ˘˘˘{öěÁ¨QŁD9~yy9222PVV†‚‚a[˛ĆĺłaôˆĄ3Yşvř‡žSJšw_&ĺÄóČؐ^ĆA r[°ňćŁq‰=444ŕäɓřŰßţ†W^y---aďWUU…ü@ mě7Ţ8č1§NŠ <ě!ĽJÚZ­VC­V‡]wVČjľšŽňj Y\žœóMÄW_}Ő' •››‹;wâÁ$š/ĘĎύ¸/]şDTI¨œ,žńED÷$}­T*ą}űvL™r˝˝,“rüšžŚ €ú(ÖŻ_Sď×UŤVçyřý~9rDʐŁBÁű°iŮťPđ‘a#•ňC=Ô§˙/“rüH”ŒŔ|ňK߇JIoę:ŹV+ţú׿Âçó…:qýă?ţ#îťď>ěŘą?ţ8ć̙z-O:Zţ$'œNg¨Ĺg,đ z›Ív‡Á`GőÎ0Îe&Kşş7vťožů&žţy´śś†nçy3gÎÄO<˛˛˛ˆ?¸ăǏ-8sć ÚŰŰă÷p,›uůŮŃĎyĽ<Ô:e•J…Gyăƍ ÝƤ,>‰”q\C'Ęg#^ć\ž|ožůfhԛ‘‘’’„žënˇ|đ>üđCŮÖ͐ž“i%F…RŠPŘ5ଜžžžąw%žœ!}ńkjjDŠD~´śśâůçŸÇ믿Ţ'  T*QVVQáÇqXąbŔĺráŘ1iNl#ł[ątćç1?>¸NŮj Ěľ…“˛Z­Ć#<‚˘˘˘ĐmLĘâŤŒ­Ś‹1Ë8Čâ_ 0Ż)öŕ)Ąşş{÷îĹɓ'ŃÝÝ żßŻ×‹––=zżůÍopúôiŠĂ’+WŽ=ž!_krW˙Ű 1yžŸL˖V$/žßďGCCƒˆŃȓęęjüú׿ĆÜšsQ^^*|~-Y˛ůË_ĐÔ4đ„5sćLŒ9püřqŘlś„Ć<ďÇĆ(SŐá0ęÚŃݰV‡Î01$ełŮj° Őjń“ŸüżůÍoĐÜÜx~…PO†×} J…‡řďIE2ŽžšÚÚsŢĆ%D2žpçŇ÷°÷Ż;ŕóӑҌ›Í†?ţü18Ž“íHx0áńxb>ˇKš$T*”Jeq˙Ű|:ÓŇŇčƒěĹonn†Ëĺ1ů"žüňKüň—żPř•;v`űöí} żT*ĘĘĘ‹E˛9§›'ƒQ9­Ăß1˛tmđ4,r¤ŹŐjągĎŒ1"t)ÇNŹëŒ­Ś‹đ6’Œ{“ŸÝŽ9Sψr,Z MĆ@`)I_ĽR)›ÝĂĄVŤőýo dĽRIĹO¤/<é–_4,üzúé§~áąÇCEE4 ,X€`/óLJF‘‰D­rcůlq[!féÚáiX<¤”őz={ěą>űĆ2)GIšÚ۰F]‡¨ńʘý´iňŞ,f „ôÜLŃ(9­ÇŽ>Bţӟţ4O§ÓQńאžčŠ(ä ‡ožů&öîÝŰg}#Çq(--ĹO<… tĐůţűď%‰łlćçĐiĂ7!!R)?účŁČĘĘ ÝƤ9r“1hÓXZzRôă2ąôÜLËK6Ł…–”uzz:”Jĺüޡőrž\6 Žăˆ6:HĺtőPœ9sżüĺ/ńĹ_ŔëőâŔ’Ä1*§3'žKČseéÚáŽ_Ť)°ţ1œ”GŒ={ö@Ť˝^^Á¤<šË8Ȍń? 0Ż9!Ďň ’s4ÇqT4áyjľ:ŤĎm˝PŠTTTX“^4¤Âr§Xńů|řűß˙Žýו,­ż´ô8.qiňlýđŐ×xôŃGűt:cRžN§…ÇKKc_×Έ?$ťzFÉJĽ˛OJ:$äˇŢzK§VŤŠX€Lúb‡[sËč‹T[łeé{0˝˜lłň˜ž7‚ôuaa!vďŢÝçó'…”ÝpŁ‹ď@#_ZőU|™wrę霯&ŐŞŽ ‘ŻGßWßi)Dz122mÜ%ädv%üy‘ě+´šĽuď¨Ëôz˝üóŐ {ąý~?Ńü#ž,žńĺ€}ŽE–ŽÝ ‹Ż5™śyȸqă°gĎěÝť7$œD5ů5~64ĄIß  ‚KĺF˝łé'<ÇĄ&ëB¨ŒÂHŒŔcq‹ˆN>׋f|‰żżM’çg MGGGJ4ŃétŠŹŹŹĽ˝FČjľú–ôôt*VT“źŘÁ7š!?´i̚xVŇ")—””`çΝ}ćŠ1Rž€ďщΐŒ‡Â?:Ёó8ˇxb•ąÍ|îúe’É8ČM“ÎĆeYƒҁ ľP@ ŇZĽR•î-䅽‹Vä ɋMš aď7œ†Z%ýĹR$Ržî3ŞÓÔá˜5IšÖ°ŒĄimm%ZíAĂT*UČ˝<źőÖ[j%ýĆH„ląX`ˇËkĆôé”\•:ŒA JŮfźyČźyópĎ=÷$­”ť,ůHsZ]`ǸHel驒ľŒ xÔUdéMR‡Áč‡ÇăAWWěKÓhhJĽRőÔSO)kBöů|ł´Z-ѓźČŇVu2Â3kŇ9🟡ŠËŇľÁ]?tĄ×üůóqçwÂçó…nK)wZF mĚ ¤ëKD72ö4,‘ľŒ@ِăÓŽąożIKĘZŤŐ*ŚM›vpMČJĽr6-B&y‘IŢ\Fü(OGĘ0’ôuYYŮ)÷(,hUëŕńёBëM›%żSŽ÷3ż„!iŇÔý™5é;PP”›r ˘h˛FŁáý~˙,๐ÓÓÓi4ňŸB&íQʄ,? 󚐟-ŻBŸĄˆDĘĺĺĺ¸ýöŰáőyс6XaOÁĄM­§JĘm–<=fş3U8Œń;á?ĐaęH:@ŽĄ …šR‡Áčé9›)k4ddd,Ž ™çůé˝{ôĘŇ— Y~”Nˆ_ăŠxH_/…Őt@x)/ť}4k”° סľ JŮK”[Lyř嘕čÉT‡n;ĺ9‰ßŠ 'ç Ý6PĆUpËliS¤ĐřYLvHĎŮ4Ě#k4(•ĘRຐsh¸’`BN>&Ž–:„˜,‰Zv—(7çÄóÚß@Řě†á6]ŸB/Ÿ‚CŤĚGĘm–üű¸r˜3Ż_`¸Ýnř—UxAű[¸á#ă¸ę– KfK›"e"ĽŸĹd†´î‡Ż)•J(Š<ŕú˛üóŐ ťÚ¨b!>Yzr ô^$Ň׋űH9=3ű3^B˝"°}\Î&2Ë3HYŽéëvk MÝ{d”qŤŠüQ÷223úɘŽ4ur ]ČÎě–: F/z÷‘„ …B \˛BĄP}wy@ňⒾą ń™XD˙ˆ¤ˇ”ýđăî9ÔhŞŽ_Ÿo2ŻT*§Đ°ŻrrQ˜—<óŽáNĘźšĂČÇs ™¤N¸”™Œ‡Ś0lŰ?†¸¤ÂYĽRAĄP”điii㘐‰Äa†Nk•:Œ¸3¨”ŻM˝pj#šő8U¤Ěd<<;Œ:Vs"Rd„ŒôôôŔHÖj‘6aB–Š” HůđŔęëkRćľ< žĚƒzlüĽĚd9šl”,œN'ÜîŘ @i˛RŠÇqE<ÇqŮ4läLň˘:Ž ńŇQ›:B€<]çĐRNç0ęs *T†ŻžAĘLĆёěS*´A2 ")NלËó<ŻĄ!`!Űív#aR›z'ť<]'ţąî –ëÝâzKY‘Š@ÁĎó 5PĘ­„K˘ZÍš6Šˆhi“É}miSjÉH˝‹FšcłŮ†żÓ Đ0Bćy<ϧó<ĎËdW9N§sř;1FNŠö ÎĎěŔW!eOćB™7PĘ-ę̘žSŕ<=nLúč–6e™Üx˘ţpŇ.mŽTýŒĘ’s8 B-Źy…B!˙|5ȄěršDŒ„Adž.˜ÉĎěŔĎë šžVd)PđĎšPć*H9Ž‹ZĆFł?Ť?Œź1čĚŕ8ař;2B*œĂ9ŽSňä_Ń6BNtZKʡ&nNY™­Ŕ¨'sĄ0ö•2)lÎ8rźútV*H„LÔ,p§ŕ9ŽKz!§ÂŐ-déYOq`x)ŤF(QđOšŕ3yQ¤Ěd=ěł*HUT ™§$Á΄œ°“Üu‚R6&ĺ‘JŒz"|™”™Œcƒ}VĺC*9(c*„LKYËŁŽäz“§ëē ‡a4‡—rÚh ~ž .ƒ;†ýź#ž3Ž=ÂdÜŁŽGę×H!+Ů™‘HZťópěŰR‡!;f_˛á“[´đë|RĘT=F…‘?́ßďJʑȘˇ*póa+.ńq Ĺůc’„ÖŽR‡Ŕ¸F*Ě!P(AÉ™äE;v,5ĽďÉĎRĐšŻ°Ă†ĎđIÜŽŻŞvĂ5óúzËŢR֌Ź!öűýo8€úG žČOőДa%4ĐÄířń$olŕCzFŒ ť8â8NÔÉxŔó<§¤ET$Bž:u*ŚN*b4ŒTä{œĹ‡řKBŸłˇ”I'cpŢd{Ӆᘄ ˜‚iq;>ƒ 4™ă8(P1žw8R‡ŔHqjWáUřţź<ĎĂď#{^žăŕ%#ápJ“Éd?ţźÔ0˛çĘ躌.mˇŹzżű‚ß?ÇÁbł“ŮÔÎUűďů†óň ŠÁ!vťĽŤŒH™}vƗćJe?SúÝЃ`0h€űŕƒ:322¤Šř`0(âEŻÓëeľ]Š čîé8Yƒěš Œąußݰ•ľgc0†ÁfłqJŁŃ¨Ÿ8q˘ŹN2 †)֎…Si•:Œ>¸Ünô˜Íŕ9™:ÔjľÔ!őĄD?^=-sZžÔq0r§ŞŞĘ͊şŒIčě8%%öš1!‚ŔwűC&¤#Cę¨#]ĐJƒA‚ @)÷vbA´ZöĹfHK˘JEâˇř{( I*8 TEĐňěťËJ:= Ô9#ƒNŇ2ĹP"ńŤžbŮvą?‚ @Ś—uźŤ,FË,0$†&!SadAbŽ ­ŽŽFSS“Č1R +ŹĐ"3nÇ÷çyášÚˇł•Űă‰j‡§A}mëĆţL¤ýž3~u¨G;Ř>Ë 2 1~|lť’Q˘¸@Ęůˇ= ůřńă"GĈ…ń…WQ˜×,u1Ąp{œŽÝę3ŕä,CŸŰÂɘ縈w~ęßpRöŒuaŃů6ŒŕĚŃĹEjˇŰlh+@MŰQ,[ś,é…ě÷űýJż—ß €äEMKK1 EyX5ç¨ÔaȊvk6~U´>ýő+“ąJĽ‚ËíŽč¸áśně/eżÎ‡ďoU㉺łĄg#ŮŢür%˛L 9‡Ó"dAü<(!“Ŕ„,zŹFŠC×dÜe¸ž~x0ŤŐꨳDjľ|żÇĽ¤'S§ÇŹ@›%'†ż y霆ż#!h4ą/ŸŁJČŠ0B&y3âŇce'š Öl<…Œc…I96ş-ěâQ.¤ČŮÇ ‚@EŰL–˛Nب#@˘d$R)˙Şh%“ň5ŘgU>¤Â€Ŕ„ĚHť^_â×ňʉDË8H$Rî6ŞđëŃËŃiÍíyiÄĺQĂábë§ĺB*Œ}>Ÿ÷ů|Ţáď*=,eS §­Ű-9řˇdœi‰ŹŤ?œ @gť.ÜH¤ÜiHĂÓE+SZĘ=,]-+Ra„ě÷ű˝źßď'ď:Ř9y貤找ݚ_^‰î(el°xđłÚObzNNŕđdÍ'ĐŰŽ_w3)O§95˙nš’ #dA\źĎçsŇ0Iíëň%/ŰGJBš^M}} p¤#ă'ŽF~FGĚĎ=*ł OÔA&“rÄ4uŒ’:F/HÎá4Ô-ű|>řý~Ďq\§×+˙Ź5ɋŞŐjĄT&žĺ!#<Šv˛‹uÎ8 ăOŸťŒƒŒĘlĂϘ”#Śą=ľ>ŁrFŁŃľ}ĽAȞŔwŽ…Ą%مĚqôz˝ˆŃ0HHĽ“ЌGfś‹ “rä4u¤^GŽdf’ľŤĽAČ^Ż^Ż÷*ďńxŽ$ť0!Ë“-V‡Nę0⎜d„Iyxş-F؜éR‡Á¸鹛"!WóN§łĘ#Ân2ń† 9šHöyd9Ę8“ňФR‡RAČnˇ[pťÝ <ĎóUN§SöU]¤/*iڃ!.É|ғłŒƒ0)NSgň~6i„ôÜMCѲËĺ\ŕőzýy§Ó)˙K˝°l„,/j[GKB\h'ń“W$DĆAFeśákŽÄ´$ęW…+љ¤}Ék[ФŃ ’s7 Łcp:ţüüüj~őęŐ͏‡Š¨I^\&dyQŰ2nx]¨ä@‡5 Oސ´š:Z b\ŐaLĂÓEŤ’n¤ěpiPßV(uŒ^¤‚}>ŸŻźźÜɁ!R $/ŽÁşÝĄäˆ×§Ŕ•ćäŮڮǚ‡3E˙?ş×Č-M=Áôľ>†Ž^g ŸF5yz_W7ĂďçĽƒŃ ’”5-B:8(důWu!°x:V˛łłEŒ„!?ÖÇśá¸ÜčąćAQx Ť á&Ě@Œƒš‡|•”çaʍBQp,i¤\Ő0AęýČɉýłEâŒDâ÷űÝŔ5!ű|žŘĺ&’×`0-.gˆO29(c˝q xđŘ.ěB‰sU2”r$˝Ż‹ăqżđ8pĐgMK ) PŐP,uŒ^h4š¤ďŇ\w0^Ż×!m8‘AÚ$++šćťh§ŰbD‡‰ŢĚEo˘CťŮŽűl;Päť^´FƒŒƒD"ĺŃžąŘj{vł=Th™ Rné ‹ŐšČ ’Ń1@ĎŮëőځë)ë.'‘ôÍeˆĎőĽ!&ş­yP~ÖGĆfłiBqüă|%TÉ8HAfž¸^Ęc}ĹxŘń¨5<Ěfs?) “ÎďY2dl’ÜÜ\˘ÇÓŕ5ˇŰ A€ë#äœN§´QEé‹Kúć2ÄçťęiR‡5=Ö<( Aoœ  ŻŒƒ¤ éř‰űg¸Iusč6d¤Ŕ0p¤|łj>s?‰táz—ľRž~#ĺď.Ó÷YLvRa„ět:ávťOׄěńx>sš\ŇF‚ Ľ­ŮY~4´ ˝‡ž÷%\šşżŒ@ŠT"ϐ‡=ÜϰĺTÉ8HAŻôő ܊=ÜăČ5äب%üH™.)7uŒDk׊Ă`ô# şœN§ŕršNׄ,‡ĂAĹě7rňńmŐ ŠCˆˆhdl0ŔqPŕNË üÓŐcTÉ8HAfţšć3Źł-ÇÁ`0$”żŠşQęa ÉjŇRĐeˇŰý …âúŮçóĽEČ$W=,e-OžŠş~?7ü%$€ÝR ÇՅČËlIxĚb1"ł śšpÚ *)óŁ>•˝”ý~ß_ž.uŒ0¤ÂŮĺrů7nÜX \reeĽŰCĂ {‘őz=ŇÓŮ..rĂlÓăJó8ŠĂRçd6%1›Ížüü|eř‡Ę’›çyäç磥ĄAĈbqüťů˜3ĺđ|âÓM=Ö\ŮĘ~†4Zaćzŕä\h37ăŁöƒżW@Ľ]Ľ]už.! Á€‚´"řáđßâSnf:j%'ĄÍ( IŮd2őůžĽœ™™ Ž lHaÁ1ô4-…QסřĂççńĹsţźŒČ(((}żb![,ß?üđUđçĐš˛˛Ňęńxäߎ ʰ+™éśqţęä„?o5ʢđ˝Š{Ó_Ć[ĚUóâ.ăޤ iČóçc´o Jܓ°¨uŮw ۗü0Ţ= Ł}cçχZHÜČ/8RŚiňŮę`˛ĹžĎ.#žÖúPTaí|ꊧBBëłwďj/9Cúb‰ #|úíBBâ…eŹ3D;2n€­fnJĚGnf,Ő á°*ËŁ[§|4ĄŐ×~?‡ĎÎ.HŘó1˘‡ôMËš˙ęŚţBnŁáĘB");VÄhbÓÜ9gŤoHČs™íŮŕFŠAĆͰT/BnfňVSGKžĄśšy1Ě)ßn䱄íuúŇ,´÷°Ž}r†äM‹Œý~?ź^oŸ~ş}„ěrš>˛Ů¨XŽL\ؼ׳}OĺĚßO•ĂăońŮž aÄrJČoΘFb-ôĘ̞näѸKŮíQăș%q}YYY0"/^ě-BśŮlđx<Ÿôž­ÇŰfłYţ‹ˇ€'Íh3fŒH‘0âŮŚÇççâWtĂd?b—ň q—ňął `u膿#C2H3˜´Ůbą‡ă@ďŰúůž{îůŇfłQńאžč,m-Ž}ť(.=†™ŒăO Ł—ꤏț'úqâBznŚd[X,ď… >ë}ß˙N‡Šî ^Ż—¨c˛üqyÔřäëŢ“É8qää%ĺC§—ɢń chHÎÍ~żŸŚ k{ď k ŒÝnˇ%q!‘A2J5jÔjľˆŃČ˝^oź ,@ii)˛łĺŐ;8ZN]ź -"mßć0âÓdÜëĺšLĆ1ň˘ŞŻo7ňĚvq>żíl ĐjľČËˋůń´¤ŤŔĺr™űß6  ˆßď˙ŃĺrĽĽĽ%&*<OĚ]ˇxžÇčŃŁqůňe‘Ł’†‚‚,_žĹĹĹÔ×ÔÔŕƒ>@WW—DŃŎßĎăĎG×c׆—  hbśgy†œ@őv4#csŐBŒ0˛F2ą’g¨Cűĺyŕ&|MzaÄÍC2łgŔŒOajYCFěÍC|~ţrlmB—Ňœ‚‚´´´PŃŤ9ZƎKÔ„–tľÓé„Ďç;ß˙ö#d‡ĂńŸV+ˑ‰_ü’’‘"‘ŽăPVV†íŰˇŁ¤¤$쇹¤¤;věŔčŃŁ%ˆœćÎ|˘uŁG?24ý˜F62&%˜žv9šD7RFţ'DéëOž^‚ÖŽŘG]rÂh4âÁÄŽ]ť0mÚ4ŠĂŇs2-BśZ­‚Ýn˙S˙Ű9==ý“ÉDEž4=1qâD‘"‘Ž•+W˘ŹŹ <x+;;;qřđaźţúëxçwBFƒ-[ś ++KĘpc&֓ŞŮžaÄ'Đgҕ‘Œ[`˝źÉXDr3ëaޚ §=şôľ!çƘç”I/ćäĆňĺËĄT*‘››‹M›6áţűďOŞěHÎɂ P“˛îééńY,–wúß>@ČŤWŻnv:T´Đ$}FŽŢ%ăǏÇüůóŠ>ţřcüîwżĂ‰'P]]łgĎâü#>üđC‚€´´4lذ!$ďĄ Y|~Ţ>ş>˙đą Ę8ú9ă–k\ľâý ‘˛Ľz^ÔRŽĽĐËďçń×ckŁúĚșÂÂBLŸ>˝ĎmĹĹĹŘšs'6mÚ$ťďl´ †Tš?vnŰś­§˙ía?ŠnˇŰ˙Ä$EÁq&L˜ b4‰eńâĹ!‰ź÷Ţ{8yňdŘyĽS§N፯ýËGŮłßwXĽRaůňĺxôŃGe—kî‰ĎżlŮ “ą| 'ĺĚḚ̌RśX,1Ků跋Ńԑ<ŁÇ•+W‚ă8x˝^:tÁŠEŽă0mÚ4ěŢ˝˖-ŁśXuňd˛ö´¤ŤŔăń„ulX!{<žďœNg|# Ň7Ö´ľJĽ 57ŠŻŻÇˇß~;äý>ŒîînŔ˛eː‘1p}ď”)S°gĎ,^ź …ˇŢzŤěžÜ‡N/C]ëĐ}n™ŒĺO)ó<v¤ěvťc)׾âÓoĆ'x ˜ —+ĐTŤŐXşt)ţáţsç΍(&'HĎĹ´ŮétÂăń|îwaß1ŤŐúLww7{#{<˘őČ'N$Şę“Š`%*\˝zuŘű{˝^ż"ž@‚ŕy˗/ŘĐţĉŻË‰'đĚ3Ď૯ž eÇŇÓÓązőjŞ ż Š‹‹c~ź Ôš§§G°Z­Ď…ű]X!oŢźůCłŮLĹ_GúF¤§§Sšc,ŒŞŞ*üř㏀ŇŇRŁźźťvíÂřńăC÷ŤŤŤĂ /ź€ƒ†Žžĺ„É–‰7ßżżďĹ“1}D“žŽDĘ~?7ß ł-yzŐϞ=;4ˇú駟ÂáčŰťÉnˇăŕÁƒxîšçpţüő•44~3Mě[†şÝnى/&“ÉsţüůÂý.ʐ9Žó;N:Ö>ŇęDžça2ę ‚Rňűý8uęž}öYœ={–h ‘;ťçŻNŮžäG/c‡­ćŞyLĆ2 °Ny~hr4RF~@Ęj'%]Żęŋ‡ę=:Q%ń•+Wđ /ŕíˇßîó]~­XąrküDZĐEËŮď÷Ăáp´ öűA…ěrš^5›tö’%>Ÿ¨kMaa!•-&żřâ řý~p‡•+Wy_•J…e˖a׎]ČÍ˝žl]]^|ńE|řá‡1ĽÁĽD€Ăßn0â2łŁ“ąËŃ [Í<śÎXFäfÖĂôă8íŃ]¤RŹSţ‡žy”\KF„^ŻÇÜšs Ą đHçϟÇŢ˝{ű~ŠT*,Z´=ö˜l żrss‘ŸŸóăIĎ˙‰Äl6Ăĺr˝>Řď}7Ün÷KÝÝÝt ‘A~…DKńCoşşşBŐŐ'N4ő>iŇ$ěŢ˝K—. Üěv;ţöˇżaßž}}Ň[4a4qßÖGch‡ŮsŐäfÖ'e §›–6é¤~QQ‘l †ÂnˇăčŃŁn.\•J…˛˛2ěŢ˝ťOąDss3^zé%8p $q!•ń#“ąÜ‰FĘ˝›‡$‹”Ýn7ž{î9|ôŃG8tčhÇmooÇ믿*üęčč˛P,ŢäääU€ÓÔ.SŘíöŽĄî3œÓrâčJ)Xô@#_ý5š›Ĺ0‹-ÂîÝťQVVÖ§zúÝwßŋ/žˆĆĆF)C%&÷^ŤÍdL‘Jšód‘˛Ďç×_~jč#&ÁÂŻ×^{MŇýƒo¸á˘ÇťÝnj˛|‹çoCÝgH!;Ž_tttPąŃşikżß÷ß‚ @ŠTHO˙îwżĂ7ß|CÍw0˘•q°`…ɘ^Xú:~ż?R’JéęöövŻÍfűŐP÷Rț6mşhľZé"ÄM,Ì˝žÎ†MMMřć›ëÝؒ%=„É8uaRNN˛˛˛0jÔ¨˜OšM4vťÝvĎ=÷ Y 0lÍťÓélĄĽ¤\Œ´5iş”>|]]]8xđ ţđ‡?PŸžłŒôü¸˜É8 Č3ÔŁób9ŽŔĹ%“2ý”––ľćĽ)]íőzát:‡= +dÇó\<ć0âéÓP;!ɇÁgŸ}śO_[ډ]ĆNźüň+8đéRXônąÉ`ąëđˇĎăĺ—_ ľŽLľBŻdcć̙D§itÜŮŮ)x<ž_wża…ŹÓéövvvR󗓦­óóóŠěm„–+ĆH “ńËhllDC{öţu;š;ĺÝ˗18-]#đü;ŰĐŘ> MMMxů嗇•˛ŰífR–1ăƍëÓ (ZhKWwuuš×Ż_?hC Ă šźźÜét:éhŮqvý¸éŚ›DІ+bČ8ˆŮŚÇ ďޏ‹ľtnľ™ĘüX?/üm+zŹ™ĄŰ˜”égÖŹYD'Ýĺ/ѸÝînŽă†M[FÔ7Íét~MS[EŇQriié€/7#qˆ)ă nŻڌcß.ˆ˙Ŕ…“çć`˙GwÁĺŘw™I™^Ôj5ńr'9îB76› .—ëX$÷TČ˙­ŁŁƒšIIŇT†VŤĽrÉd 2â÷sřűŠĺ8đŮíđxŮ—\q{ÔřóŃux˙ä-„Á‹~“˛BŃwd&ey1}út˘Í-hKWˇˇˇű<Ď˙É}#ňƍO™L&jÚhúý~â7ŒĽ­O {˙úžŠšŃýĂIŮ`0°‘˛Œ!MWÓT] 6›ÍžaÆóĂß3B!€Óéě Šr—4Ĺ>aÂj×$ÓHVVvěŘŃGĆ×:Űôš_;źôŇKQ/ńjďÉÁsśáș%CŽÂ‰A)ęß˙ít˜˘Űy­ŠŠ űöí }çŁI_oŰś ƒAœ?‚1,YYY(..&:MÓ§~żN§ł)ŇűG,dˇŰýšÔ]]˘Áív-ýáysćĚ1"ĆĘÝt´ IDAT`Flßž˝O‡1‹Ĺ2 ËndüĘ+Ż Š)âĎ{|~Gž^ŠW>ź;ťř’ Ť#Żź>żbř„ĄĄĄ/˝ôRÔsĘŮŮŮŘšs')'ˆ9sć­=öů|ÄEť‰¤ŤŤ ‡cĐݝúą;;;˙­˝˝žWä˙óćÍcĹ]q&\š:RG“ŚŠËĹříŸÂWŚ‹gŤ§ă7o?‚ކńÄÇbsĘňFĽRáć›o&:MĹ\ĐŮŮé6â yŰśm=vťÝ[XŇ@šÚČČČ śż5 GĆRĘ8ˆÍ™?ş/˝ÚşóD;.#<ŚźňÁ=řĎO6ŔîԊvÜÁ攙”Ľ§´´éééDÇ IȂ Ŕápt–——G,˘ˆ… .—ë]‹Ĺ}dáóůˆˇćZ°€-“‰Aggć ŁMSÇŤ-hMÓX<ű—x˙ä*¸<ę¸?“çćâ×oí¡U7‚˘‚NYsŠn"ţă?wáČ×Kcž+Ž&eyQRRB´ď1@W1´śśşÇć1Q yÝşu6›Ş´ľËĺ".‘'˝˛c\Gî2îĹŽÇŰG×ăĹ÷ś˘şŹ24•ůą~ž;đ öT “-sřˆ“˛| =‡ŇśöXŘíöÎĘĘJk4‹JČ}ikAˆçnźńFčtlƒRÂÉŘl6ËRĆ˝ŠmW>܂g˙˛çjڞs„\n,ĆsśáՃwĄĄ˝P’˜”ĽÇh47Zc`•HbIW1™Ć´uđË+ …sçÎ)šÔ$x‚ë?2ŽdńË/ż,™Œ{Óܙ7߉g˙˛“‰y!šţ݁íxůƒ-hh—~Ł–ŚŚ&ěßż?ta”ˇmۆĚĚčꓑyó慾ϱBzO4ą¤Ť„LcÚZŒľk , j÷–ĘDZM­P(ŒŒ÷íŰ'ť}[şF„ÄüĺůŮpşŮçÂáŇŕ‹ćŕ™??‚ýU˘ą=öçăA]]ţđ‡?D=RÎÎÎĆĂ?ĚFĘ1˘Őj‰3nˇ›ŞbŽXÓŐ@ BčK[äWXZ­–5 ‰HçŒ  ƒlŇԑĐŇ5ďžX}íqźqřN\n,NšQsSÇ(źsüvüâőŔ{Ÿß‚śîطԋ7$éëŢ”ŒČc C[1WŹéj F!Әśă*kńâĹPŠT"E”ü$łŒ{ăő)qŽf*^ţ` ţă?wářwóaą'óٖ‰cgâßßڃ˝ݎSfÁăĽă{kú:++‹I9JÔj5q1—Ď磪˜ ZZZbJW@Ě=Ě:Ô^ZZ*ßËá0hľZdddăÝwßŗ_~)RDÉKŞČx(FdľcĘŘjLý#Ćä7€ c ü~|q lGGGčöîîn|÷Ýwň<Ż›1cĆżö› żß žž<ĽŢ֝‹‹ľqŠ~"ęZGSŸ 3f xŕĐčÍď÷Ăd2 ¸hWŤŐĐëőĄvÝÝÝx饗ĐÝݝđ˜icɒ%¸őÖ[‰ŽaľZŠ! ‚€łgĎ6ŻZľ*Śâ‰˜űB^K[oŁi§Ó‰ôôt˘^ŞK—.ĹéӧКÓH4LĆÚşóĐ֝‡cß.@f†“ÇTcňč*Ô!M%ďŽCNwŽ4ĂĽş ¸X;VGrřëęę°oßž”ƒ#ĺţRŽ”ƒRŽ”™”‡FŠTbá…DÇc…L˘!IWBöűý˙ÔŇŇrŻ^Ż§ŚQđ Öh41Ă`0 ´´_ýľˆ‘%LĆá1Űô8uaN]l=§Oˇ`ěČŒÍŻGAn3 r[ R’u•‹ŸŸG§)ľ-ŁQ×6í#Ńޓ›ôť`1)Ǐٳgď–çt:ŠZęÍÍÍŽXÓՁ×­[×ńŃGľ ‚PH2âL4‡ƒHČPVV†ożý–h7ŠdÄ`0PÓôCj,v=ÎŐLĹššŔ† ޏ‘9­ČĎjGŽĄ 9™]ČÎěFf†š¨‹5 €ÍŠƒŮŚG§9 Ś,tš˛ŃÚ=­]#ŕó“-KĄ•hĽ\Ť<8 …‹/&>mKü~?ěv{C,ŐŐAˆś2r:˙w{{ű‹#FŒ ĆČÁ"ľ:ö}NNJKKńÍ7߈Ý †>ëŒDź´)ŐdŸŸGcű¨°Ë…źú ;žüçXŽŹŔ/^ 6‡.eĽ;ŃHŮl6ł‘ň0̞=›¸řÍĺrQ7ŕinnÇĎIŽAô ­¨¨xŠ­­ÍNr )°ŰÉC^ąbۚńádŽWŞĽŠĹÂççaŽšĺ$ł-“Éx‚RŽĽúúÁdÍCŽĄVŤQ^^N|1Îщڧ§Ç˛aÆż’ƒř[jˇŰĎĐT^Ż—¸”Ţh4˛î]`2f$ąJ9''=ô“2€… Ďť\.ęŠfív;l6Űç¤Ç!˛ÓéÜÚÔÔ$M5 b\-[ś,Ľťw13’ &ĺŘIOOOÉšchllô(Ší¤Ç!ňƍkM&S7mŐpbŒ’ĹúŇ“1#YaRŽe˖ĚşÝnâ=ěÍľâŐöŐŤW7“K”‰%‡Ăąˇťť›.#CœQň˘E‹Rn'(&cF˛Ă¤bMáŃ8wÜŢŢ.Říö_ˆq,Q„ź~ýú˙ŃÚÚJ×D2ŁdŇM'Ä*b &cFŞŔ¤9bšŇ8:€ÖÖVdž žăX˘™ă8żÍfŤ&•›ˆqEvóÍ7#7—Ş.˘1Ń E€É˜‘ÜÔŐŐáŐW_eR‚‘#G˘´´”ř84ÎťÝn¸Ýîď9Že–hk!,ËśŚŚ&şJăx<âQ˛BĄŔÚľkEŠHž„kúÁdĚHjkkŁ)I)ŻYł†xżcˇŰM|–‚ĆĆFŸÓé$.ć "š+++Ďtww›h+î›ÍF|Œ &`ęÔŠ"D#?˘iúÁdĚHF˘I_›Íć”)—––˘¸¸˜ř84Î ‚€žžžŽ 6œ똢v pš\OwvvRgdŻ×+Jó5kÖ$ÝöŒlΘÁŔć”ű’––Fź›8WĐ8wÜŢŢ.X,–˙)ć1EňÚľk˙WKK ůpSěv;q#sŁŃˆ%K–ˆ‘ô03}aRžÎ˛eˈ˙A¨@KK‹uăƍ˘s˝ŸžŐj=Ř{…|>Ÿ(űn.Y˛$)61g2f0Âäř[,X@|§ÓI]Ďj p.t8ű¸˘ šŠŠé††ę–@*?ŇQ˛JĽÂíˇß.RDŇŔdĚ` MŞKy͚5ĘŽíŽ$RD‰ĽžžŢiˇŰű¸˘ ů‘GąŰlśó´m, > b”ŢO:“'O!˘ÄĂdĚ`Dɒ¨íۡ÷|–ŠiÓŚaҤIÄÇc$N§vťýtee%YŤÇ0Äe ˜öööM ôÍŇ#đ!#…˛aĂâ6r‰†DĆűöíc2f¤Ń.‰ (77;vě n¤ŹŃhDYâ)ÖĄÔŐŐyŹVëÝń8v\„źuëÖ+&“Š…ś;ńŠ ôz=nšĺ"J ýeÍ:ă}űöĄžž>á13r ÚôuÓ×kÖŹ%^1ŠhĽŔçóÁfłŐVVV6ĆăřqŰ$ŐétîlnnŚośɈąH}Μ9?~źŗĚĚ̘×33ŠąNyüřń˜9s&ńq<(ËLĽ ĄĄÁo2™ˆ×ńă&䊊Šƒ=4^â4 á86l€Z­!˘řNĆldĚ`DO2zĽĽĽáŽ;îÇqÄDzZ­"D”xA@wwwÇć͛‰÷=Œ¸ œN'•B€@ł1 Ꞟ˛°rĺJ"Ÿ ŒsrrBˇE"c—ËĹdĚ`„!YĽ|ë­ˇÂh4ÇnˇƒĆŠL ´Ť“¨@úW!Ż]ťö577SŮ(|xÄ(đZ°`Ǝ+BDâA"ăW^y…ɘÁŇ )äV}]\\Œ9sćGŹU,RŃÜÜlЍ¨řm<Ÿ#ŽBťÝţ|WW•ŁdADM]ËĽ­&“1ƒ_jkką˙ţĐwŠV)ŤŐjQSŐ´Naśľľ 6›íßâý}:nşé&â㈕i”Šööö„ŒŽ yíÚľ?oll¤ł´ľgbÍ}Üyç0 ˘+úË8ŇuĆLĆ ŃJ9ˆ”RÎĚĚĆ D9–Ăá ś Íëׯ˙˙ń\ 2Říö˝´V\/1śÓjľŘ´iń†ŢŃndÉ:c&cC˘‘rďuĘRH™ă8lŢźZ­–řX^Ż—Ú~Ő@hîř—‰zž„YaíÚľ˙…ćQ2 Ţúšââb,Z´H”c GŹijˇŰýű÷33"AKúşźźĹĹĹÄǁÚ5ÇAš››M6lHČčH Ŕjľţ–ćQ˛˜W{+WŽDQQ‘(Ç żúꍸzőj\ăc0R šKš¨¨eee˘KŹŹ˘T´ľľ ‡ă‰|΄ š˘˘â_h%‹ő!S(ŘźysÜ6  -ŕb2fȉ‘#GâŔȑ#Ľ…˜X—DĺććĆu—¨ôôtÜ}÷Ýâˆą+IIKKKĎşuëţw"Ÿ3ĄB›ÍöLGGľŁd@źÔuNN6oŢ,ĘżŢčt:lŰś-ć4u]]¨ńČ‘źźŸŔ‹/žúťľZ-nšĺ<účŁ(--MŠ´h2qńâETWW222°lŮ2i)Ľœ››‹7ŠňyO†TľĎçCkkkÓwÜń)ž_!€Ýn_őęUzómV-Ęą´Z-ślŮÓV$2ŢżJɸ7ÍÍÍxőŐWą˙~´ľľ ***đĐCÉnCTçŕÁƒĄŹÔÍ7ߜ^A¤˛Z­Ć–-[DË6X­VŞSŐpőęUŻÓé\/ŐóK&ä 6|ŮŐŐu‰ćţŚ@`_`ą6ŰÎĎĎGEEETÉĚĚÄΝ;Ł–q°šúʕ+äSΕ+Wđűß˙ďż˙~荠 <đîšçQzů2ČéęęÂɓ'„uŰmˇ%U&ŁśśŻ˝öZDŐ×ýĽź}űvčtşˆŸ+˜•1b„(ą‹y” §Ó‰îîîď***ž‘*Ʉ nˇ{yMM Ýď"Ä˝2,--8GRMýÚkŻĽD5u¤řý~œ9sżýíoqüřńІ"'NÄîÝťeť§uŞqüřqôôôF4^AŽ\šqőuo)çĺĺaǎ”WŽ\‰iÓŚ‰ł˜™B)š|ů˛ËëőŢ*e ’ š˘˘˘Őd2}BűźƒŘs'+VŹŔ̙3‡źiššŒĂăršpäČ<ű쳥žBĄ`Ĺ^2Áăńŕřń㡟“ŠŔ+HźÓ×7Ýt“hÍ?‚ç>ZˇU b2™`ľZß]ˇn]‡”qH*d8}úôşŤWŻRyĺőzEťJä8wÜqJJJÂţžÉ8ţčőúЉÍápŕłĎ>“8"†BĄŔÜšsąjŐŞĐm˘.” A)ǒžJĘĹĹĹQO‹ …ÍfŁşWÚÚZëš5kî’:ɅüÔSOy­VëÓííít_b!pâkN\ĄPŕž{îAnnnŸŰ™ŒĂŞUŤBó“Ÿ}öŐ ň“’’ěÚľ ŤWŻFZZA@p•ĆěŮł“ŞŔ+Hmm-öíŰ'š”óňňpď˝÷Š–íqš\p:˘KJš››ťÝţ?9Ž“|ُäB€őë×˙÷†††ڗAĽPbýZ­<đ@¨i…N§ĂöíۙŒăĚ´iÓ0fĚ@OONŸ>-qDŠ‹VŤĹwމűîť/T+ŃÖֆýű÷‡R×<Ď'MŻţD#奜nĚČČŔý÷ß/ZzßçóQżq¨innîXˇn]B{V†,„ g[}}=őFî3,֜JVVśnÝŠŹŹ,lßž˝OU$“ąř( ,_ž<ôóáǓ"%G#ŁFÂŽ]ťpĂ 7ŒČţţ÷żă…^ŔŐŤWqâĉP×˜1cPZZ*e¸q#R)ť\ްRÎÎÎĆ}÷Ý'Újd™7€ÚÚZŸÓé”|XâˆR—I“&…ţ˙ĺ—_{˙ŽŽ.|ţů璳ƒW¤˛Ď磞OuŻ×‹ŚŚŚŚľk×ţWŠcéě„ +Ż\šâ‘:1ŤČ‹É8ž,_žJĽpćĚtuuIQ꒙™ú¤ďCďŻŃŁG'mWDJYěBUŠš|ů˛Çl6˲őž,…źe˖ď:;;?M–+2ŇŤK&ăřRXXˆéÓ§z;vLâˆR›Ţk[#m™J^A%e1ł|Rc6›a2™>Ř´iÓEŠc ‡,… §OŸž­ŚŚĆ’,Wenˇ;ŚN^LĆńgŊĄçńăǓfžŒVZZZB˙´ß2ÇqPŤŐ!q${WH;zĹ*eŤŐ*zŒT‚€ššÓíˇß~§Ôą †l…üÔSOyÇÎdX›ÄápDUy‰Œ ŞŞ*eˇP$eňäÉ7n€ŔHŕÔŠSŇÄŔ… Bb7oްťĺĺĺaëÖ­¸ăŽ;B"Ş­­Ĺ™3g⍨­­Ĺĺ˗C?‹%eťÝž¸‚ÔÖÖú<σrčČ5˛2Ź[ˇîÍśśś ÉôĄ°Ůl-ˆTĆ0}útlܸqŔ팡áyžOC‡…vybH‡ŐjĹ7ßvŔÓh4Řźy3ŇŇŇÜOŁŃ`ŐŞUxřá‡C{WŰl68pŻžújhëd†çyÜqǘ:uę€ŰI¤ěrš’*StmÍń÷ˇß~űŠc ĽÔ ‡ĹbYZ]]ÝxĂ 7$̈́ĹbĎóPŠTaŒƒĚœ9oźńFR,ÚOłgĎľ!miiÁšsç$Žˆäȑ#˜8q" F‡~Ÿţ9ZZZ VŤ1~üx̚5 Z­@ âéÓ§qôčѤŐ …BĄ@eee¨ţĄ?A)›LŚ>sŔÁÁ`óóýŰp&UUUłŮźdř{J‹ě…\YYŮőîťďţŚŚŚ˙ˇ   i†€fłƒ!TŮŰűöhedʔ)ŘşukŸMÎáIKKĂŇĽKC?ł& ňÂétâü#śnÝ N‡ŹŹ,ŹYł&ě}kkkqđŕA´śś&8JéPŤŐزe &L˜0äý˘•˛×ëM:766úNçżTVVĘžů6‚[ˇnÝ˙nllŹłó•Ô—ôţ’Č8HII śnݚôڤ,^ź8´iÇĽK—XAœ éččŔ /ź€K—.…˝XjllÄoźW_}5ĽdŹŃhđŕƒ+ă ‘ŚŻ}>_R-oóŕ­­­ßß~űíż–:–Hý9ˆŐj˝ůÇl1cFz˛,ú÷űýĄ‘r¸jĆhedܸqŘž};öíŰ'ÚÍɄ^ŻÇÜšsރ#GŽHc0, Ţ|óMFAŤŐÂáp žž&“IęđNFF|đAŒ5*ŞÇ 7RÎČȀŮlm§:9 ŞŞŞlWŻ^](u,‘BĹ*++­N§óÁşşşäXw ŸĎ‡îînŃd¤  ;vě@VV–a&óćÍ Íßýő×hoo—8"ĆpôôôŕÜšs8uęΝ;—’2ÎÉÉÁĂ?ľŒƒ 5RîîîNšľĆAŽ\šâłŰí÷>ňČ#ÔT§Q#dXťvíśľľ}‘ űpöŚŠˆTĆAňňň°{÷îP*#ŔáÇńÎ;ď ŤŤ Ÿ~úŠÔá0Ă2fĚ<üđĂĄ=Ące0)'SšL˙uvv]ż~ý;RÇ T žúęŤeUUUćdJ­ôF,IOOÇśmŰ0cĆ QŽ— ‚€łgĎâŮgŸe)}†ěšá†°mŰśPÍ)ƒI9Yđűý¨ŽŽîšíśŰn‘:–hĄNČO=ő”×nˇß’,˝ŽűĂóźčń•J%6oŢÜgÍ-#ůFŒäcÁ‚¸ëŽť]"+Ç%íUUU^§Óš\Î @ƒ:!Ŕ† žěčč8ĐÝݝtgTÇ—JGŽă°|ůň>݌ †<áyXłfčâ&“))űtvv ÝÝÝި¨řFęXbJ!ŔÚľk+kjj:’ął’ÇăÉdŠËnöěٸďžűز(CŚhľZ<řŕƒĄí@Ĺ$™eěńxP[[ŰźnÝş­RÇ+Ô zzzTUU%e Ż× “É—e'NÄîÝť‘ŸŸ/úą FěŒ9ťwďFII‰čÇöűýI+c¸té’Ëd2͕:¨ňÝwß}Ůl6˙_uuuÔÍDB<Ľœ““ƒ]ťvᦛnýŘ #zJKKńČ# ;;[ôc{$ŤŒkkký‹ĺg•••RÇBŐB€uëÖýŞĽĽĺ„ÉdJşůd °N9^RVŠT¸óÎ;QQQÁć• ‰ŕyˇÜr 6oŢ,zńü#cłŮ,ttt^ż~ý^Šc!…z!Ŕš5k–VWWˇ'ă|2_)Ŕœ9s°}űöˆ7ƒg0␙™‰;vôéŤ.&A'[ӏ nˇUUU­ŤWŻŚn‰S8’BČŕršf\źxљŹKY‚RŽ×kěŘąxôŃGă2wĹ`02nÜ8ěŮłc̉Ëń}>zzz’VƂ ŕâŋvÇsŁÔąˆEŇš˘˘˘Ől6?XSS“œy\˙‚Ĺ+‘‘xeeeloe#N÷áŢž};t:]\žĂăń §§'ŠzS÷§şşÚg2™î_ˇn]‡ÔąˆERu7lŘđfww÷_;;;“s˜ŒëťD›Â‹BĄŔŞUŤ°sçθ—0ЌŃhÄöíŰą|ůň¸]ôş\˙§˝;nŤş÷ţÝ­!žb;ĆNBÇÁ Ľ)C{Cń{M=DQěŘZZščˆ‚ ő^Ëk1ÜŢŰń’rĄ€erĄĽÍęu %¸)”Ţ^nçP  bă8ÎŕȎmɚgéě÷G,ُ™Y:ög-­XGŇ9?'Š~ÚÓoűÓn׌éĚf3ľZ­űëëë ËlJŤ„ ŐŐŐş“'OžIçMĘ)Ľp:ˆçv” ,ŔΝ;ąvíÚ¸]ƒa2ÉŞUŤđŕƒbѢEqť†×ë…ÓéLëděóů044t˘śś6e×_JÚ%d ”Žéííő¤sw ¸Ýî¸Öb–Ëĺhhh@SS EÜŽĂ0é,ú˙Q< ňÄűó đ<ŢŢ^—ÍfťIčXâ!-rMMĂívoHŰńäˆD|#^ľjvîÜ×oö “Ž.\÷žŚDô˜%‹ţţţ ÓéŹÔétéľĺߤ´LČPWW÷[›Íö“É”ŢÍd|2fρÜÜ\lŰś Z­2™,n×a˜t •JąaĂlßž=Žs1"?â5§$™œ>}šˇŮlßݲeËűBÇ/i› śśöĂĂĂżš˜˜Hß•I‘Y•ń\üOÁşuë°k×.,]ş4n×a˜TV^^ŽććfŹ_ż>ŽŤBĄP\W]$‹ĹBÍfó›ćBÇOi ŚŚF388xÂĺJËŽ DŠÄűŰr^^śč<ňš IDATnÝŠŚŚ&(•ʸ^‹aR…BĄ€VŤĹWžňäććĆőZ~ż?ŽĹ‚’‰ŰíĆéÓ§ťŤŞŞę„Ž%Ţ$Bď˝÷Ţr‘HtnőęŐsăQš.™DƓ‚Á`ÜÖ8FŹZľ ‹-ÂŰożŁGĆőZ “ĚV­ZF•J÷kšÝîŒ/ÎWâúřăÇ˙ň—żdDŃý´o!@KKKČçó­ěééń¤ór€h>Ÿ/!ß Őj5pĎ=÷Ä˝UŔ0É&??÷Ţ{/šššâžŒ#=`™’ŒyžÇącÇ\@`EKKKÚOĐ2$!ç+yY­Öő˝˝˝iš]ăĹ$b\9bůňĺhnnƆ ؤ/&íIĽRTVV⡇²eËâ~˝ČÎo™0^Ń××p8•éT‰ëJ2&!€N§űĐfł=888˜žĹ]/"QăĘŔůŠőë×ăá‡ĆÚľkA‰ű5&‘!Xľjš››QYY ‰$ţŁ~‘ŢŽt­I}1'Nœsé<Łúb2*!€VŤm›˜˜xudd$3úŽńɸr˘*řdggŁĄĄƒóçϏűő&JJJ°}űv455%dx&ň˙ÖĺrĽuĺ­éFFFřńńń—~,t,‰–q ŞŤŤď3™LŘívĄCI(żßŸ°.l˜?>8ŽƒVŤMČd†‰ľZşş:ěŘą×_}BŽYҔ 닣Y­Vj2™ţ¤Ńhv‹2b–őĹTWWć­ˇŢ:^QQą4Ţł‘“IdÇ(ĽR™%K"‘ë֭Í7ވ#GŽŕ÷ż˙=ŇšÎ8“>är9nšĺÜqǐËĺ ťŽ×ëMű˜ărš0000PSSŸÍĄS@Jś[ZZfĺ‹DUUŐ ýýýŚL|ó{<ž„Žc”ÉdXż~=}ôQTVV&ôŽaf"ň^Ýłg6lؐ°÷j¤ęVŚ~őőő­ŽŽŽ:!ĽbBŒŒ|Äq\7bŒŸÂŰíö%}}}ćLlľEfa'rćŚBĄ@ee%}ôQŹ_ż>!“bćjˆĹbŹ[ˇ<ň6lŘ׍ Ś °Ůl2fČŸĎ‡ŢŢŢq§ÓYFI˙J'—‘r ™ă¸#”ŇV †žX[Ë:.ŕp8÷ööÚ2iIADdv˘'Ž(•Jlذ?ü0nžůfˆĹâ„]›a˘I$|ć3ŸÁŁ> ­V÷‚:Ń(Ľpš\qŻEŸŹ‚Á z{{m<Ď/Őét™÷mdš”JČ_ţň—‹ŹˆÜ§”V ÷566Ć´đU§Óšxž_ŃÓÓăΤĽŃ|>ŹVkÂ×9ćć梎Ž{öěAeeeB[%Lf“Ëĺ¸íśŰđČ#`ÓŚMČÎÎNčőŹVkĆΊ…Bčîîvy˝ŢŠšš‡Đń$ƒ”JČŻžúę˜BĄ(`:\–ŸŸ?Ŕq\L3”ŞŞŞF,˧zzzź™řMř¤ľ,Äçjľ•••Řłgjkk1gΜ„^ŸÉ*• •••xěąÇP[[›đDœé­bŕügMOOobbb­VŤ:žd‘R žyć™QËLDŽQJÜşukL‹›ššúm6ŰǎógŇşżéü~?ŹVŤ ăY‘VËc=†-[ś °°0á10é)??ľľľxěąÇPYY …B‘đ2˝U œOĆÝÝݡŰ}{SSÓ ĄăI&)—Ŕh4šĺrůbBČš¨Ăód2Y˙îÝťcÚ|t˖-ďŰlśM}}}LNʑŸN§Sońbą7Ýtvíڅťîş eeeŹň3c„,]ş÷ÜsvďލŰnť Bl0Ăó<œNgFˇŠó˝}}}AÇSŁŐj˙.t<É&e§¸>ű쳎ĆĆĆňźźź”L.tťÝ'š››+&[Ň×¤ŽŽîŽŽ}˙že˖I29řý~ƒA¨T*A–*‰D"Ź\š+WŽ„ĹbÁ‡~ˆ>ř'áą0Š#++ ŤWŻĆmˇÝ†˘˘"Acńů|đx<ˆóÉřă?:Î{4ÍoŻöuÇ5RJ ŕo<Ď?°oßž´Mä)Ÿieššš}„ĹQ‡*•jĹŢ˝{Mąœű7ŢhČĎĎß_QQ!Íä¤!‘H VŤ_Ş …Đ×ׇ÷ß'NdF—H$ÂSO=0›?ŠľoľZŃŮŮů H$RŻYłć;999S€çy|ó›ßLxźB)))Áşuë°víZAZÂŃBĄ\.WÂ*ă%łH2Űşe˖ýWűşx * FTo.!dŔžÖÖփńˆUHi‘eeyyy=–FöBÖśśśĺÜoźńF]^^Ţ._žœ%ĺIrš*• "‘đ#çΝĂ|€ŽŽŽ´.¨ŔňĽŠŐjŹ^˝ëÖ­ź5 œO>'cśIź’ÉnęŔÄÄÄ=›7o~m&ŻŐëő~BČĽVŃLxś¤¤ä[é˛=c:eÇqGŹŽ:楔ŽmkkëĺÄŻżţzíÜšs__ž|š<’P2 „@ŠT 21ćbxžÇĐĐŽ=ŠÎÎδŤĚň…¤R)n¸áŹ]ťĺĺĺIłŽÝçóÁívgÔf—ĂóŽ:4 ŔÜéĎĽ”RBČ_ĽRió~ôŁ÷Ž1lAĽ|fŮľkWv ˜'‰J‚Á`1!d.!ä{Śś˘”$Éí/źđ‡ą\ëŕÁƒŸËÍÍýŻ•+W˛–ň4ršJĽ2iZ*nˇǎC__Svƒ÷LMČRŠeee¨¨¨ŔŠ+˛!ĘL„Ăa¸ÝîŒ,yy9“ëŒý.—ëó›6m:Ëšôzý„›'ďúKJJÔgϞÝ,‹żK)]r‰—BZZ[[_Œĺډ–” yǎˇ†ĂáVB yžŸKÉ   Ś”JČ̚ŞŤŐ:÷ŔŽXb;tčĐMŮŮŮZąb…‚%ĺ”ʉ8ߊ9sć úúúĐÝÝ §Ó)tHW-“˛RŠÄ˛eËPQQňňň¤Üˆ„çyx<žŒ^O|)‘˘fłůs:.ŚFěŘącM(ú(ňqOyŚľľőaŕ8îsžpă%^î!„ü”RşŰh4&ýҌ¤HČÇ)yž˙!¤žR„řÄŐj4íßżMQQŃ{ŤV­ĘJĆē ˛˛˛ T*“bâ×ĹPJ144„žž>ô÷÷ctt4ŠÇýŇ9!BP\\Œn¸(--MÚ5ç<ĎĂëőÂçó%őűE(áp===^‹ĹrťN§;:[çŐëő=„HŮä€ŐjsŕŔŠn‰­[ˇVHĽŇŹżDC§”ţ˛Íh4&íŇ Á×!s÷0€ď‰D˘X!)€0!$ČóźŸâŕŕ‰Dż}ńĹ‹9ŘIwß}wçţýűoéîî>˛bĹ E2Ÿ& ŸĎżß?•˜“í–‚… bá…ذaü~?Ξ=‹'N```###ě7ŽňóóQVV†˛˛2,Y˛$麢§Ł”N­'f ƒčééqŰíö›t:]LŤ[Ś“H$ŰÂáđ_&ďĘrrr~ŕČ㯟ňJ€Ď777Ďóx^QQQœČmŰR ĎópťÝđx<ČĘʂBĄHÚŽěľZ5kÖ`͚5ÎW,;w‡a2™0<<Œńńqö!}sćĚAII JKKQRR‚… &} x:žçáóůŕőzŮżńŘív?~|ř̙3ĺƒ!nă´<Ď?$‰Oޕĺçç? ŔpąçNŽôzý„o˜ő”ŁÉ–ŒZČ---“Éä$„D7+l4ˇ OŹ(Ľ˘wŢyço×_ýššsçŚFP@„Čd˛¤üuľ<L&L&ĆĆĆ0>>łŮ—™ÜÉŘB–Éd(((@aa!ŠŠŠPZZŠŇŇҤY›~-Âá0|>#žJ‹…žuŒR ›ÍłŮŒńńńŠ$mąXŕršRŽžąX,†Z­ĆÜšs§’oaa! “““2CW …ŕőzÓŽ L<ŒŒđĂĂĂż­ŽŽŢŔË> }ňgéđđđóžz5/4­ZăŘlęS°.ň!Äk4ď(ŽYUUUuçáÇ_ňů|[—,Y’şMż ÉdP(IU`äZB——‡źźź 5đI×˝ÍfƒÓé„ÝnŸÚÚnˇOľĚ‘˛˛˛.¸ĺää ;;ŮŮŮS?çää@­V§MŇ˝˜`0Ż×ËÖĎĐŠS§ÂŁŁŁĆ7>pĺgĎŁŃx€ă¸1E@ůrccăCą.gM‚$dJiIä?9Ďó ťŽUmmíöŽŽŽcÁ`đ;˖-cőŻŻR$1‹ĹbdeeA.—'ý8óL‰D"̙3sćĚšâs#“‰"KlŽu˝+!_ýęW§&ŐEn™üžŒôĐx˝Ţ´¨â–H”Rô÷÷‡Çă7nüž@1|˛oňgInnîóҢQ'Č'!$şő8!D Ó566Ş9Ž{…ă¸oÇz.Fó´ÝnßÔÓÓăKľ.JĄE*Y­V8Î”­Ź+B ňóóQRR‚%K.UčĘçY˛d Žťî:äĺĺAĄPdl2Žěž411—ËŒń …ĂatuuĚfs}MM ÉÚÚÚţ€5r_$Ýľk׎lĄâ™M‚$dJéÔŹBČŇË=7Ţvďޝo0äććÚ|Ŕ˙ĺ8îŮXĎ[[[űśĂḽłłÓÍĆĽf.ҊąŰí°Ůll’ sM"ď#›ÍĆŢG1đů|čęęr:Î›6oŢ<ăőť÷ß˙ŻvěŘqó•Ÿyu!˙ů™R*ńűýI=6|ľ„ęüCäBČrŽă Çq×éőúĂnˇ{œRÚ0­Őţ ÇqߊőZ­öďÁ`°¤ŤŤëŹŐjeŸ×hzË&S[ÍĚŐ ƒSď§ÓÉ6}ˆŐjĽ===CVŤľdóćÍÇfúz˝^˙'žçŤyž?ÂqܝłSkkëDמmÜ˝{wţlœ[H‚$dľZýÎWÖĽTŕ׉ş6Çqeƒá]&BH .ńw ‰>žŘń™ŞŠŠqTWW/řÍéÓ§Y˙u "ăŞvťpťÝ,93SÂá0<ŹVëÔ$9ÖŽÉdâOœ8ń›ŞŞŞ…:nƧôzý„Űó-YBČ;Űśmű_łĽtoÔ]ąŰíNŠ$.F„źwď^Ľ4zoĚOéőúKvƒp×ČqÜŢK=~5śmŰśŠă¸#ŽSJ? ×`ó¸pb™ăĹ_|5–ëMWSSóő‘‘Ç{{{ƒl\9v‘šÂvťVŤ‡ f ČűŔfłą÷Á,šÜÇ8h2™žŞŽŽţⵞ‡ňSߚ)ĽąXüΎ;6Çc[[ۢ ‘lIőV˛`ÓXm6[!ÄšOŮČqܰÁ`¸+rLŻ×/ÓëőżĆůugs÷3˝ÎΝ;oá8î¨H$ęp Ś%bJéŻÂáđ§)ĽSÇ)ĽĆkúĽŽ@ŁŃ|gbbâŸ:;;]l—˜Ů3˝eÄfĎŚˇp8<őe,ŇSÂş¤gßďGWW—×l6WÖÖÖ>ËšŒFăĹbńzD%e˘p8üúöíŰb‹ôü%˘Ďëvť4 çŒ` yrÝŘ:љé:Jé~˝^Ďs"„|L‰^t~ŸÁ`ř?Ws~Žăîä8Ž/ p㴙Ľ”ň…BQŇÖÖV+•J˙%j‡Íf{<–ßír6oŢüŢŮłgôôôœu8Ź?m–E6ЎZ­°Z­Ź[;M„BĄŠ/]ěß5~ěv;íî˜˜___˙ÇŮ8ç /źpD,šRýY/"„´ëőúŤ*ęq)%%%{(Ľ‘EäAJéwc9ŸĐ_˙ĐÜÜ<ĎëőţŔÂ+=—Rz¤´´ôŸZZZ.ůuxűöíZąXüCJ颋Ÿ‚ţZ­ţňŢ˝{M°k׎lżßoĹ'_NŢ4ÚkřUfě­ˇŢzŤ¸¸ř óçĎgEDâ,RŽ3rKĺĽ?3)™Ş(ĽƒSëÓŮ0Oü={–yżŚŚćÖxœß`0Ź ”~ şd2°c˛ŠÖ5Ńëő/‰D˘Żđ<G[[۟cT@‚×+|ć™gF\ĎqÜn_P8ý9”ŇQBČ#mmmű/užíۡI$}@Éô‰”R*‰ţ …îݡoßéčÇ|>ßł„H2ډäÁX§ŤU]]]}řđáďšÝî‡ËËËĽéV#™D—ë$„@"‘@*•B*•B"‘¤t‚N”R„B!ƒÁŠ“ápýýýŤŐú-­VűĎńşNkkë1ƒÁ°†Rz@dˇŕŽăäFŁń߯弄‡D"ŃK­­­Gf-X$ݧĐîÝťKÝn÷˙&„,ŕy~XŠTžLÚ5š“Ç“.Ö ”ŇżBî˝ŘŚÔ“›\¸ !˛ÉCĆľłôŤ\ľýű÷Ż)((ř]yyy.Ű1J‘-“É’>A§C 9’€CĄBĄ›-—Ë…›ÇăŠÔjľOÄ5xŕĄP¨Ŕ%ë(Ľ_okk‹š0S*KŢOŤ ×ë˙›rŠumďQJďmkkëżÔë9Žű€=Q‡*FăťłäU˘”Š~ýë_˙Ą¨¨čÖ °Ś˛€"-čč[2íJ•Š 9O%ŕȍ%`a ń###]ďż˙ţ§/7 Ç] @δ‡ž4-WzýöíŰż …~ůĘ+ŻŘâŸPRúƒßfłm0}ŒRÚK)˝Éh4Ţzšd<éţ¨ŸG„JĆŔůŁŞŞŞ>;44ÔÜŐŐĺe]v‰Œ_z˝^8NX­VX,Řív¸Ýnřý~6‹ű2xžG €ÇăĂáŔÄÄÄT)TŻ×‹`0Č’ą€‚Á şşş&“iOmmíÚD'c0#–°L{č ˝^˙ý+˝^,M*•žlnnžwĽçŚ’”Nȓ3ľË !§|D)˝Š­­mE[[ŰŃ+˝–㸇pa—‰`ľYŁiľÚçĚfsyggçŤî•<.—¤].×ԎA™”¨Ăá0ź^/\.ěv;, &&&ŕp8ŕńx؄Ź$cľZéG}4nľZ—i4š§…ŒĹh4šŔRcŃÇ !]Š|1Ľt!$×ëőö Qé1^RşË:“[xE&ůŒFŁ IśëÔáÇ_/((Ř´xńbI2i2˙H,˙ĂM$A$Ĺ<>¨.kžçA)E8Ďó‡ĂܘÔA)Ĺɓ'Ăfłů7ľľľŐBÇ­ąąQ——× tÚC/ĆXŐŇŇ"`2B–ľśśO@¨q'ř,k! †zJiôlîŸ"ɒ1ÔÖÖn9|ř°ÖápüŹ˘˘B•••uĺ1IárI‹2•œŁ“ôô?ٟk§”N%ŘHwqä~tâţ“u+§ŸĎ‡ŻŰínܸqăaĄă™îŔŽĆĆĆ%yyyǔE=tÇq9FŁqKôóGFFîÄd2Ś”†FcZ$c C2€‹ú™{.ůLŐÖÖzůĺ—ç‡BĄKKK_wÝuŹŠœâ"IoŚ­Ě™&e‹Ĺ2u=&3 ó###‡ăćkŠE(466ŽČËËë°,ęĄz˝^˙F[[ŰTŠMBČ#ďiBˆi$ĽÇŻĹΝ;o‰.B)ý]˛ĎÔťďžűlŐŐŐe§OŸ~ŹłłÓÍĘnfŚ™śZY+7sů|>tvvúO:őDUUŐ ÉœŒ#80Ë|}œ˘Őëő˙š‡o‰zl !Ć]Ć%ä`0xAEžç*–™Ňh4O[,–…===˝gϞMş.v†a„g2™řžžžţ3gÎkľÚ˜ˇ‘M0Ţh4~ ŔߢBîÔëő™üš'tLłĄ­­í6‘HôVô1BČg!SUzŠŠŠţ+ń‘ĹOĆLâ8Ž€2J)| ™0s j–tttÜŤT*TVVŚVŠTB‡Ă$Đĺ–=1éĎăńŕřńăŻ×ű FŁyYčxâAŻ×wB6^ä!—Ńhœs‘ă)+cZČápxsT2!äy!ă™Mć'óz{{?:}út˜b`˜ôYWÜÝÝýWąX<7]“1´ľľiœ~<ÝƏ JČűöí{‰RŠţźîşëbÚx;Ů Ouuőڑ‘‘MG0›ÍŹ›aŇĐÄÄý裏ěcccőľľľˇÜyçiżěÂh4nĄ”žučxiié (N2ŚË:âţűď˙Ľ´¤ľľőnĄc‰§ŽŽŽçÔjőW—.]Ş`EŇë˛Î^Ż'Ožô9Ž}&aŰÄ&ƒÁĐJ)­,))Y.D îx˸„œIÚŰŰŐŮŮŮoĺççßzýő×KŘ~Ëé‡%äôÇó[SSă:&&>2ľRWF˜,đOŻżţú:ťÝŢ1oŢź"Vé‹aRÇřř8=sćŒŐétj˙,t€âŽŽŽ{déŇĽJ6›a’—ŰíĆŕŕ ×étţPŤŐ~]čx˜Ä`}˜DŁŃ|ăĉ…˝˝˝>~üxíšĚ0É% ˘żż?ŘŰŰűŽŮlÎeÉ8ł°r†1 Ÿ}íľ×Ęív{Gnnn٢E‹$ {+0ŒPÂá0†††B‹epllŹęŢ{ď=)tLLâąOá ŐĐĐp@E{{űÍvťýç‹,X fż&qxžÇđđpxllěœĎçÓhľÚż #–3œN§ű@ůÁƒď°Z­?),,œ_RR"Šu˙]†a.çyŒŒŒĐŃŃQŤÇăšŤŽŽ.­j23׆%dP__˙;×˙ň—żl˙Ńźyó Š‹‹ KĚ 3ťFGGŠÉdr:Î‡~,tß Űínill<$t\ 3S,!3)Šžžţw~íííĽ‹ĺI…BQ#—Ëóóóóeůůů„íD•žŹV+˜˜đű|ž‰@ ЇŸĐjľŁBÇĆ0ą` ™Iy“HśGîŸÇď÷Ÿöx<˙V__˙363šI',!3i'şőÜŇŇ"Y˝zľNĄP<$“ÉĘd2™:''G–““#b;S%J)<l6ďp8Á`Đáóůý~˙ÓuuuŻł̤3–™´ÖŇҰňŘż˙çrrrvI$’ŰĺryŽ\.ĎĘÍÍϙ3‡MK0ŸĎ§ÓIm6[8 z}>ŸÍď÷˙1 ţű–-[ŽĂ$KČLĆšűîť˙ŕ‘űżúŐŻ˛M&SJĽşW"‘TČd˛92™,kΜ9ľZM”J%[j#žçáńxŕvťŠĂáƒAŸßďwc~ż˙'ápř:Î%tœ #$–™Œ7š$ćǓ7@kkŤ277wƒRŠŹ—H$ˇËd˛ŠTŞ’ÉdRľZ-VŤŐDĄP€•úźP$ńz<ęršÂó܁@Ŕţěóů^+**úÍäŹy†a˘°„Ě01YÁéÉ۔ööö|• …bƒX,žU&“Í•H$*ąXœ%“ÉÄ …BŹT*IVVd2YÚľŹ)Ľđűýđűýđz˝Ôív‡@˜Rę ƒîP(d …BGÜn÷[çîťď>›Đ13LŞ` ™af`˛ĘÓk“ˇ źüňËš*•ęVŠTzŤL&űŹH$Z,•JUbąXN‘IĽRąH$’dee‰˛˛˛ˆT*%‰RŠRŠT°äÍó String { // access request identity if let Some(id) = id.identity() { format!("Logged in Secure User: {}\n", id) } else { "Welcome Anonymous!".to_owned() } } #[get("/")] async fn index(id: Identity) -> String { // access request identity if let Some(id) = id.identity() { format!("Welcome! {}", id) } else { println!("Found new anonymous user\n"); "Welcome Anonymous!\n".to_owned() } } #[post("/login")] async fn login(id: Identity, data: web::Data) -> HttpResponse { // here you might do whatever checks are needed to authenticate user // create a new identity and wrap it in an auth cookie let authenticated_user_id = Uuid::new_v4().to_string(); println!("Logged in user {}\n", authenticated_user_id); //create an implicit assertion (or you could create claim) let assertion = ImplicitAssertion::from(authenticated_user_id.as_str()); //creating a paseto key let key_val = data.paseto_key.as_bytes(); let key = PasetoSymmetricKey::::from(Key::from(key_val)); //create a token using the identity as an implicit assertion (you could also use a claim if //needed) let token = PasetoBuilder::::default() .set_implicit_assertion(assertion) .build(&key) .expect("Couldn't create paseto auth token"); // remember new authenticated identity id.remember(authenticated_user_id.to_string()); // return the response creating a new cookie to hold the token HttpResponse::build(StatusCode::OK).cookie(Cookie::build("auth-token", token).path("/").expires(OffsetDateTime::now_utc()).secure(false).http_only(true).same_site(SameSite::Lax).finish()).finish() } #[post("/logout")] async fn logout(id: Identity) -> String { println!("Logging out user {}\n", id.identity().expect("Couldn't get identity")); //build logout msg let logout_msg = format!("Goodbye {}!\n", id.identity().expect("Couldn't get identity")).to_owned(); // remove identity id.forget(); logout_msg } //shared state pub(crate) struct AppData { paseto_key: &'static str } #[actix_web::main] // or #[tokio::main] async fn main() -> std::io::Result<()> { HttpServer::new(move || { // create cookie identity backend (inside closure, since policy is not Clone) let policy = IdentityService::new(CookieIdentityPolicy::new(&[0; 32]) .name("auth-cookie") .secure(false)); // create a paseto cookie policy, for this use case, however I recommend using a middleware but // this shows how you might use a policy instead let paseto_policy = PasetoCookieIdentityPolicy {}; //paths that are not verified with the paseto token let unauthenticated_scope = web::scope("").service(services![index, login, logout]); //paths that should verify that a token exists and is valid let authenticated_scope = web::scope("/app").wrap(IdentityService::new(paseto_policy)).service(services![secure]); //create and run the server App::new() .app_data(web::Data::new(AppData { paseto_key: "wubbalubbadubdubwubbalubbadubdub"})) .wrap(policy) // wrap policy into middleware identity middleware .service(authenticated_scope) .service(unauthenticated_scope) }) .bind(("127.0.0.1", 8080))? .run() .await } rusty_paseto-0.7.1/examples/actix_identity/paseto.rs000064400000000000000000000037051046102023000210770ustar 00000000000000use crate::AppData; use actix_identity::{IdentityPolicy, RequestIdentity}; use actix_utils::future::{ready, Ready}; use actix_web::{ dev::{ServiceRequest, ServiceResponse}, error::{Error, Result}, web::Data, }; use rusty_paseto::prelude::*; pub struct PasetoCookieIdentityPolicy {} fn validate_auth_token(request: &mut ServiceRequest) -> Result, Error> { //try to find the cookie with the auth token, panic if not found //ideally we should map the errors to an http not authorized error let cookie = request .cookie("auth-token") .expect("No auth token found in PasetoCookieIdentityPolicy"); //now grab the token from the cookie let token: &str = cookie.value(); //get the identity from the identity cookie let identity = request.get_identity().expect("Couldn't find identity"); let id = identity.as_str(); //get the paseto key from the shared state let key_val = request.app_data::>().unwrap().paseto_key.as_bytes(); //create a paseto key let key = PasetoSymmetricKey::::from(Key::from(key_val)); //attempt to parse the token when accessing a secure path, in practice this should also map to an HTTP error PasetoParser::::default() .set_implicit_assertion(ImplicitAssertion::from(id)) .parse(token, &key) .map_err(|err_val| println!("{}", err_val)) .expect("Couldn't validate authentication token"); println!( "Validated auth token in PasetoCookieIdentityPolicy\n for user {}\n", id ); Ok(Some(identity)) } impl IdentityPolicy for PasetoCookieIdentityPolicy { type Future = Ready, Error>>; type ResponseFuture = Ready>; fn from_request(&self, request: &mut ServiceRequest) -> Self::Future { ready(validate_auth_token(request)) } fn to_response( &self, _identity: Option, _changed: bool, _response: &mut ServiceResponse, ) -> Self::ResponseFuture { ready(Ok(())) } } rusty_paseto-0.7.1/examples/actix_identity/readme.md000064400000000000000000000037651046102023000210230ustar 00000000000000# An example using a paseto token with the actix_identity crate This example creates a simple actix_web server using the default [CookieIdentityPolicy](https://docs.rs/actix-identity/latest/actix_identity/struct.CookieIdentityPolicy.html) as well as a custom [PasetoCookieIdentityPolicy](https://github.com/rrrodzilla/rusty_paseto/blob/main/examples/actix_identity/paseto.rs). The [CookieIdentityPolicy](https://docs.rs/actix-identity/latest/actix_identity/struct.CookieIdentityPolicy.html) is used for a an identity cookie. The latter policy is used to validate a PASETO authentication token that is stored in a separate cookie when the user logs in by making a POST request to the login endpoint. When a user makes a request to a secure endpoint `/app/secure`, the [PasetoCookieIdentityPolicy](https://github.com/rrrodzilla/rusty_paseto/blob/main/examples/actix_identity/paseto.rs) validates the PASETO token on each request using the [CookieIdentityPolicy's](https://docs.rs/actix-identity/latest/actix_identity/struct.CookieIdentityPolicy.html) identity as the implicit assertion. This means if a user comes from a different device or changes their cookies after logging in, the implicit assertion will fail and the PASETO won't be validated. This example panics when this happens, but in practice you would map the error to a Not Authorized HTTP error. ## Usage First run `cargo run --example actix_identity" to build and start the web server. Then run the following command from a separate shell (I'm using [Fish](https://fishshell.com/)) to execute a series of requests that do the following: ```fish curl http://localhost:8080;curl -X POST http://localhost:8080/login -c ~/cookies; curl http://localhost:8080/app/secure -b ~/cookies; curl -X POST http://localhost:8080/logout -b ~/cookies ``` 1) Visits the site as an anonymous user and then, 2) Login the user, creating a paseto token and storing it in a cookie using the identity as the implicit assertion and then, 3) Logout the user, forgetting the identity rusty_paseto-0.7.1/readme.md000064400000000000000000000576671046102023000141760ustar 00000000000000

rusty_paseto

A type-driven, ergonomic implementation of the PASETO protocol
for secure stateless tokens.

![unit tests](https://github.com/rrrodzilla/rusty_paseto/actions/workflows/rust.yml/badge.svg) ![GitHub](https://img.shields.io/github/license/rrrodzilla/rusty_paseto?label=License) [![unsafe forbidden](https://img.shields.io/badge/unsafe-forbidden-success.svg)](https://github.com/rust-secure-code/safety-dance/) [![Crates.io](https://img.shields.io/crates/v/rusty_paseto.svg)](https://crates.io/crates/rusty_paseto) [![Documentation](https://docs.rs/rusty_paseto/badge.svg)](https://docs.rs/rusty_paseto/) --- ## Table of Contents 1) [About PASETO](#user-content-about-paseto) 2) [Examples](#user-content-examples) - [Building and parsing tokens](#user-content-building-and-parsing-tokens) - [A default token](#user-content-a-default-token) - [A default parser](#user-content-a-default-parser) - [A token with a footer](#user-content-a-token-with-a-footer) - [A token with an implicit assertion (V3/V4 only)](#user-content-a-token-with-an-implicit-assertion-v3-or-v4-versioned-tokens-only) - [Setting a different expiration time](#user-content-setting-a-different-expiration-time) - [Tokens that never expire](#user-content-tokens-that-never-expire) - [Setting PASETO claims](#user-content-setting-paseto-claims) - [Setting your own Custom Claims](#user-content-setting-your-own-custom-claims) - [Validating claims](#user-content-validating-claims) - [Checking claims](#user-content-checking-claims) - [Custom validation](#user-content-custom-validation) 3) [Architecture](#user-content-architecture) - [Feature gates](#user-content-feature-gates) - [default](#user-content-default) - [batteries_included](#user-content-batteries_included) - [generic](#user-content-generic) - [core](#user-content-core) 4) [Roadmap and Current Feature Status](#user-content-roadmap-and-current-feature-status) - [PASETO Specification](#user-content-paseto-specification) - [PASERK Specification](#user-content-paserk-specification) 5) [Acknowledgments](#user-content-acknowledgments) 6) [Support](#user-content-support) 7) [Like this crate](#user-content-like-this-crate) --- ## About PASETO ### PASETO: Platform-Agnostic Security Tokens Paseto is everything you love about JOSE (JWT, JWE, JWS) without any of the [many design deficits that plague the JOSE standards](https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-bad-standard-that-everyone-should-avoid). **rusty_paseto** is meant to be flexible and configurable for your specific use case. Whether you want to [get started quickly](#user-content-default) with sensible defaults, [create your own version of rusty_paseto](#user-content-generic) in order to customize your own defaults and functionality or just want to use the [core PASETO crypto features](#user-content-core), the crate is heavily [feature gated](#user-content-feature-gates) to allow for your needs. ## Examples ### Building and parsing tokens Here's a basic, default token with the [batteries_included](#user-content-batteries_included) feature: ```rust use rusty_paseto::prelude::*; // create a key specifying the PASETO version and purpose let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); // use a default token builder with the same PASETO version and purpose let token = PasetoBuilder::::default().build(&key)?; // token is a String in the form: "v4.local.encoded-payload" ``` ### A default token * Has no [footer](https://github.com/paseto-standard/paseto-spec/tree/master/docs) * Has no [implicit assertion](https://github.com/paseto-standard/paseto-spec/tree/master/docs) for V3 or V4 versioned tokens * Expires in **1 hour** after creation (due to an included default ExpirationClaim) * Contains an IssuedAtClaim defaulting to the current utc time the token was created * Contains a NotBeforeClaim defaulting to the current utc time the token was created You can parse and validate an existing token with the following: ```rust let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); // now we can parse and validate the token with a parser that returns a serde_json::Value let json_value = PasetoParser::::default().parse(&token, &key)?; //the ExpirationClaim assert!(json_value["exp"].is_string()); //the IssuedAtClaim assert!(json_value["iat"].is_string()); ``` ### A default parser * Validates the token structure and decryptes the payload or verifies the signature of the content * Validates the [footer](https://github.com/paseto-standard/paseto-spec/tree/master/docs) if one was provided * Validates the [implicit assertion](https://github.com/paseto-standard/paseto-spec/tree/master/docs) if one was provided (for V3 or V4 versioned tokens only)
back to toc
### A token with a footer PASETO tokens can have an [optional footer](https://github.com/paseto-standard/paseto-spec/tree/master/docs). In rusty_paseto we have strict types for most things. So we can extend the previous example to add a footer to the token by using code like the following: ```rust use rusty_paseto::prelude::*; let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); let token = PasetoBuilder::::default() // note how we set the footer here .set_footer(Footer::from("Sometimes science is more art than science")) .build(&key)?; // token is now a String in the form: "v4.local.encoded-payload.footer" ``` And parse it by passing in the same expected footer ```rust // now we can parse and validate the token with a parser that returns a serde_json::Value let json_value = PasetoParser::::default() .set_footer(Footer::from("Sometimes science is more art than science")) .parse(&token, &key)?; //the ExpirationClaim assert!(json_value["exp"].is_string()); //the IssuedAtClaim assert!(json_value["iat"].is_string()); ```
back to toc
### A token with an implicit assertion (V3 or V4 versioned tokens only) Version 3 (V3) and Version 4 (V4) PASETO tokens can have an [optional implicit assertion](https://github.com/paseto-standard/paseto-spec/tree/master/docs). So we can extend the previous example to add an implicit assertion to the token by using code like the following: ```rust use rusty_paseto::prelude::*; let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); let token = PasetoBuilder::::default() .set_footer(Footer::from("Sometimes science is more art than science")) // note how we set the implicit assertion here .set_implicit_assertion(ImplicitAssertion::from("There’s a lesson here, and I’m not going to be the one to figure it out.")) .build(&key)?; // token is now a String in the form: "v4.local.encoded-payload.footer" ``` And parse it by passing in the same expected implicit assertion at parse time ```rust // now we can parse and validate the token with a parser that returns a serde_json::Value let json_value = PasetoParser::::default() .set_footer(Footer::from("Sometimes science is more art than science")) .set_implicit_assertion(ImplicitAssertion::from("There’s a lesson here, and I’m not going to be the one to figure it out.")) .parse(&token, &key)?; ```
back to toc
### Setting a different expiration time As mentioned, default tokens expire **1 hour** from creation time. You can set your own expiration time by adding an ExpirationClaim which takes an ISO 8601 (Rfc3339) compliant datetime string. #### Note: *claims taking an ISO 8601 (Rfc3339) string use the TryFrom trait and return a Result<(),PasetoClaimError>* ```rust use rusty_paseto::prelude::*; // must include use std::convert::TryFrom; let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); // real-world example using the time crate to expire 5 minutes from now let token = PasetoBuilder::::default() // note the TryFrom implmentation for ExpirationClaim //.set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(ExpirationClaim::try_from(in_5_minutes)?) .set_footer(Footer::from("Sometimes science is more art than science")) .build(&key)?; // token is a String in the form: "v4.local.encoded-payload.footer" ```
back to toc
### Tokens that never expire A **1 hour** ExpirationClaim is set by default because the use case for non-expiring tokens in the world of security tokens is fairly limited. Omitting an expiration claim or forgetting to require one when processing them is almost certainly an oversight rather than a deliberate choice. When it is a deliberate choice, you have the opportunity to deliberately remove this claim from the Builder. The method call required to do so ensures readers of the code understand the implicit risk. ```rust let token = PasetoBuilder::::default() .set_claim(ExpirationClaim::try_from(in_5_minutes)?) // even if you set an expiration claim (as above) it will be ignored // due to the method call below .set_no_expiration_danger_acknowledged() .build(&key)?; ```
back to toc
### Setting PASETO Claims The PASETO specification includes [seven reserved claims](https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/04-Claims.md) which you can set with their explicit types: ```rust // real-world example using the time crate to prevent the token from being used before 2 // minutes from now let in_2_minutes = (time::OffsetDateTime::now_utc() + time::Duration::minutes(2)).format(&Rfc3339)?; let token = PasetoBuilder::::default() //json payload key: "exp" .set_claim(ExpirationClaim::try_from(in_5_minutes)?) //json payload key: "iat" // the IssueAtClaim is automatically set to UTC NOW by default // but you can override it here // .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) //json payload key: "nbf" //don't use this token before two minutes after UTC NOW .set_claim(NotBeforeClaim::try_from(in_2_minutes)?) //json payload key: "aud" .set_claim(AudienceClaim::from("Cromulons")) //json payload key: "sub" .set_claim(SubjectClaim::from("Get schwifty")) //json payload key: "iss" .set_claim(IssuerClaim::from("Earth Cesium-137")) //json payload key: "jti" .set_claim(TokenIdentifierClaim::from("Planet Music - Season 988")) .build(&key)?; ```
back to toc
### Setting your own Custom Claims The CustomClaim struct takes a tuple in the form of `(key: String, value: T)` where T is any serializable type #### Note: *CustomClaims use the TryFrom trait and return a Result<(), PasetoClaimError> if you attempt to use one of the [reserved PASETO keys](https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/04-Claims.md) in your CustomClaim* ```rust let token = PasetoBuilder::::default() .set_claim(CustomClaim::try_from(("Co-star", "Morty Smith"))?) .set_claim(CustomClaim::try_from(("Universe", 137))?) .build(&key)?; ``` This throws an error: ```rust // "exp" is a reserved PASETO claim key, you should use the ExpirationClaim type let token = PasetoBuilder::::default() .set_claim(CustomClaim::try_from(("exp", "Some expiration value"))?) .build(&key)?; ```
back to toc
### Validating claims rusty_paseto allows for flexible claim validation at parse time #### Checking claims Let's see how we can check particular claims exist with expected values. ```rust // use a default token builder with the same PASETO version and purpose let token = PasetoBuilder::::default() .set_claim(SubjectClaim::from("Get schwifty")) .set_claim(CustomClaim::try_from(("Contestant", "Earth"))?) .set_claim(CustomClaim::try_from(("Universe", 137))?) .build(&key)?; PasetoParser::::default() // you can check any claim even custom claims .check_claim(SubjectClaim::from("Get schwifty")) .check_claim(CustomClaim::try_from(("Contestant", "Earth"))?) .check_claim(CustomClaim::try_from(("Universe", 137))?) .parse(&token, &key)?; // no need for the assertions below since the check_claim methods // above accomplish the same but at parse time! //assert_eq!(json_value["sub"], "Get schwifty"); //assert_eq!(json_value["Contestant"], "Earth"); //assert_eq!(json_value["Universe"], 137); ```
back to toc
#### Custom validation What if we have more complex validation requirements? You can pass in a reference to a closure which receives the key and value of the claim you want to validate so you can implement any validation logic you choose. Let's see how we can validate our tokens only contain universe values with prime numbers: ```rust // use a default token builder with the same PASETO version and purpose let token = PasetoBuilder::::default() .set_claim(SubjectClaim::from("Get schwifty")) .set_claim(CustomClaim::try_from(("Contestant", "Earth"))?) .set_claim(CustomClaim::try_from(("Universe", 137))?) .build(&key)?; PasetoParser::::default() .check_claim(SubjectClaim::from("Get schwifty")) .check_claim(CustomClaim::try_from(("Contestant", "Earth"))?) .validate_claim(CustomClaim::try_from("Universe")?, &|key, value| { //let's get the value let universe = value .as_u64() .ok_or(PasetoClaimError::Unexpected(key.to_string()))?; // we only accept prime universes in this app if primes::is_prime(universe) { Ok(()) } else { Err(PasetoClaimError::CustomValidation(key.to_string())) } }) .parse(&token, &key)?; ``` This token will fail to parse with the validation code above: ```rust // 136 is not a prime number let token = PasetoBuilder::::default() .set_claim(CustomClaim::try_from(("Universe", 136))?) .build(&key)?; ```
back to toc
--- ## Architecture The rusty_paseto crate architecture is composed of three layers (batteries_included, generic and core) which can be further refined by the PASETO version(s) and purpose(s) required for your needs. All layers use a common crypto core which includes various cipher crates depending on the version and purpose you choose. The crate is heavily featured gated to allow you to use only the versions and purposes you need for your app which minimizes download compile times for using rusty_paseto. A description of each architectural layer, their uses and limitations and how to minimize your required dependencies based on your required PASETO version and purpose follows: batteries_included --> generic --> core
back to toc
### Feature gates Valid version/purpose feature combinations are as follows: - "v1_local" (NIST Original Symmetric Encryption) - "v2_local" (Sodium Original Symmetric Encryption) - "v3_local" (NIST Modern Symmetric Encryption) - "v4_local" (Sodium Modern Symmetric Encryption) - "v1_public" (NIST Original Asymmetric Authentication) - "v2_public" (Sodium Original Asymmetric Authentication) - "v3_public" (NIST Modern Asymmetric Authentication) - "v4_public" (Sodium Modern Asymmetric Authentication)
back to toc
#### default The default feature is the quickest way to get started using rusty_paseto. The default feature includes the outermost architectural layer called batteries_included (described below) as well as the two latest PASETO versions (V3 - NIST MODERN, V4 - SODIUM MODERN) and the Public (Asymmetric) and Local (Symmetric) purposed key types for each of these versions. That should be four specific version and purpose combinations however at the time of this writing I have yet to implement the V3 - Public combination, so there are 3 in the default feature. Additionally, this feature includes JWT style claims and business rules for your PASETO token (default, but customizable expiration, issued at, not-before times, etc as described in the usage documentation and examples further below). ```toml ## Includes V3 (local) and V4 (local, public) versions, ## purposes and ciphers. rusty_paseto = "latest" ``` ``` // at the top of your source file use rusty_paseto::prelude::*; ```
back to toc
#### batteries_included The outermost architectural layer is called batteries_included. This is what most people will need. This feature includes JWT style claims and business rules for your PASETO token (default, but customizable expiration, issued at, not-before times, etc as described in the usage documentation and examples below). You must specify a version and purpose with this feature in order to reduce the size of your dependencies like in the following Cargo.toml entry which only includes the V4 - Local types with batteries_included functionality: ```toml ## Includes only v4 modern sodium cipher crypto core ## and local (symmetric) key types with all claims and default business rules. rusty_paseto = {version = "latest", features = ["batteries_included", "v4_local"] } ``` ``` // at the top of your source file use rusty_paseto::prelude::*; ```
back to toc
#### generic The generic architectural and feature layer allows you to create your own custom version of the batteries_included layer by following the same pattern I've used in the source code to create your own custom builder and parser. This is probably not what you need as it is for advanced usage. The feature includes a generic builder and parser along with claims for you to extend. It includes all the PASETO and custom claims but allows you to create different default claims in your custom builder and parser or use a different time crate or make up your own default business rules. As with the batteries_included layer, parsed tokens get returned as a serder_json Value. Again, specify the version and purpose to include in the crypto core: ```toml ## Includes only v4 modern sodium cipher crypto core and local (symmetric) ## key types with all claims and default business rules. rusty_paseto = {version = "latest", features = ["generic", "v4_local"] } ``` ``` // at the top of your source file use rusty_paseto::generic::*; ```
back to toc
#### core The core architectural layer is the most basic PASETO implementation as it accepts a Payload, optional Footer and (if v3 or v4) an optional Implicit Assertion along with the appropriate key to encrypt/sign and decrypt/verify basic strings. There are no default claims or included claim structures, business rules or anything other than basic PASETO crypto functions. Serde crates are not included in this feature so it is extremely lightweight. You can use this when you don't need JWT-esque functionality but still want to leverage the safe cipher combinations and algorithm lucidity afforded by the PASETO specification. ```toml ## Includes only v4 modern sodium cipher crypto core and local (symmetric) ## key types with NO claims, defaults or validation, just basic PASETO ## encrypt/signing and decrypt/verification. rusty_paseto = {version = "latest", features = ["core", "v4_local"] } ```
back to toc
--- ## Roadmap and Current Feature Status ### [PASETO](https://github.com/paseto-standard/paseto-spec) specification | APIs, Tests & Documentation |v1.L|v1.P|v2.L|v2.P|v3.L|v3.P|v4.L|v4.P| | ------------: |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| | PASETO Token Builder |🟢|🟢|🟢|🟢|🟢|🟢|🟢|🟢| | PASETO Token Parser |🟢|🟢|🟢|🟢|🟢|🟢|🟢|🟢| | Flexible Claim Validation |🟢|🟢|🟢|🟢|🟢|🟢|🟢|🟢| | Generic Token Builder |🟢|🟢|🟢|🟢|🟢|🟢|🟢|🟢| | Generic Token Parser |🟢|🟢|🟢|🟢|🟢|🟢|🟢|🟢| | Encryption/Signing |🟢|🟢|🟢|🟢|🟢|🟢|🟢|🟢| | Decryption/Verification |🟢|🟢|🟢|🟢|🟢|🟢|🟢|🟢| | [PASETO Test vectors](https://github.com/paseto-standard/test-vectors) |🟢|🟢|🟢|🟢|🟢|🟢|🟢|🟢| | Feature - core |🟢|🟢|🟢|🟢|🟢|🟢|🟢|🟢| | Feature - generic |🟢|🟢|🟢|🟢|🟢|🟢|🟢|🟢| | Feature - batteries_included |🟢|🟢|🟢|🟢|🟢|🟢|🟢|🟢| | Docs - [core](#user-content-core) |🟢|🟢|🟢|🟢|🟢|🟢|🟢|🟢| | Docs - [generic](#user-content-generic) |🟢|🟢|🟢|🟢|🟢|🟢|🟢|🟢| | Docs - [batteries_included](#user-content-batteries_included) |🟢|🟢|🟢|🟢|🟢|🟢|🟢|🟢|

🟢 - completed âšŤ - planned

back to toc
### [PASERK](https://github.com/paseto-standard/paserk) specification |lid|local|seal|local-wrap|local-pw|sid|public|pid|secret|secret-wrap|secret-pw| |:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:| |⚫|⚫|⚫|⚫|⚫|⚫|⚫|⚫|⚫|⚫|⚫|

🟢 - completed âšŤ - planned

back to toc
--- ## Support File an [issue](https://github.com/rrrodzilla/rusty_paseto/issues/new/choose) or hit me up on [Twitter](https://twitter.com/rrrodzilla)!
back to toc
--- ## Acknowledgments If the API of this crate doesn't suit your tastes, check out the other PASETO implementations in the Rust ecosystem which inspired rusty_paseto: - [paseto](https://crates.io/crates/paseto) - by [Cynthia Coan](https://crates.io/users/Mythra) - [pasetors](https://crates.io/crates/pasetors) - by [Johannes](https://crates.io/users/brycx)
back to toc
--- ## Like this crate? ⭐ Star https://github.com/rrrodzilla/rusty_paseto 🐦 Follow https://twitter.com/rrrodzilla ---
readme created with cargo-markdown
rusty_paseto-0.7.1/readme_crates_io.md000064400000000000000000000467421046102023000162160ustar 00000000000000# rusty_paseto A type-driven, ergonomic implementation of the [PASETO](https://github.com/paseto-standard/paseto-spec) protocol for secure stateless tokens. ### PASETO: Platform-Agnostic Security Tokens Paseto is everything you love about JOSE (JWT, JWE, JWS) without any of the [many design deficits that plague the JOSE standards](https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-bad-standard-that-everyone-should-avoid). ![unit tests](https://github.com/rrrodzilla/rusty_paseto/actions/workflows/rust.yml/badge.svg) ![GitHub](https://img.shields.io/github/license/rrrodzilla/rusty_paseto?label=License) ## Roadmap and Current Feature Status | APIs, Tests & Documentation | v1.
local| v1.
public | v2.
local | v2.
public |v3.
local | v3.
public | v4.
local | v4.
public | | ------------: | :-----------: | :----------: |:-----------: |:-----------: |:-----------: |:-----------: |:-----------: |:-----------: | | PASETO Token Builder | - [x] | - [x] | - [x] | - [x] | - [x] | - [ ] | - [x] | - [x] | | PASETO Token Parser | - [x] | - [x] | - [x] | - [x] | - [x] | - [ ] | - [x] | - [x] | | Flexible Claim Validation | - [x] | - [x] | - [x] | - [x] | - [x] | - [ ] | - [x] | - [x] | | Generic Token Builder | - [x] | - [x] | - [x] | - [x] | - [x] | - [ ] | - [x] | - [x] | | Generic Token Parser | - [x] | - [x] | - [x] | - [x] | - [x] | - [ ] | - [x] | - [x] | | Encryption/Signing | - [x] | - [x] | - [x] | - [x] | - [x] | - [ ] | - [x] | - [x] | | Decryption/Verification | - [x] | - [x] | - [x] | - [x] | - [x] | - [ ] | - [x] | - [x] | | [PASETO Test vectors](https://github.com/paseto-standard/test-vectors) | - [x] | - [x] | - [x] | - [x] | - [x] | - [ ] | - [x] | - [x] | | Documentation | - [ ] | - [ ] | - [ ] | - [ ] | - [ ] | - [ ] | - [ ] | - [ ] | | Feature | Status | | ------------: | :-----------: | | Feature gates | - [x] | | PASERK support | - [ ] | # Usage rusty_paseto is meant to be flexible and configurable for your specific use case. Whether you want to get started quickly with sensible defaults, create your own version of rusty_paseto in order to customize your own defaults and functionality or just want to use the core PASETO crypto features, the crate is heavily feature gated to allow for your needs. ## Architecture The rusty_paseto crate architecture is composed of three layers (batteries_included, generic and core) which can be further refined by the PASETO version(s) and purpose(s) required for your needs. All layers use a common crypto core which includes various cipher crates depending on the version and purpose you choose. The crate is heavily featured gated to allow you to use only the versions and purposes you need for your app which minimizes download compile times for using rusty_paseto. A description of each architectural layer, their uses and limitations and how to minimize your required dependencies based on your required PASETO version and purpose follows: ![paseto_batteries_included_small](https://user-images.githubusercontent.com/24578097/147881895-36878b22-bf17-49e4-98d7-f94920353368.png) ![paseto_generic_small](https://user-images.githubusercontent.com/24578097/147881907-a765ede6-c8e5-44ff-9845-db53f0634f07.png) ![paseto_core_small](https://user-images.githubusercontent.com/24578097/147881920-14c52256-1a0c-40be-9f18-759a8c9ad77d.png) batteries_included --> generic --> core ### default The default feature is the quickest way to get started using rusty_paseto. ![paseto_default_small](https://user-images.githubusercontent.com/24578097/147882602-0a88c55e-3ba9-4545-ba99-867406ac9c76.png) The default feature includes the outermost architectural layer called batteries_included (described below) as well as the two latest PASETO versions (V3 - NIST MODERN, V4 - SODIUM MODERN) and the Public (Asymmetric) and Local (Symmetric) purposed key types for each of these versions. That should be four specific version and purpose combinations however at the time of this writing I have yet to implement the V3 - Public combination, so there are 3 in the default feature. Additionally, this feature includes JWT style claims and business rules for your PASETO token (default, but customizable expiration, issued at, not-before times, etc as described in the usage documentation and examples further below). ```toml ## Includes V3 (local) and V4 (local, public) versions, purposes and ciphers. rusty_paseto = "latest" ``` ``` // at the top of your source file use rusty_paseto::prelude::*; ``` ### batteries_included The outermost architectural layer is called batteries_included. This is what most people will need. This feature includes JWT style claims and business rules for your PASETO token (default, but customizable expiration, issued at, not-before times, etc as described in the usage documentation and examples below). ![paseto_batteries_included_small](https://user-images.githubusercontent.com/24578097/147881895-36878b22-bf17-49e4-98d7-f94920353368.png) You must specify a version and purpose with this feature in order to reduce the size of your dependencies like in the following Cargo.toml entry which only includes the V4 - Local types with batteries_included functionality: ```toml ## Includes only v4 modern sodium cipher crypto core and local (symmetric) ## key types with all claims and default business rules. rusty_paseto = {version = "latest", features = ["batteries_included", "v4_local"] } ``` ![paseto_batteries_included_v4_local_small](https://user-images.githubusercontent.com/24578097/147882822-46dac1d1-a922-4301-be45-d3341dabfee1.png) #### Feature gates Valid version/purpose feature combinations are as follows: - "v1_local" (NIST Original Symmetric Encryption) - "v2_local" (Sodium Original Symmetric Encryption) - "v3_local" (NIST Modern Symmetric Encryption) - "v4_local" (Sodium Modern Symmetric Encryption) - "v1_public" (NIST Original Asymmetric Authentication) - "v2_public" (Sodium Original Asymmetric Authentication) - *"v3_public" (NIST Modern Asymmetric Authentication)* - **NOT YET IMPLEMENTED** - "v4_public" (Sodium Modern Asymmetric Authentication) ``` // at the top of your source file use rusty_paseto::prelude::*; ``` ### generic The generic architectural and feature layer allows you to create your own custom version of the batteries_included layer by following the same pattern I've used in the source code to create your own custom builder and parser. This is probably not what you need as it is for advanced usage. The feature includes a generic builder and parser along with claims for you to extend. ![paseto_generic_small](https://user-images.githubusercontent.com/24578097/147881907-a765ede6-c8e5-44ff-9845-db53f0634f07.png) It includes all the PASETO and custom claims but allows you to create different default claims in your custom builder and parser or use a different time crate or make up your own default business rules. As with the batteries_included layer, parsed tokens get returned as a serde_json Value. Again, specify the version and purpose to include in the crypto core: ```toml ## Includes only v4 modern sodium cipher crypto core and local (symmetric) ## key types with all claims and default business rules. rusty_paseto = {version = "latest", features = ["generic", "v4_local"] } ``` ``` // at the top of your source file use rusty_paseto::generic::*; ``` ### core The core architectural layer is the most basic PASETO implementation as it accepts a Payload, optional Footer and (if v3 or v4) an optional Implicit Assertion along with the appropriate key to encrypt/sign and decrypt/verify basic strings. ![paseto_core_small](https://user-images.githubusercontent.com/24578097/147881920-14c52256-1a0c-40be-9f18-759a8c9ad77d.png) There are no default claims or included claim structures, business rules or anything other than basic PASETO crypto functions. Serde crates are not included in this feature so it is extremely lightweight. You can use this when you don't need JWT-esque functionality but still want to leverage the safe cipher combinations and algorithm lucidity afforded by the PASETO specification. ```toml ## Includes only v4 modern sodium cipher crypto core and local (symmetric) ## key types with NO claims, defaults or validation, just basic PASETO ## encrypt/signing and decrypt/verification. rusty_paseto = {version = "latest", features = ["core", "v4_local"] } ``` # Examples ## Building and parsing tokens with batteries_included Here's a basic, default token: ```rust use rusty_paseto::prelude::*; // create a key specifying the PASETO version and purpose let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); // use a default token builder with the same PASETO version and purpose let token = PasetoBuilder::::default().build(&key)?; // token is a String in the form: "v4.local.encoded-payload" ``` ## A default token * Has no [footer](https://github.com/paseto-standard/paseto-spec/tree/master/docs) * Has no [implicit assertion](https://github.com/paseto-standard/paseto-spec/tree/master/docs) for V3 or V4 versioned tokens * Expires in **1 hour** after creation (due to an included default ExpirationClaim) * Contains an IssuedAtClaim defaulting to the current utc time the token was created * Contains a NotBeforeClaim defaulting to the current utc time the token was created You can parse and validate an existing token with the following: ```rust let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); // now we can parse and validate the token with a parser that returns a serde_json::Value let json_value = PasetoParser::::default().parse(&token, &key)?; //the ExpirationClaim assert!(json_value["exp"].is_string()); //the IssuedAtClaim assert!(json_value["iat"].is_string()); ``` ## A default parser * Validates the token structure and decryptes the payload or verifies the signature of the content * Validates the [footer](https://github.com/paseto-standard/paseto-spec/tree/master/docs) if one was provided * Validates the [implicit assertion](https://github.com/paseto-standard/paseto-spec/tree/master/docs) if one was provided (for V3 or V4 versioned tokens only) ## A token with a footer PASETO tokens can have an [optional footer](https://github.com/paseto-standard/paseto-spec/tree/master/docs). In rusty_paseto we have strict types for most things. So we can extend the previous example to add a footer to the token by using code like the following: ```rust use rusty_paseto::prelude::*; let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); let token = PasetoBuilder::::default() // note how we set the footer here .set_footer(Footer::from("Sometimes science is more art than science")) .build(&key)?; // token is now a String in the form: "v4.local.encoded-payload.footer" ``` And parse it by passing in the same expected footer ```rust // now we can parse and validate the token with a parser that returns a serde_json::Value let json_value = PasetoParser::::default() .set_footer(Footer::from("Sometimes science is more art than science")) .parse(&token, &key)?; //the ExpirationClaim assert!(json_value["exp"].is_string()); //the IssuedAtClaim assert!(json_value["iat"].is_string()); ``` ## A token with an implicit assertion (V3 or V4 versioned tokens only) Version 3 (V3) and Version 4 (V4) PASETO tokens can have an [optional implicit assertion](https://github.com/paseto-standard/paseto-spec/tree/master/docs). So we can extend the previous example to add an implicit assertion to the token by using code like the following: ```rust use rusty_paseto::prelude::*; let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); let token = PasetoBuilder::::default() .set_footer(Footer::from("Sometimes science is more art than science")) // note how we set the implicit assertion here .set_implicit_assertion(ImplicitAssertion::from("There’s a lesson here, and I’m not going to be the one to figure it out.")) .build(&key)?; // token is now a String in the form: "v4.local.encoded-payload.footer" ``` And parse it by passing in the same expected implicit assertion at parse time ```rust // now we can parse and validate the token with a parser that returns a serde_json::Value let json_value = PasetoParser::::default() .set_footer(Footer::from("Sometimes science is more art than science")) .set_implicit_assertion(ImplicitAssertion::from("There’s a lesson here, and I’m not going to be the one to figure it out.")) .parse(&token, &key)?; ``` ## Setting a different expiration time As mentioned, default tokens expire **1 hour** from creation time. You can set your own expiration time by adding an ExpirationClaim which takes an ISO 8601 (Rfc3339) compliant datetime string. #### Note: *claims taking an ISO 8601 (Rfc3339) string use the TryFrom trait and return a Result<(),PasetoClaimError>* ```rust use rusty_paseto::prelude::*; // must include use std::convert::TryFrom; let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); // real-world example using the time crate to expire 5 minutes from now let token = PasetoBuilder::::default() // note the TryFrom implmentation for ExpirationClaim //.set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(ExpirationClaim::try_from(in_5_minutes)?) .set_footer(Footer::from("Sometimes science is more art than science")) .build(&key)?; // token is a String in the form: "v4.local.encoded-payload.footer" ``` ## Tokens that never expire A **1 hour** ExpirationClaim is set by default because the use case for non-expiring tokens in the world of security tokens is fairly limited. Omitting an expiration claim or forgetting to require one when processing them is almost certainly an oversight rather than a deliberate choice. When it is a deliberate choice, you have the opportunity to deliberately remove this claim from the Builder. The method call required to do so ensures readers of the code understand the implicit risk. ```rust let token = PasetoBuilder::::default() .set_claim(ExpirationClaim::try_from(in_5_minutes)?) // even if you set an expiration claim (as above) it will be ignored // due to the method call below .set_no_expiration_danger_acknowledged() .build(&key)?; ``` ## Setting PASETO Claims The PASETO specification includes [seven reserved claims](https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/04-Claims.md) which you can set with their explicit types: ```rust // real-world example using the time crate to prevent the token from being used before 2 // minutes from now let in_2_minutes = (time::OffsetDateTime::now_utc() + time::Duration::minutes(2)).format(&Rfc3339)?; let token = PasetoBuilder::::default() //json payload key: "exp" .set_claim(ExpirationClaim::try_from(in_5_minutes)?) //json payload key: "iat" // the IssueAtClaim is automatically set to UTC NOW by default // but you can override it here // .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) //json payload key: "nbf" //don't use this token before two minutes after UTC NOW .set_claim(NotBeforeClaim::try_from(in_2_minutes)?) //json payload key: "aud" .set_claim(AudienceClaim::from("Cromulons")) //json payload key: "sub" .set_claim(SubjectClaim::from("Get schwifty")) //json payload key: "iss" .set_claim(IssuerClaim::from("Earth Cesium-137")) //json payload key: "jti" .set_claim(TokenIdentifierClaim::from("Planet Music - Season 988")) .build(&key)?; ``` ## Setting your own Custom Claims The CustomClaim struct takes a tuple in the form of `(key: String, value: T)` where T is any serializable type #### Note: *CustomClaims use the TryFrom trait and return a Result<(), PasetoClaimError> if you attempt to use one of the [reserved PASETO keys](https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/04-Claims.md) in your CustomClaim* ```rust let token = PasetoBuilder::::default() .set_claim(CustomClaim::try_from(("Co-star", "Morty Smith"))?) .set_claim(CustomClaim::try_from(("Universe", 137))?) .build(&key)?; ``` This throws an error: ```rust // "exp" is a reserved PASETO claim key, you should use the ExpirationClaim type let token = PasetoBuilder::::default() .set_claim(CustomClaim::try_from(("exp", "Some expiration value"))?) .build(&key)?; ``` # Validating claims rusty_paseto allows for flexible claim validation at parse time ## Checking claims Let's see how we can check particular claims exist with expected values. ```rust // use a default token builder with the same PASETO version and purpose let token = PasetoBuilder::::default() .set_claim(SubjectClaim::from("Get schwifty")) .set_claim(CustomClaim::try_from(("Contestant", "Earth"))?) .set_claim(CustomClaim::try_from(("Universe", 137))?) .build(&key)?; PasetoParser::::default() // you can check any claim even custom claims .check_claim(SubjectClaim::from("Get schwifty")) .check_claim(CustomClaim::try_from(("Contestant", "Earth"))?) .check_claim(CustomClaim::try_from(("Universe", 137))?) .parse(&token, &key)?; // no need for the assertions below since the check_claim methods // above accomplish the same but at parse time! //assert_eq!(json_value["sub"], "Get schwifty"); //assert_eq!(json_value["Contestant"], "Earth"); //assert_eq!(json_value["Universe"], 137); ``` # Custom validation What if we have more complex validation requirements? You can pass in a reference to a closure which receives the key and value of the claim you want to validate so you can implement any validation logic you choose. Let's see how we can validate our tokens only contain universe values with prime numbers: ```rust // use a default token builder with the same PASETO version and purpose let token = PasetoBuilder::::default() .set_claim(SubjectClaim::from("Get schwifty")) .set_claim(CustomClaim::try_from(("Contestant", "Earth"))?) .set_claim(CustomClaim::try_from(("Universe", 137))?) .build(&key)?; PasetoParser::::default() .check_claim(SubjectClaim::from("Get schwifty")) .check_claim(CustomClaim::try_from(("Contestant", "Earth"))?) .validate_claim(CustomClaim::try_from("Universe")?, &|key, value| { //let's get the value let universe = value .as_u64() .ok_or(PasetoClaimError::Unexpected(key.to_string()))?; // we only accept prime universes in this app if primes::is_prime(universe) { Ok(()) } else { Err(PasetoClaimError::CustomValidation(key.to_string())) } }) .parse(&token, &key)?; ``` This token will fail to parse with the validation code above: ```rust // 136 is not a prime number let token = PasetoBuilder::::default() .set_claim(CustomClaim::try_from(("Universe", 136))?) .build(&key)?; ``` # Acknowledgments If the API of this crate doesn't suit your tastes, check out the other PASETO implementations in the Rust ecosystem which inspired rusty_paseto: - [paseto](https://crates.io/crates/paseto) - by [Cynthia Coan](https://crates.io/users/Mythra) - [pasetors](https://crates.io/crates/pasetors) - by [Johannes](https://crates.io/users/brycx) # Questions? File an issue or hit me up on [Twitter](https://twitter.com/rrrodzilla)! rusty_paseto-0.7.1/rustfmt.toml000064400000000000000000000001631046102023000147730ustar 00000000000000tab_spaces = 2 max_width = 120 imports_granularity = "Crate" use_field_init_shorthand = true merge_imports = true rusty_paseto-0.7.1/src/core/common/authentication_key.rs000064400000000000000000000010471046102023000216400ustar 00000000000000use std::marker::PhantomData; use std::ops::Deref; pub(crate) struct AuthenticationKey { pub(crate) version: PhantomData, pub(crate) purpose: PhantomData, pub(crate) key: Vec, } impl AsRef<[u8]> for AuthenticationKey { fn as_ref(&self) -> &[u8] { &self.key } } impl Deref for AuthenticationKey { type Target = [u8]; fn deref(&self) -> &Self::Target { &self.key } } rusty_paseto-0.7.1/src/core/common/authentication_key_impl/mod.rs000064400000000000000000000000531046102023000234340ustar 00000000000000mod v1_local; mod v3_local; mod v4_local;rusty_paseto-0.7.1/src/core/common/authentication_key_impl/v1_local.rs000064400000000000000000000014761046102023000243670ustar 00000000000000#![cfg(feature = "v1_local")] use std::marker::PhantomData; use ring::hkdf; use crate::core::{Local, PasetoError, PasetoNonce, PasetoSymmetricKey, V1}; use crate::core::common::authentication_key::AuthenticationKey; use crate::core::common::hkdf_key::HkdfKey; impl AuthenticationKey { pub(crate) fn try_from( message: &[u8; 24], key: &PasetoSymmetricKey, nonce: &PasetoNonce, ) -> Result { let info = message.as_ref(); let salt = hkdf::Salt::new(hkdf::HKDF_SHA384, &nonce[..16]); let HkdfKey(out) = salt.extract(key.as_ref()).expand(&[info], HkdfKey(32))?.try_into()?; Ok(Self { version: PhantomData, purpose: PhantomData, key: out, }) } } rusty_paseto-0.7.1/src/core/common/authentication_key_impl/v3_local.rs000064400000000000000000000013051046102023000243600ustar 00000000000000#![cfg(feature = "v3_local")] use std::marker::PhantomData; use ring::hkdf; use crate::core::{Key, Local, PasetoError, PasetoSymmetricKey, V3}; use crate::core::common::HkdfKey; impl crate::core::common::authentication_key::AuthenticationKey { pub(crate) fn try_from(message: &Key<56>, key: &PasetoSymmetricKey) -> Result { let info = message.as_ref(); let salt = hkdf::Salt::new(hkdf::HKDF_SHA384, &[]); let HkdfKey(out) = salt.extract(key.as_ref()).expand(&[info], HkdfKey(48))?.try_into()?; Ok(Self { version: PhantomData, purpose: PhantomData, key: out, }) } }rusty_paseto-0.7.1/src/core/common/authentication_key_impl/v4_local.rs000064400000000000000000000014141046102023000243620ustar 00000000000000#![cfg(feature = "v4_local")] use std::marker::PhantomData; use std::ops::Deref; use blake2::digest::consts::U32; use blake2::{Blake2bMac, digest::Update}; use blake2::digest::FixedOutput; use digest::KeyInit; use crate::core::{Key, Local, PasetoSymmetricKey, V4}; impl crate::core::common::authentication_key::AuthenticationKey { pub(crate) fn from(message: &Key<56>, key: &PasetoSymmetricKey) -> Self { let mut context = Blake2bMac::::new_from_slice(key.as_ref()).unwrap(); context.update(message.as_ref()); let binding = context.finalize_fixed(); let key = binding.to_vec(); Self { version: PhantomData, purpose: PhantomData, key, } } }rusty_paseto-0.7.1/src/core/common/authentication_key_separator.rs000064400000000000000000000021461046102023000237210ustar 00000000000000use std::fmt; use std::fmt::Display; use std::ops::{Add, Deref}; use crate::core::{Key, Local, PasetoNonce}; #[derive(Debug)] pub (crate) struct AuthenticationKeySeparator(&'static str); impl Display for AuthenticationKeySeparator { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", &self.0) } } impl Default for AuthenticationKeySeparator { fn default() -> Self { Self("paseto-auth-key-for-aead") } } impl Deref for AuthenticationKeySeparator { type Target = [u8]; fn deref(&self) -> &Self::Target { self.0.as_bytes() } } impl AsRef for AuthenticationKeySeparator { fn as_ref(&self) -> &str { self.0 } } impl<'a, Version> Add<&PasetoNonce<'a, Version, Local>> for AuthenticationKeySeparator { type Output = Key<56>; fn add(self, rhs: &PasetoNonce) -> Self::Output { let mut output = [0u8; 56]; output[..24].copy_from_slice(self.0.as_bytes()); output[24..].copy_from_slice(rhs.as_ref()); Key::<56>::from(output) } } rusty_paseto-0.7.1/src/core/common/cipher_text.rs000064400000000000000000000010401046102023000202600ustar 00000000000000use std::marker::PhantomData; pub(crate) struct CipherText { pub(crate) ciphertext: Vec, pub(crate) version: PhantomData, pub(crate) purpose: PhantomData, } impl AsRef> for CipherText { fn as_ref(&self) -> &Vec { &self.ciphertext } } impl std::ops::Deref for CipherText { type Target = Vec; fn deref(&self) -> &Self::Target { &self.ciphertext } }rusty_paseto-0.7.1/src/core/common/cipher_text_impl/mod.rs000064400000000000000000000001121046102023000220570ustar 00000000000000mod v1_public; mod v1_local; mod v2_local; mod v3_local; mod v4_local;rusty_paseto-0.7.1/src/core/common/cipher_text_impl/v1_local.rs000064400000000000000000000016411046102023000230100ustar 00000000000000#![cfg(feature = "v1_local")] use std::marker::PhantomData; use aes::Aes256Ctr; use aes::cipher::generic_array::GenericArray; use aes::cipher::{NewCipher, StreamCipher}; use crate::core::common::cipher_text::CipherText; use crate::core::{Local, V1}; use crate::core::common::EncryptionKey; impl CipherText { pub(crate) fn from(payload: &[u8], encryption_key: &EncryptionKey) -> Self { let key = GenericArray::from_slice(encryption_key.as_ref()); let nonce = GenericArray::from_slice(encryption_key.counter_nonce()); let mut cipher = Aes256Ctr::new(key, nonce); let mut ciphertext = vec![0u8; payload.as_ref().len()]; ciphertext.copy_from_slice(payload); cipher.apply_keystream(&mut ciphertext); CipherText { ciphertext, version: PhantomData, purpose: PhantomData, } } }rusty_paseto-0.7.1/src/core/common/cipher_text_impl/v1_public.rs000064400000000000000000000017701046102023000231770ustar 00000000000000#![cfg(feature = "v1_public")] use std::marker::PhantomData; use ring::signature::{RSA_PSS_2048_8192_SHA384, UnparsedPublicKey}; use crate::core::common::{CipherText, PreAuthenticationEncoding}; use crate::core::{Footer, Header, PasetoError, Public, V1}; impl CipherText { pub(crate) fn try_verify(decoded_payload: &[u8], public_key: &impl AsRef<[u8]>, footer: &Footer) -> Result { let signature = decoded_payload[(decoded_payload.len() - 256)..].as_ref(); let public_key = UnparsedPublicKey::new(&RSA_PSS_2048_8192_SHA384, public_key); let msg = decoded_payload[..(decoded_payload.len() - 256)].as_ref(); let pae = PreAuthenticationEncoding::parse(&[&Header::::default(), msg, footer]); public_key.verify(&pae, signature)?; let ciphertext = Vec::from(msg); Ok(CipherText { ciphertext, version: PhantomData, purpose: PhantomData, }) } }rusty_paseto-0.7.1/src/core/common/cipher_text_impl/v2_local.rs000064400000000000000000000036501046102023000230130ustar 00000000000000#![cfg(feature = "v2_local")] use std::marker::PhantomData; use chacha20poly1305::{KeyInit, XChaCha20Poly1305, XNonce}; use chacha20poly1305::aead::{Aead, Payload}; use crate::core::common::{CipherText, PreAuthenticationEncoding}; use crate::core::{Local, PasetoError, PasetoSymmetricKey, V2}; impl CipherText { pub(crate) fn try_decrypt_from( key: &PasetoSymmetricKey, nonce: &XNonce, payload: &[u8], pre_auth: &PreAuthenticationEncoding, ) -> Result { //let ciphertext = CipherText::try_from(&key, &nonce, &payload, &pae)?; let aead = XChaCha20Poly1305::new_from_slice(key.as_ref()).map_err(|_| PasetoError::Cryption)?; //encrypt cipher_text let ciphertext = aead .decrypt( nonce, Payload { msg: payload, aad: pre_auth.as_ref(), }, ) .map_err(|_| PasetoError::ChaChaCipherError)?; Ok(CipherText { ciphertext, version: PhantomData, purpose: PhantomData, }) } pub(crate) fn try_from( key: &PasetoSymmetricKey, nonce: &XNonce, payload: &[u8], pre_auth: &PreAuthenticationEncoding, ) -> Result { let aead = XChaCha20Poly1305::new_from_slice(key.as_ref()).map_err(|_| PasetoError::Cryption)?; //encrypt cipher_text let ciphertext = aead .encrypt( nonce, Payload { msg: payload, aad: pre_auth.as_ref(), }, ) .map_err(|_| PasetoError::ChaChaCipherError)?; Ok(CipherText { ciphertext, version: PhantomData, purpose: PhantomData, }) } }rusty_paseto-0.7.1/src/core/common/cipher_text_impl/v3_local.rs000064400000000000000000000015631046102023000230150ustar 00000000000000#![cfg(feature = "v3_local")] use std::marker::PhantomData; use aes::Aes256Ctr; use aes::cipher::generic_array::GenericArray; use aes::cipher::{NewCipher, StreamCipher}; use crate::core::common::{CipherText, EncryptionKey}; use crate::core::{Local, V3}; impl CipherText { pub(crate) fn from(payload: &[u8], encryption_key: &EncryptionKey) -> Self { let key = GenericArray::from_slice(encryption_key.as_ref()); let nonce = GenericArray::from_slice(encryption_key.counter_nonce()); let mut cipher = Aes256Ctr::new(key, nonce); let mut ciphertext = vec![0u8; payload.len()]; ciphertext.copy_from_slice(payload); cipher.apply_keystream(&mut ciphertext); CipherText { ciphertext, version: PhantomData, purpose: PhantomData, } } }rusty_paseto-0.7.1/src/core/common/cipher_text_impl/v4_local.rs000064400000000000000000000013541046102023000230140ustar 00000000000000#![cfg(feature = "v4_local")] use std::marker::PhantomData; use chacha20::cipher::{KeyIvInit, StreamCipher}; use crate::core::common::{CipherText, EncryptionKey}; use crate::core::{Local, V4}; impl CipherText { pub(crate) fn from(payload: &[u8], encryption_key: &EncryptionKey) -> Self { let mut ciphertext = vec![0u8; payload.len()]; ciphertext.copy_from_slice(payload); let n2 = encryption_key.counter_nonce(); let mut cipher = chacha20::XChaCha20::new(encryption_key.as_ref(), n2); cipher.apply_keystream(&mut ciphertext); CipherText { ciphertext, version: PhantomData, purpose: PhantomData, } } } rusty_paseto-0.7.1/src/core/common/encryption_key.rs000064400000000000000000000005361046102023000210150ustar 00000000000000use std::marker::PhantomData; #[derive(Default)] pub(crate) struct EncryptionKey { pub(crate) version: PhantomData, pub(crate) purpose: PhantomData, pub(crate) key: Vec, #[cfg(any(feature = "v1_local", feature = "v3_local", feature = "v4_local"))] pub(crate) nonce: Vec, } rusty_paseto-0.7.1/src/core/common/encryption_key_impl/mod.rs000064400000000000000000000000711046102023000226070ustar 00000000000000mod v1_local; mod v3_local; mod v4_local; mod v_local;rusty_paseto-0.7.1/src/core/common/encryption_key_impl/v1_local.rs000064400000000000000000000016511046102023000235350ustar 00000000000000#![cfg(feature = "v1_local")] use std::marker::PhantomData; use ring::hkdf; use crate::core::common::EncryptionKey; use crate::core::{Key, Local, PasetoError, PasetoNonce, PasetoSymmetricKey, V1}; use crate::core::common::hkdf_key::HkdfKey; impl EncryptionKey { pub(crate) fn try_from( message: &Key<21>, key: &PasetoSymmetricKey, nonce: &PasetoNonce, ) -> Result { let info = message.as_ref(); let salt = hkdf::Salt::new(hkdf::HKDF_SHA384, &nonce[..16]); let HkdfKey(out) = salt.extract(key.as_ref()).expand(&[info], HkdfKey(32))?.try_into()?; Ok(Self { version: PhantomData, purpose: PhantomData, key: out.to_vec(), nonce: nonce[16..].to_vec(), }) } pub(crate) fn counter_nonce(&self) -> &Vec { &self.nonce } }rusty_paseto-0.7.1/src/core/common/encryption_key_impl/v3_local.rs000064400000000000000000000014661046102023000235430ustar 00000000000000#![cfg(feature = "v3_local")] use std::marker::PhantomData; use ring::hkdf; use crate::core::{Key, Local, PasetoError, PasetoSymmetricKey, V3}; use crate::core::common::{EncryptionKey, HkdfKey}; impl EncryptionKey { pub(crate) fn try_from(message: &Key<53>, key: &PasetoSymmetricKey) -> Result { let info = message.as_ref(); let salt = hkdf::Salt::new(hkdf::HKDF_SHA384, &[]); let HkdfKey(out) = salt.extract(key.as_ref()).expand(&[info], HkdfKey(48))?.try_into()?; Ok(Self { version: PhantomData, purpose: PhantomData, key: out[..32].to_vec(), nonce: out[32..].to_vec(), }) } pub(crate) fn counter_nonce(&self) -> &Vec { &self.nonce } } rusty_paseto-0.7.1/src/core/common/encryption_key_impl/v4_local.rs000064400000000000000000000025611046102023000235410ustar 00000000000000#![cfg(feature = "v4_local")] use std::marker::PhantomData; use std::ops::Deref; use blake2::digest::consts::U56; use blake2::{Blake2bMac, digest::Update}; use blake2::digest::FixedOutput; use digest::KeyInit; use chacha20::{XNonce, Key}; use crate::core::common::EncryptionKey; use crate::core::{Local, PasetoSymmetricKey, V4}; impl EncryptionKey { pub(crate) fn from(message: &crate::core::Key<53>, key: &PasetoSymmetricKey) -> Self { let mut context = Blake2bMac::::new_from_slice(key.as_ref()).unwrap(); context.update(message.as_ref()); let binding = context.finalize_fixed(); let context = binding.to_vec(); let key = context[..32].to_vec(); let nonce = context[32..56].to_vec(); assert_eq!(key.len(), 32); assert_eq!(nonce.len(), 24); Self { key, nonce, version: PhantomData, purpose: PhantomData, } } pub(crate) fn counter_nonce(&self) -> &XNonce { XNonce::from_slice(&self.nonce) } } impl AsRef for EncryptionKey { fn as_ref(&self) -> &Key { Key::from_slice(&self.key) } } impl Deref for EncryptionKey { type Target = [u8]; fn deref(&self) -> &Self::Target { Key::from_slice(&self.key) } }rusty_paseto-0.7.1/src/core/common/encryption_key_impl/v_local.rs000064400000000000000000000007141046102023000234530ustar 00000000000000use std::ops::Deref; use crate::core::common::EncryptionKey; use crate::core::{Local, V1orV3}; impl AsRef> for EncryptionKey where Version: V1orV3, { fn as_ref(&self) -> &Vec { &self.key } } impl Deref for EncryptionKey where Version: V1orV3, { type Target = [u8]; fn deref(&self) -> &Self::Target { &self.key } }rusty_paseto-0.7.1/src/core/common/encryption_key_separator.rs000064400000000000000000000021131046102023000230660ustar 00000000000000use std::fmt; use std::fmt::Display; use std::ops::{Add, Deref}; use crate::core::{Key, Local, PasetoNonce}; #[derive(Debug)] pub (crate) struct EncryptionKeySeparator(&'static str); impl Display for EncryptionKeySeparator { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", &self.0) } } impl Default for EncryptionKeySeparator { fn default() -> Self { Self("paseto-encryption-key") } } impl Deref for EncryptionKeySeparator { type Target = [u8]; fn deref(&self) -> &Self::Target { self.0.as_bytes() } } impl AsRef for EncryptionKeySeparator { fn as_ref(&self) -> &str { self.0 } } impl<'a, Version> Add<&PasetoNonce<'a, Version, Local>> for EncryptionKeySeparator { type Output = Key<53>; fn add(self, rhs: &PasetoNonce) -> Self::Output { let mut output = [0u8; 53]; output[..21].copy_from_slice(self.0.as_bytes()); output[21..].copy_from_slice(rhs.as_ref()); Key::<53>::from(output) } } rusty_paseto-0.7.1/src/core/common/encryption_nonce.rs000064400000000000000000000004361046102023000213260ustar 00000000000000#[cfg(feature = "chacha20poly1305")] use chacha20poly1305::XNonce; #[cfg(feature = "chacha20poly1305")] struct EncryptionNonce(XNonce); #[cfg(feature = "chacha20poly1305")] impl AsRef for EncryptionNonce { fn as_ref(&self) -> &XNonce { &self.0 } } rusty_paseto-0.7.1/src/core/common/hkdf_key.rs000064400000000000000000000010251046102023000175310ustar 00000000000000use ring::hkdf; use crate::core::PasetoError; #[derive(Debug, PartialEq)] pub(crate) struct HkdfKey(pub T); impl hkdf::KeyType for HkdfKey { fn len(&self) -> usize { self.0 } } impl TryFrom>> for HkdfKey> { type Error = PasetoError; fn try_from(okm: hkdf::Okm>) -> Result { let mut r = vec![0u8; okm.len().0]; okm.fill(&mut r)?; Ok(Self(r)) } } rusty_paseto-0.7.1/src/core/common/mod.rs000064400000000000000000000015041046102023000165260ustar 00000000000000#![allow(unused)] pub(crate) mod cipher_text; mod encryption_key; mod encryption_nonce; mod tag; mod raw_payload; mod authentication_key; mod authentication_key_separator; mod encryption_key_separator; mod pre_authentication_encoding; mod hkdf_key; mod encryption_key_impl; mod tag_impl; mod raw_payload_impl; mod authentication_key_impl; mod cipher_text_impl; pub(crate) use encryption_key::EncryptionKey; pub(crate) use raw_payload::RawPayload; pub(crate) use pre_authentication_encoding::PreAuthenticationEncoding; pub(crate) use cipher_text::CipherText; pub(crate) use authentication_key::AuthenticationKey; pub(crate) use authentication_key_separator::AuthenticationKeySeparator; pub(crate) use encryption_key_separator::EncryptionKeySeparator; pub(crate) use tag::Tag; pub(crate) use hkdf_key::HkdfKey; rusty_paseto-0.7.1/src/core/common/pre_authentication_encoding.rs000064400000000000000000000027521046102023000235100ustar 00000000000000use std::ops::Deref; pub struct PreAuthenticationEncoding(Vec); /// Performs Pre-Authentication Encoding (or PAE) as described in the /// Paseto Specification v2. /// impl PreAuthenticationEncoding { /// * `pieces` - The Pieces to concatenate, and encode together. /// Refactored from original code found at /// pub fn parse<'a>(pieces: &'a [&'a [u8]]) -> Self { let the_vec = PreAuthenticationEncoding::le64(pieces.len() as u64); Self(pieces.iter().fold(the_vec, |mut acc, piece| { acc.extend(PreAuthenticationEncoding::le64(piece.len() as u64)); acc.extend(piece.iter()); acc })) } /// Encodes a u64-bit unsigned integer into a little-endian binary string. /// /// * `to_encode` - The u8 to encode. /// Copied and gently refactored from pub(crate) fn le64(mut to_encode: u64) -> Vec { let mut the_vec = Vec::with_capacity(8); for _idx in 0..8 { the_vec.push((to_encode & 255) as u8); to_encode >>= 8; } the_vec } } impl Deref for PreAuthenticationEncoding { type Target = [u8]; fn deref(&self) -> &Self::Target { self.0.deref() } } impl AsRef> for PreAuthenticationEncoding { fn as_ref(&self) -> &Vec { &self.0 } } rusty_paseto-0.7.1/src/core/common/raw_payload.rs000064400000000000000000000002411046102023000202460ustar 00000000000000use std::marker::PhantomData; pub struct RawPayload { version: PhantomData, purpose: PhantomData, } rusty_paseto-0.7.1/src/core/common/raw_payload_impl/mod.rs000064400000000000000000000000741046102023000220520ustar 00000000000000mod nist_local; mod v2_local; mod v4_local; mod v_public;rusty_paseto-0.7.1/src/core/common/raw_payload_impl/nist_local.rs000064400000000000000000000021321046102023000234170ustar 00000000000000#![cfg(any(feature = "v1_local", feature = "v3_local"))] use base64::prelude::*; use crate::core::common::RawPayload; use crate::core::{Local, PasetoError, PasetoNonce, V1orV3}; impl RawPayload where Version: V1orV3, { pub(crate) fn from( nonce: &PasetoNonce, ciphertext: &impl AsRef>, tag: &impl AsRef<[u8]>, ) -> Result { let tag_len = tag.as_ref().len(); let concat_len: usize = match (nonce.len() + tag_len).checked_add(ciphertext.as_ref().len()) { Some(len) => len, None => return Err(PasetoError::Signature), }; let mut raw_token = vec![0u8; concat_len]; raw_token[..nonce.as_ref().len()].copy_from_slice(nonce.as_ref()); raw_token[nonce.as_ref().len()..nonce.as_ref().len() + ciphertext.as_ref().len()] .copy_from_slice(ciphertext.as_ref()); raw_token[concat_len - tag_len..].copy_from_slice(tag.as_ref()); Ok(BASE64_URL_SAFE_NO_PAD.encode(&raw_token)) } } rusty_paseto-0.7.1/src/core/common/raw_payload_impl/v2_local.rs000064400000000000000000000006651046102023000230020ustar 00000000000000#![cfg(feature = "v2_local")] use base64::prelude::*; use crate::core::common::RawPayload; use crate::core::{Local, V2}; impl RawPayload { pub(crate) fn from(blake2_hash: &[u8], ciphertext: &[u8]) -> String { let mut raw_token = Vec::new(); raw_token.extend_from_slice(blake2_hash); raw_token.extend_from_slice(ciphertext); BASE64_URL_SAFE_NO_PAD.encode(&raw_token) } } rusty_paseto-0.7.1/src/core/common/raw_payload_impl/v4_local.rs000064400000000000000000000020051046102023000227720ustar 00000000000000#![cfg(feature = "v4_local")] use base64::prelude::*; use crate::core::common::RawPayload; use crate::core::{Local, PasetoError, PasetoNonce, V4}; impl RawPayload { pub(crate) fn try_from( nonce: &PasetoNonce, ciphertext: &impl AsRef>, tag: &impl AsRef<[u8]>, ) -> Result { let tag_len = tag.as_ref().len(); let concat_len: usize = match (nonce.len() + tag_len).checked_add(ciphertext.as_ref().len()) { Some(len) => len, None => return Err(PasetoError::Cryption), }; let mut raw_token = vec![0u8; concat_len]; raw_token[..nonce.as_ref().len()].copy_from_slice(nonce.as_ref()); raw_token[nonce.as_ref().len()..nonce.as_ref().len() + ciphertext.as_ref().len()] .copy_from_slice(ciphertext.as_ref()); raw_token[concat_len - tag_len..].copy_from_slice(tag.as_ref()); Ok(BASE64_URL_SAFE_NO_PAD.encode(&raw_token)) } } rusty_paseto-0.7.1/src/core/common/raw_payload_impl/v_public.rs000064400000000000000000000007541046102023000231030ustar 00000000000000#![cfg(any(feature = "v1_public", feature = "v2_public", feature = "v3_public", feature = "v4_public"))] use base64::prelude::*; use crate::core::common::RawPayload; use crate::core::Public; impl RawPayload { pub(crate) fn from(payload: &[u8], signature: &impl AsRef<[u8]>) -> String { let mut raw_token = Vec::from(payload); raw_token.extend_from_slice(signature.as_ref()); BASE64_URL_SAFE_NO_PAD.encode(&raw_token) } }rusty_paseto-0.7.1/src/core/common/tag.rs000064400000000000000000000007751046102023000165330ustar 00000000000000use std::marker::PhantomData; use std::ops::Deref; pub(crate) struct Tag { pub(crate) version: PhantomData, pub(crate) purpose: PhantomData, pub(crate) tag: Vec, } impl AsRef<[u8]> for Tag { fn as_ref(&self) -> &[u8] { &self.tag } } impl Deref for Tag { type Target = [u8]; fn deref(&self) -> &Self::Target { &self.tag } }rusty_paseto-0.7.1/src/core/common/tag_impl/mod.rs000064400000000000000000000000361046102023000203210ustar 00000000000000mod v4_local; mod nist_local;rusty_paseto-0.7.1/src/core/common/tag_impl/nist_local.rs000064400000000000000000000014421046102023000216730ustar 00000000000000#![cfg(any(feature = "v1_local", feature = "v3_local"))] use std::marker::PhantomData; use hmac::{Hmac, Mac}; use crate::core::{Local, V1orV3}; use crate::core::common::PreAuthenticationEncoding; impl crate::core::common::tag::Tag where Version: V1orV3, { pub(crate) fn from(authentication_key: impl AsRef<[u8]>, pae: &PreAuthenticationEncoding) -> Self { type HmacSha384 = Hmac; let mut mac = HmacSha384::new_from_slice(authentication_key.as_ref()).expect("HMAC can take key of any size"); mac.update(pae.as_ref()); let out = mac.finalize(); Self { tag: out.into_bytes().to_vec(), version: PhantomData, purpose: PhantomData, } } }rusty_paseto-0.7.1/src/core/common/tag_impl/v4_local.rs000064400000000000000000000014651046102023000212540ustar 00000000000000#![cfg(feature = "v4_local")] use std::marker::PhantomData; use std::ops::Deref; use blake2::digest::consts::U32; use blake2::{Blake2bMac, digest::Update}; use blake2::digest::FixedOutput; use digest::KeyInit; use crate::core::common::PreAuthenticationEncoding; use crate::core::{Local, V4}; impl crate::core::common::tag::Tag { pub(crate) fn from(authentication_key: impl AsRef<[u8]>, pae: &PreAuthenticationEncoding) -> Self { let mut tag_context = Blake2bMac::::new_from_slice(authentication_key.as_ref()).unwrap(); tag_context.update(pae.as_ref()); let binding = tag_context.finalize_fixed(); let tag = binding.to_vec(); Self { tag, version: PhantomData, purpose: PhantomData, } } } rusty_paseto-0.7.1/src/core/error.rs000064400000000000000000000065411046102023000156160ustar 00000000000000use std::array::TryFromSliceError; use thiserror::Error; /// Potential errors from attempting to build a token claim #[derive(Debug, Error)] pub enum PasetoError { ///A general, unspecified (for security reasons) cipher error #[error("A cipher error occurred")] PasetoCipherError(Box), ///A general, unspecified (for security reasons) cipher error #[error("An unspecified cryption error occured")] Cryption, ///A problem generating a signature #[error("Key was not in the correct format")] InvalidKey, ///A problem generating a signature #[error("Could not assemble final signature.")] Signature, /// Occurs when a private RSA key is not in pkcs#8 format #[error("A private RSA key was not in the correct format")] KeyRejected { ///Surfaces key rejection errors from ring #[from] source: ring::error::KeyRejected, }, ///A general, unspecified (for security reasons) cipher error #[error("An unspecified cipher error occurred")] Cipher { ///Surfaces unspecified errors from ring #[from] source: ring::error::Unspecified, }, #[cfg(feature = "ed25519-dalek")] ///An RSA cipher error #[error("An unspecified cipher error occurred")] RsaCipher { ///An RSA cipher error #[from] source: ed25519_dalek::ed25519::Error, }, #[cfg(feature = "p384")] ///An ECSDA cipher error #[error("An unspecified ECSDA error occurred")] ECSDAError { ///An ECSDA cipher error #[from] source: p384::ecdsa::Error, }, #[cfg(feature = "blake2")] ///An RSA cipher error #[error("An unspecified cipher error occurred")] InvalidLength { ///An RSA cipher error #[from] source: blake2::digest::InvalidLength, }, ///Occurs when a signature fails verification #[error("The token signature could not be verified")] InvalidSignature, #[error("A slice conversion error occurred")] TryFromSlice { ///Surfaces errors from slice conversion attempts #[from] source: TryFromSliceError, }, ///Occurs when an untrusted token string is unable to be parsed into its constituent parts #[error("This string has an incorrect number of parts and cannot be parsed into a token")] IncorrectSize, ///Occurs when an incorrect header is provided on an untrusted token string #[error("The token header is invalid")] WrongHeader, ///Occurs when an incorrect footer was passed in an attempt to parse an untrusted token string #[error("The provided footer is invalid")] FooterInvalid, ///Occurs when a base64 encoded payload cannot be decoded #[error("A base64 decode error occurred")] PayloadBase64Decode { ///Surfaced from the base64 crate #[from] source: base64::DecodeError, }, ///Occurs when a string fails parsing as Utf8 #[error("A Utf8 parsing error occurred")] Utf8Error { ///Surfaced from std::str::Utf8 #[from] source: std::str::Utf8Error, }, ///A cipher error from the ChaCha algorithm #[error("An unspecified cipher error occurred")] ChaChaCipherError, ///An infallible error #[error("A Utf8 parsing error occurred")] Infallibale { ///An infallible error #[from] source: std::convert::Infallible, }, ///Occurs when a string fails conversion from Utf8 #[error("A Utf8 parsing error occurred")] FromUtf8Error { ///Surfaced from std::string::FromUtf8Error #[from] source: std::string::FromUtf8Error, }, } rusty_paseto-0.7.1/src/core/footer.rs000064400000000000000000000037071046102023000157640ustar 00000000000000use super::*; use std::fmt; use std::ops::Deref; /// Unencrypted text, potentially JSON or some other structured format, typically used for key rotation schemes, packed into the /// payload as part of the cipher scheme. /// /// # Usage /// ``` /// # #[cfg(feature = "default")] /// # { /// # use rusty_paseto::prelude::*; /// # let key = PasetoSymmetricKey::::from(Key::<32>::from(b"wubbalubbadubdubwubbalubbadubdub")); /// let token = PasetoBuilder::::default() /// // note how we set the footer here /// .set_footer(Footer::from("Sometimes science is more art than science")) /// .build(&key)?; /// /// // the footer same footer should be used to parse the token /// let json_value = PasetoParser::::default() /// .set_footer(Footer::from("Sometimes science is more art than science")) /// .parse(&token, &key)?; /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` #[derive(Default, Debug, Clone, Copy)] pub struct Footer<'a>(&'a str); impl<'a> Base64Encodable for Footer<'a> {} impl<'a> Deref for Footer<'a> { type Target = [u8]; fn deref(&self) -> &'a Self::Target { self.0.as_bytes() } } impl<'a> AsRef for Footer<'a> { fn as_ref(&self) -> &str { self.0 } } impl<'a> From<&'a str> for Footer<'a> { fn from(s: &'a str) -> Self { Self(s) } } impl<'a> fmt::Display for Footer<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } impl<'a> PartialEq for Footer<'a> { fn eq(&self, other: &Self) -> bool { self.0 == other.0 } } impl<'a> Eq for Footer<'a> {} #[cfg(test)] mod unit_tests { use super::*; #[test] fn test_v2_footer() { let footer = Footer::default(); assert_eq!(footer.as_ref(), ""); assert!(footer.as_ref().is_empty()); } #[test] fn test_set_v2_footer() { let footer: Footer = "wubbulubbadubdub".into(); assert_eq!(footer.as_ref(), "wubbulubbadubdub"); assert!(!footer.as_ref().is_empty()); } } rusty_paseto-0.7.1/src/core/header.rs000064400000000000000000000060531046102023000157130ustar 00000000000000use super::*; use std::fmt; use std::fmt::Display; use std::marker::PhantomData; use std::ops::Deref; /// The [Header] identifies the [protocol version and cryptographic format](https://github.com/paseto-standard/paseto-spec/tree/master/docs/01-Protocol-Versions) for the token /// /// [at least one code example that users can copy/paste to try it] /// #[derive(PartialEq, Debug, Copy, Clone)] pub(crate) struct Header where Version: VersionTrait, Purpose: PurposeTrait, { version: PhantomData, purpose: PhantomData, header: &'static str, } impl Deref for Header { type Target = [u8]; fn deref(&self) -> &Self::Target { self.header.as_bytes() } } impl AsRef for Header where Version: VersionTrait, Purpose: PurposeTrait, { fn as_ref(&self) -> &str { self.header } } //note: ugly workaround to minimize heap allocations and allow the full struct to implement Copy static V1_LOCAL: &str = "v1.local."; static V1_PUBLIC: &str = "v1.public."; static V2_LOCAL: &str = "v2.local."; static V2_PUBLIC: &str = "v2.public."; static V3_LOCAL: &str = "v3.local."; static V3_PUBLIC: &str = "v3.public."; static V4_LOCAL: &str = "v4.local."; static V4_PUBLIC: &str = "v4.public."; impl Default for Header where Version: VersionTrait, Purpose: PurposeTrait, { fn default() -> Self { let header = match (Version::default().as_ref(), Purpose::default().as_ref()) { ("v1", "local") => V1_LOCAL, ("v1", "public") => V1_PUBLIC, ("v2", "local") => V2_LOCAL, ("v2", "public") => V2_PUBLIC, ("v3", "local") => V3_LOCAL, ("v3", "public") => V3_PUBLIC, ("v4", "local") => V4_LOCAL, ("v4", "public") => V4_PUBLIC, _ => "", //this should never happen }; Self { version: PhantomData, purpose: PhantomData, header, } } } impl Display for Header where Version: VersionTrait, Purpose: PurposeTrait, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.header) } } #[cfg(all(test, any(feature = "v4", feature = "v2")))] mod unit_tests { use super::*; fn test_header_equality(valid_value: H, header: S) where S: AsRef, H: AsRef, { assert_eq!(header.as_ref(), valid_value.as_ref()); } #[cfg(feature = "v4_local")] #[test] fn test_v4_local_header_equality() { test_header_equality(Header::::default(), "v4.local."); } #[cfg(feature = "v4_public")] #[test] fn test_v4_public_header_equality() { test_header_equality(Header::::default(), "v4.public."); } #[cfg(feature = "v2_public")] #[test] fn test_v2_public_header_equality() { test_header_equality(Header::::default(), "v2.public."); } #[cfg(feature = "v2_local")] #[test] fn test_v2_local_header_equality() { test_header_equality(Header::::default(), "v2.local."); } } rusty_paseto-0.7.1/src/core/implicit_assertion.rs000064400000000000000000000037471046102023000203730ustar 00000000000000use std::fmt; use std::ops::Deref; /// Unencrypted but authenticated data (like the optional footer), but is NOT stored in the PASETO token (thus, implicit) and MUST be asserted when verifying a token. /// The main purpose for Implicit Assertions is to bind the token to some value that, due to business reasons, shouldn't ever be revealed publicly (i.e., a primary key or foreign key from a relational database table). /// Implicit Assertions allow you to build systems that are impervious to Confused Deputy attacks without ever having to disclose these internal values. /// /// # Usage /// ``` /// # #[cfg(feature = "default")] /// # { /// # use rusty_paseto::prelude::*; /// # let key = PasetoSymmetricKey::::from(Key::<32>::from(b"wubbalubbadubdubwubbalubbadubdub")); /// let token = PasetoBuilder::::default() /// // note how we set the footer here /// .set_implicit_assertion(ImplicitAssertion::from("Sometimes science is more art than science")) /// .build(&key)?; /// /// // the footer same footer should be used to parse the token /// let json_value = PasetoParser::::default() /// .set_implicit_assertion(ImplicitAssertion::from("Sometimes science is more art than science")) /// .parse(&token, &key)?; /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` #[derive(Default, Debug, Copy, Clone)] pub struct ImplicitAssertion<'a>(&'a str); impl<'a> Deref for ImplicitAssertion<'a> { type Target = [u8]; fn deref(&self) -> &'a Self::Target { self.0.as_bytes() } } impl<'a> AsRef for ImplicitAssertion<'a> { fn as_ref(&self) -> &str { self.0 } } impl<'a> From<&'a str> for ImplicitAssertion<'a> { fn from(s: &'a str) -> Self { Self(s) } } impl<'a> fmt::Display for ImplicitAssertion<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } impl<'a> PartialEq for ImplicitAssertion<'a> { fn eq(&self, other: &Self) -> bool { self.0 == other.0 } } impl<'a> Eq for ImplicitAssertion<'a> {} rusty_paseto-0.7.1/src/core/key/keys.rs000064400000000000000000000035551046102023000162320ustar 00000000000000use crate::core::PasetoError; use ring::rand::{SecureRandom, SystemRandom}; use std::convert::{From, TryFrom}; use std::fmt::Debug; use std::ops::Deref; use zeroize::Zeroize; /// A wrapper for a slice of bytes that constitute a key of a specific size #[derive(Zeroize)] #[zeroize(drop)] #[derive(Clone)] pub struct Key([u8; KEYSIZE]); impl Default for Key { fn default() -> Self { Self([0u8; KEYSIZE]) } } impl AsRef<[u8]> for Key { fn as_ref(&self) -> &[u8] { &self.0 } } impl Deref for Key { type Target = [u8; KEYSIZE]; fn deref(&self) -> &Self::Target { &self.0 } } impl From<&[u8]> for Key { fn from(key: &[u8]) -> Self { let mut me = Key::::default(); me.0.copy_from_slice(key); me } } impl From<&[u8; KEYSIZE]> for Key { fn from(key: &[u8; KEYSIZE]) -> Self { Self(*key) } } impl From<[u8; KEYSIZE]> for Key { fn from(key: [u8; KEYSIZE]) -> Self { Self(key) } } impl TryFrom<&str> for Key { type Error = hex::FromHexError; fn try_from(value: &str) -> Result { let key = hex::decode(value)?; let mut me = Key::::default(); me.0.copy_from_slice(&key); Ok(me) } } impl Key { /// Uses the system's RNG to create a random slice of bytes of a specific size pub fn try_new_random() -> Result { let rng = SystemRandom::new(); let mut buf = [0u8; KEYSIZE]; rng.fill(&mut buf)?; Ok(Self(buf)) } } impl Debug for Key { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "!!! KEY IS PRIVATE !!!") } } rusty_paseto-0.7.1/src/core/key/mod.rs000064400000000000000000000005771046102023000160370ustar 00000000000000mod keys; mod paseto_asymmetric_private_key; mod paseto_asymmetric_public_key; mod paseto_nonce; mod paseto_symmetric_key; mod paseto_nonce_impl; pub use keys::Key; pub use paseto_asymmetric_private_key::PasetoAsymmetricPrivateKey; pub use paseto_asymmetric_public_key::PasetoAsymmetricPublicKey; pub use paseto_nonce::PasetoNonce; pub use paseto_symmetric_key::PasetoSymmetricKey; rusty_paseto-0.7.1/src/core/key/paseto_asymmetric_private_key.rs000064400000000000000000000027631046102023000234110ustar 00000000000000use super::*; use crate::core::*; use std::convert::{AsRef, From}; use std::marker::PhantomData; /// A wrapper for the private half of an asymmetric key pair /// /// [V2] and [V4] keys are created from [Key] of size 64, [V1] keys are of an arbitrary size pub struct PasetoAsymmetricPrivateKey<'a, Version, Purpose> { version: PhantomData, purpose: PhantomData, key: &'a [u8], } impl<'a, Version> From<&'a [u8]> for PasetoAsymmetricPrivateKey<'a, Version, Public> where Version: V2orV4, { fn from(key: &'a [u8]) -> Self { Self { version: PhantomData, purpose: PhantomData, key, } } } impl<'a, Version, Purpose> AsRef<[u8]> for PasetoAsymmetricPrivateKey<'a, Version, Purpose> { fn as_ref(&self) -> &[u8] { self.key } } #[cfg(feature = "v1_public")] impl<'a> From<&'a [u8]> for PasetoAsymmetricPrivateKey<'a, V1, Public> { fn from(key: &'a [u8]) -> Self { Self { version: PhantomData, purpose: PhantomData, key, } } } impl<'a, Version> From<&'a Key<64>> for PasetoAsymmetricPrivateKey<'a, Version, Public> where Version: V2orV4, { fn from(key: &'a Key<64>) -> Self { Self { version: PhantomData, purpose: PhantomData, key: key.as_ref(), } } } #[cfg(feature = "v3_public")] impl<'a> From<&'a Key<48>> for PasetoAsymmetricPrivateKey<'a, V3, Public> { fn from(key: &'a Key<48>) -> Self { Self { version: PhantomData, purpose: PhantomData, key: key.as_ref(), } } } rusty_paseto-0.7.1/src/core/key/paseto_asymmetric_public_key.rs000064400000000000000000000027301046102023000232070ustar 00000000000000use super::Key; use crate::core::*; use std::convert::{AsRef, From}; use std::marker::PhantomData; /// A wrapper for the public half of an asymmetric key pair /// /// [V2] and [V4] keys are created from [Key] of size 32, [V1] keys are of an arbitrary size pub struct PasetoAsymmetricPublicKey<'a, Version, Purpose> { version: PhantomData, purpose: PhantomData, key: &'a [u8], } impl<'a, Version, Purpose> AsRef<[u8]> for PasetoAsymmetricPublicKey<'a, Version, Purpose> { fn as_ref(&self) -> &[u8] { self.key } } #[cfg(feature = "v1_public")] impl<'a> From<&'a [u8]> for PasetoAsymmetricPublicKey<'a, V1, Public> { fn from(key: &'a [u8]) -> Self { Self { version: PhantomData, purpose: PhantomData, key, } } } #[cfg(feature = "v3_public")] impl<'a> TryFrom<&'a Key<49>> for PasetoAsymmetricPublicKey<'a, V3, Public> { type Error = PasetoError; fn try_from(key: &'a Key<49>) -> Result { if key[0] != 2 && key[0] != 3 { return Err(PasetoError::InvalidKey); } //if this is successful, we can be sure our key is in a valid format Ok(Self { version: PhantomData, purpose: PhantomData, key: key.as_ref(), }) } } impl<'a, Version> From<&'a Key<32>> for PasetoAsymmetricPublicKey<'a, Version, Public> where Version: V2orV4, { fn from(key: &'a Key<32>) -> Self { Self { version: PhantomData, purpose: PhantomData, key: key.as_ref(), } } } rusty_paseto-0.7.1/src/core/key/paseto_nonce.rs000064400000000000000000000027601046102023000177310ustar 00000000000000use std::convert::AsRef; use std::marker::PhantomData; use std::ops::Deref; /// A nonce key for use in PASETO algorithms /// /// Key sizes for nonces are either 32 or 24 bytes in size /// /// Nonces can be specified directly for testing or randomly in production /// # Example usage /// ``` /// # #[cfg(feature = "v4_local")] /// # { /// use serde_json::json; /// use rusty_paseto::core::*; /// /// let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); /// // generate a random nonce with /// let nonce = Key::<32>::try_new_random()?; /// let nonce = PasetoNonce::::from(&nonce); /// /// let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); /// let payload = payload.as_str(); /// let payload = Payload::from(payload); /// /// //create a public v4 token /// let token = Paseto::::builder() /// .set_payload(payload) /// .try_encrypt(&key, &nonce)?; /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` pub struct PasetoNonce<'a, Version, Purpose> { pub(crate) version: PhantomData, pub(crate) purpose: PhantomData, pub(crate) key: &'a [u8], } impl<'a, Version, Purpose> Deref for PasetoNonce<'a, Version, Purpose> { type Target = [u8]; fn deref(&self) -> &Self::Target { self.key } } impl<'a, Version, Purpose> AsRef<[u8]> for PasetoNonce<'a, Version, Purpose> { fn as_ref(&self) -> &[u8] { self.key } } rusty_paseto-0.7.1/src/core/key/paseto_nonce_impl/mod.rs000064400000000000000000000001121046102023000215160ustar 00000000000000mod v1_local; mod v2_local; mod v3_local; mod v4_local; mod v2_public;rusty_paseto-0.7.1/src/core/key/paseto_nonce_impl/v1_local.rs000064400000000000000000000005421046102023000224460ustar 00000000000000#![cfg(feature = "v1_local")] use std::marker::PhantomData; use crate::core::{Key, Local, PasetoNonce, V1}; impl<'a> From<&'a Key<32>> for PasetoNonce<'a, V1, Local> { fn from(key: &'a Key<32>) -> Self { Self { version: PhantomData, purpose: PhantomData, key: key.as_ref(), } } } rusty_paseto-0.7.1/src/core/key/paseto_nonce_impl/v2_local.rs000064400000000000000000000020421046102023000224440ustar 00000000000000#![cfg(feature = "v2_local")] use std::marker::PhantomData; use crate::core::{Key, Local, PasetoNonce, V2}; impl<'a> From<&'a Key<24>> for PasetoNonce<'a, V2, Local> { fn from(key: &'a Key<24>) -> Self { Self { version: PhantomData, purpose: PhantomData, key: key.as_ref(), } } } impl<'a> From<&'a Key<32>> for PasetoNonce<'a, V2, Local> { fn from(key: &'a Key<32>) -> Self { Self { version: PhantomData, purpose: PhantomData, key: key.as_ref(), } } } #[cfg(all(test, feature = "v2_local"))] mod builders { use std::convert::From; use crate::core::*; use anyhow::Result; use super::PasetoNonce; #[test] fn v2_local_key_test() -> Result<()> { let key = Key::<32>::from(b"wubbalubbadubdubwubbalubbadubdub"); let paseto_key = PasetoNonce::::from(&key); assert_eq!(paseto_key.as_ref().len(), key.as_ref().len()); Ok(()) } } rusty_paseto-0.7.1/src/core/key/paseto_nonce_impl/v2_public.rs000064400000000000000000000006351046102023000226360ustar 00000000000000#![cfg(feature = "v2_public")] use std::marker::PhantomData; use crate::core::{PasetoNonce, Public, V2}; impl<'a, T> From<&'a T> for PasetoNonce<'a, V2, Public> where T: Into<&'a [u8]>, &'a [u8]: From<&'a T>, { fn from(key: &'a T) -> Self { Self { version: PhantomData, purpose: PhantomData, key: key.into(), } } } rusty_paseto-0.7.1/src/core/key/paseto_nonce_impl/v3_local.rs000064400000000000000000000005421046102023000224500ustar 00000000000000#![cfg(feature = "v3_local")] use std::marker::PhantomData; use crate::core::{Key, Local, PasetoNonce, V3}; impl<'a> From<&'a Key<32>> for PasetoNonce<'a, V3, Local> { fn from(key: &'a Key<32>) -> Self { Self { version: PhantomData, purpose: PhantomData, key: key.as_ref(), } } } rusty_paseto-0.7.1/src/core/key/paseto_nonce_impl/v4_local.rs000064400000000000000000000005401046102023000224470ustar 00000000000000#![cfg(feature = "v4_local")] use std::marker::PhantomData; use crate::core::{Key, Local, PasetoNonce, V4}; impl<'a> From<&'a Key<32>> for PasetoNonce<'a, V4, Local> { fn from(key: &'a Key<32>) -> Self { Self { version: PhantomData, purpose: PhantomData, key: key.as_ref(), } } }rusty_paseto-0.7.1/src/core/key/paseto_symmetric_key.rs000064400000000000000000000012121046102023000215020ustar 00000000000000use super::Key; use crate::core::Local; use std::convert::{AsRef, From}; use std::marker::PhantomData; /// A wrapper for a symmetric key /// /// Keys are created from [Key] of size 32 pub struct PasetoSymmetricKey { version: PhantomData, purpose: PhantomData, key: Key<32>, } impl From> for PasetoSymmetricKey { fn from(key: Key<32>) -> Self { Self { version: PhantomData, purpose: PhantomData, key, } } } impl AsRef<[u8]> for PasetoSymmetricKey { fn as_ref(&self) -> &[u8] { self.key.as_ref() } } rusty_paseto-0.7.1/src/core/mod.rs000064400000000000000000000050651046102023000152440ustar 00000000000000//! The **core** architectural layer and feature contains only paseto primitives for lightweight //! encrypting / decrypting or signing / verification //! //! ![paseto_core_small](https://user-images.githubusercontent.com/24578097/147881920-14c52256-1a0c-40be-9f18-759a8c9ad77d.png) //! //! The **core** feature requires you to specify the version and purpose //! ```toml //! ## Includes only v4 modern sodium cipher crypto core and local (symmetric) //! ## key types with NO claims, defaults or validation, just basic PASETO //! ## encrypt/signing and decrypt/verification. //! //! rusty_paseto = {version = "latest", features = ["core", "v4_local"] } //! //! ``` //! # Example usage //! ``` //! # #[cfg(feature = "v4_local")] //! # { //! # use serde_json::json; //! use rusty_paseto::core::*; //! //! let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); //! let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; //! // generate a random nonce with //! // let nonce = Key::<32>::try_new_random()?; //! let nonce = PasetoNonce::::from(&nonce); //! //! let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); //! let payload = payload.as_str(); //! let payload = Payload::from(payload); //! //! //create a public v4 token //! let token = Paseto::::builder() //! .set_payload(payload) //! .try_encrypt(&key, &nonce)?; //! //! //validate the test vector //! assert_eq!(token.to_string(), "v4.local.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAr68PS4AXe7If_ZgesdkUMvSwscFlAl1pk5HC0e8kApeaqMfGo_7OpBnwJOAbY9V7WU6abu74MmcUE8YWAiaArVI8XJ5hOb_4v9RmDkneN0S92dx0OW4pgy7omxgf3S8c3LlQg"); //! //! //now let's try to decrypt it //! let json = Paseto::::try_decrypt(&token, &key, None, None)?; //! assert_eq!(payload, json); //! } //! # Ok::<(),anyhow::Error>(()) //! ``` mod error; mod footer; mod header; mod implicit_assertion; mod key; mod paseto; mod payload; mod purpose; mod traits; mod version; mod common; mod paseto_impl; pub use error::PasetoError; pub use footer::Footer; pub(crate) use header::Header; pub use implicit_assertion::ImplicitAssertion; pub use key::{Key, PasetoAsymmetricPrivateKey, PasetoAsymmetricPublicKey, PasetoNonce, PasetoSymmetricKey}; pub use paseto::Paseto; pub use payload::Payload; pub use purpose::{Local, Public}; pub(crate) use traits::{Base64Encodable, V1orV3, V2orV4}; pub use traits::{ImplicitAssertionCapable, PurposeTrait, VersionTrait}; pub use version::*; rusty_paseto-0.7.1/src/core/paseto.rs000064400000000000000000000214371046102023000157610ustar 00000000000000use std::{ str, }; use crate::core::{Base64Encodable, Footer, Header, ImplicitAssertion, ImplicitAssertionCapable, PasetoError, Payload, PurposeTrait, VersionTrait}; /// Used to build and encrypt / decrypt core PASETO tokens /// /// Given a [Payload], optional [Footer] and optional [ImplicitAssertion] ([V3] or [V4] only) /// returns an encrypted token when [Local] is specified as the purpose or a signed token when /// [Public] is specified /// # Example usage /// ``` /// # #[cfg(feature = "v4_local")] /// # { /// # use serde_json::json; /// use rusty_paseto::core::*; /// /// let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); /// let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; /// // generate a random nonce with /// // let nonce = Key::<32>::try_new_random()?; /// let nonce = PasetoNonce::::from(&nonce); /// /// let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); /// let payload = payload.as_str(); /// let payload = Payload::from(payload); /// /// //create a public v4 token /// let token = Paseto::::builder() /// .set_payload(payload) /// .try_encrypt(&key, &nonce)?; /// /// //validate the test vector /// assert_eq!(token.to_string(), "v4.local.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAr68PS4AXe7If_ZgesdkUMvSwscFlAl1pk5HC0e8kApeaqMfGo_7OpBnwJOAbY9V7WU6abu74MmcUE8YWAiaArVI8XJ5hOb_4v9RmDkneN0S92dx0OW4pgy7omxgf3S8c3LlQg"); /// /// //now let's try to decrypt it /// let json = Paseto::::try_decrypt(&token, &key, None, None)?; /// assert_eq!(payload, json); /// } /// # Ok::<(),anyhow::Error>(()) /// ``` #[derive(Default, Copy, Clone)] pub struct Paseto<'a, Version, Purpose> where Version: VersionTrait, Purpose: PurposeTrait, { pub(crate) header: Header, pub(crate) payload: Payload<'a>, pub(crate) footer: Option>, pub(crate) implicit_assertion: Option>, } impl<'a, Version: VersionTrait, Purpose: PurposeTrait> Paseto<'a, Version, Purpose> { /// Returns a builder for creating a PASETO token /// /// # Example usage /// ``` /// # #[cfg(feature = "v4_local")] /// # { /// # use serde_json::json; /// # use rusty_paseto::core::*; /// /// # let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); /// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; /// # // generate a random nonce with /// # // let nonce = Key::<32>::try_new_random()?; /// # let nonce = PasetoNonce::::from(&nonce); /// /// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); /// # let payload = payload.as_str(); /// # let payload = Payload::from(payload); /// /// //create a public v4 token /// let token = Paseto::::builder() /// .set_payload(payload) /// .try_encrypt(&key, &nonce)?; /// /// # //validate the test vector /// # assert_eq!(token.to_string(), "v4.local.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAr68PS4AXe7If_ZgesdkUMvSwscFlAl1pk5HC0e8kApeaqMfGo_7OpBnwJOAbY9V7WU6abu74MmcUE8YWAiaArVI8XJ5hOb_4v9RmDkneN0S92dx0OW4pgy7omxgf3S8c3LlQg"); /// /// # //now let's try to decrypt it /// # let json = Paseto::::try_decrypt(&token, &key, None, None)?; /// # assert_eq!(payload, json); /// } /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn builder() -> Paseto<'a, Version, Purpose> { Self { ..Default::default() } } /// Sets the payload for the token pub fn set_payload(&mut self, payload: Payload<'a>) -> &mut Self { self.payload = payload; self } /// Sets an optional footer for the token /// /// # Example usage /// ``` /// # #[cfg(feature = "v4_local")] /// # { /// # use serde_json::json; /// # use rusty_paseto::core::*; /// /// # let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); /// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; /// # // generate a random nonce with /// # // let nonce = Key::<32>::try_new_random()?; /// # let nonce = PasetoNonce::::from(&nonce); /// /// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); /// # let payload = payload.as_str(); /// # let payload = Payload::from(payload); /// /// // Set the footer with a Footer struct /// let token = Paseto::::builder() /// .set_payload(payload) /// .set_footer(Footer::from("Supah doopah!")) /// .try_encrypt(&key, &nonce)?; /// /// # //now let's try to decrypt it /// # let json = Paseto::::try_decrypt(&token, &key, Footer::from("Supah doopah!"), None)?; /// # assert_eq!(payload, json); /// } /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn set_footer(&mut self, footer: Footer<'a>) -> &mut Self { self.footer = Some(footer); self } /* BEGIN PRIVATE FUNCTIONS */ pub(crate) fn format_token(&self, encrypted_payload: &str) -> String { let footer = self.footer.map(|f| f.encode()); match footer { Some(f) => format!("{}{}.{}", self.header, encrypted_payload, f), None => format!("{}{}", self.header, encrypted_payload), } } pub(crate) fn parse_raw_token( raw_token: &'a str, footer: (impl Into>> + Copy), v: &Version, p: &Purpose, ) -> Result, PasetoError> { //split the raw token into parts let potential_parts = raw_token.split('.').collect::>(); //inspect the parts match potential_parts.len() { length if !(3..=4).contains(&length) => { return Err(PasetoError::IncorrectSize); } 4 => { //verify expected footer let footer = footer.into().unwrap_or_default(); let found_footer = Footer::from(potential_parts[3]); if !footer.constant_time_equals(found_footer) { return Err(PasetoError::FooterInvalid); } } _ => {} } //grab the header let potential_header = format!("{}.{}.", potential_parts[0], potential_parts[1]); //we should be able to verify the header using the passed in Version and Purpose let expected_header = format!("{}.{}.", v, p); //verify the header if potential_header.ne(&expected_header) { return Err(PasetoError::WrongHeader); }; let encrypted_payload = Payload::from(potential_parts[2]); Ok(encrypted_payload.decode()?) } /* END PRIVATE FUNCTIONS */ } impl<'a, Version, Purpose> Paseto<'a, Version, Purpose> where Purpose: PurposeTrait, Version: ImplicitAssertionCapable, { /// Sets an optional [ImplicitAssertion] for the token /// /// *NOTE:* Only for [V3] or [V4] tokens /// /// # Example usage /// ``` /// # #[cfg(feature = "v4_local")] /// # { /// # use serde_json::json; /// # use rusty_paseto::core::*; /// /// # let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); /// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; /// # // generate a random nonce with /// # // let nonce = Key::<32>::try_new_random()?; /// # let nonce = PasetoNonce::::from(&nonce); /// /// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); /// # let payload = payload.as_str(); /// # let payload = Payload::from(payload); /// /// // Set the ImplicitAssertion /// let token = Paseto::::builder() /// .set_payload(payload) /// .set_implicit_assertion(ImplicitAssertion::from("Supah doopah!")) /// .try_encrypt(&key, &nonce)?; /// /// # //now let's try to decrypt it /// # let json = Paseto::::try_decrypt(&token, &key, None, ImplicitAssertion::from("Supah doopah!"))?; /// # assert_eq!(payload, json); /// } /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn set_implicit_assertion(&mut self, implicit_assertion: ImplicitAssertion<'a>) -> &mut Self { self.implicit_assertion = Some(implicit_assertion); self } } rusty_paseto-0.7.1/src/core/paseto_impl/mod.rs000064400000000000000000000001721046102023000175520ustar 00000000000000mod v1_public; mod v2_public; mod v1_local; mod v2_local; mod v3_local; mod v4_local; mod v4_public; mod v3_public;rusty_paseto-0.7.1/src/core/paseto_impl/v1_local.rs000064400000000000000000000127031046102023000204760ustar 00000000000000#![cfg(feature = "v1_local")] use std::str; use hmac::{Hmac, Mac}; use crate::core::{Footer, Header, Key, Local, Paseto, PasetoError, PasetoNonce, PasetoSymmetricKey, V1}; use crate::core::common::{AuthenticationKey, AuthenticationKeySeparator, CipherText, EncryptionKey, EncryptionKeySeparator, PreAuthenticationEncoding, RawPayload, Tag}; use ring::constant_time::verify_slices_are_equal as ConstantTimeEquals; use sha2::Sha384; impl<'a> Paseto<'a, V1, Local> { /// Attempts to decrypt a PASETO token /// ``` /// # use serde_json::json; /// # use rusty_paseto::core::*; /// # let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); /// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; /// # let nonce = PasetoNonce::::from(&nonce); /// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); /// # let payload = payload.as_str(); /// # let payload = Payload::from(payload); /// # let token = Paseto::::builder().set_payload(payload).try_encrypt(&key, &nonce)?; /// // decrypt a public v1 token /// let json = Paseto::::try_decrypt(&token, &key, None)?; /// # assert_eq!(payload, json); /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn try_decrypt( token: &'a str, key: &PasetoSymmetricKey, footer: (impl Into>> + Copy), ) -> Result { let decoded_payload = Self::parse_raw_token(token, footer, &V1::default(), &Local::default())?; let nonce = Key::from(&decoded_payload[..32]); let nonce = PasetoNonce::::from(&nonce); let aks: &[u8] = &AuthenticationKeySeparator::default(); let authentication_key = AuthenticationKey::::try_from(&Key::from(aks), key, &nonce)?; let eks: &[u8] = &EncryptionKeySeparator::default(); let encryption_key = EncryptionKey::::try_from(&Key::from(eks), key, &nonce)?; let ciphertext = &decoded_payload[32..(decoded_payload.len() - 48)]; //pack preauth let pae = PreAuthenticationEncoding::parse(&[ &Header::::default(), nonce.as_ref(), ciphertext, &footer.into().unwrap_or_default(), ]); //generate tags let tag = &decoded_payload[(nonce.len() + ciphertext.len())..]; let tag2 = &Tag::::from(authentication_key, &pae); //compare tags ConstantTimeEquals(tag, tag2)?; //decrypt payload let ciphertext = CipherText::::from(ciphertext, &encryption_key); let decoded_str = str::from_utf8(&ciphertext)?; //return decrypted payload Ok(decoded_str.to_owned()) } /// Attempts to encrypt a PASETO token /// ``` /// # use serde_json::json; /// # use rusty_paseto::core::*; /// # let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); /// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; /// # let nonce = PasetoNonce::::from(&nonce); /// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); /// # let payload = payload.as_str(); /// # let payload = Payload::from(payload); /// // encrypt a public v1 token /// let token = Paseto::::builder().set_payload(payload).try_encrypt(&key, &nonce)?; /// # let json = Paseto::::try_decrypt(&token, &key, None)?; /// # assert_eq!(payload, json); /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn try_encrypt( &mut self, key: &PasetoSymmetricKey, nonce: &PasetoNonce, ) -> Result { //setup let footer = self.footer.unwrap_or_default(); //calculate nonce type HmacSha384 = Hmac; let mut mac = HmacSha384::new_from_slice(nonce.as_ref()).expect("HMAC can take key of any size"); mac.update(&self.payload); let out = mac.finalize(); let nonce = Key::from(&out.into_bytes()[..32]); let nonce = PasetoNonce::::from(&nonce); //split key let aks: &[u8] = &AuthenticationKeySeparator::default(); let authentication_key = AuthenticationKey::::try_from(&Key::from(aks), key, &nonce)?; let eks: &[u8] = &EncryptionKeySeparator::default(); let encryption_key = EncryptionKey::::try_from(&Key::from(eks), key, &nonce)?; //encrypt payload let ciphertext = CipherText::::from(&self.payload, &encryption_key); //pack preauth let pae = PreAuthenticationEncoding::parse(&[&self.header, nonce.as_ref(), &ciphertext, &footer]); // //generate tag let tag = Tag::::from(authentication_key, &pae); // //generate appended and base64 encoded payload let raw_payload = RawPayload::::from(&nonce, &ciphertext, &tag)?; //format as paseto with header and optional footer Ok(self.format_token(&raw_payload)) } } rusty_paseto-0.7.1/src/core/paseto_impl/v1_public.rs000064400000000000000000000034201046102023000206560ustar 00000000000000#![cfg(feature = "v1_public")] use ring::rand::SystemRandom; use ring::signature::{RSA_PSS_SHA384, RsaKeyPair}; use crate::core::{Footer, Paseto, PasetoAsymmetricPrivateKey, PasetoAsymmetricPublicKey, PasetoError, Public, V1}; use crate::core::common::{CipherText, PreAuthenticationEncoding, RawPayload}; impl<'a> Paseto<'a, V1, Public> { /// Verifies a signed V1 Public Paseto pub fn try_verify( signature: &'a str, public_key: &PasetoAsymmetricPublicKey, footer: (impl Into>> + Copy), ) -> Result { let decoded_payload = Self::parse_raw_token(signature, footer, &V1::default(), &Public::default())?; let ciphertext = CipherText::::try_verify(&decoded_payload, public_key, &footer.into().unwrap_or_default())? .ciphertext; Ok(String::from_utf8(ciphertext)?) } /// Attempts to sign a V1 Public Paseto /// Fails with a PasetoError if the token is malformed or the private key isn't in a valid pkcs#8 /// format pub fn try_sign(&mut self, key: &PasetoAsymmetricPrivateKey) -> Result { let footer = self.footer.unwrap_or_default(); let key_pair = RsaKeyPair::from_pkcs8(key.as_ref())?; let pae = PreAuthenticationEncoding::parse(&[&self.header, &self.payload, &footer]); let random = SystemRandom::new(); let mut signature = [0; 256]; key_pair .sign(&RSA_PSS_SHA384, &random, &pae, &mut signature) .map_err(|_| PasetoError::InvalidSignature)?; let raw_payload = RawPayload::::from(&self.payload, &signature); Ok(self.format_token(&raw_payload)) } } rusty_paseto-0.7.1/src/core/paseto_impl/v2_local.rs000064400000000000000000000104221046102023000204730ustar 00000000000000#![cfg(feature = "v2_local")] use blake2::Blake2bMac; use blake2::digest::{FixedOutput, Mac}; use chacha20poly1305::XNonce; use crate::core::{Footer, Header, Local, Paseto, PasetoError, PasetoNonce, PasetoSymmetricKey, V2}; use crate::core::common::{CipherText, PreAuthenticationEncoding, RawPayload}; use std::str; impl<'a> Paseto<'a, V2, Local> { /// Attempts to decrypt a PASETO token /// ``` /// # use serde_json::json; /// # use rusty_paseto::core::*; /// # let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); /// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; /// # let nonce = PasetoNonce::::from(&nonce); /// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); /// # let payload = payload.as_str(); /// # let payload = Payload::from(payload); /// # let token = Paseto::::builder().set_payload(payload).try_encrypt(&key, &nonce)?; /// // decrypt a public v2 token /// let json = Paseto::::try_decrypt(&token, &key, None)?; /// # assert_eq!(payload, json); /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn try_decrypt( token: &'a str, key: &PasetoSymmetricKey, footer: (impl Into>> + Copy), ) -> Result { //get footer let decoded_payload = Self::parse_raw_token(token, footer, &V2::default(), &Local::default())?; let (nonce, ciphertext) = decoded_payload.split_at(24); //pack preauth let pae = &PreAuthenticationEncoding::parse(&[ &Header::::default(), nonce, &footer.into().unwrap_or_default(), ]); //create the nonce let nonce = XNonce::from_slice(nonce); //encrypt payload let ciphertext = CipherText::::try_decrypt_from(key, nonce, ciphertext, pae)?; //generate appended and base64 encoded payload let decoded_str = str::from_utf8(&ciphertext)?; //return decrypted payload Ok(decoded_str.to_owned()) } /// Attempts to encrypt a PASETO token /// ``` /// # use serde_json::json; /// # use rusty_paseto::core::*; /// # let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); /// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; /// # let nonce = PasetoNonce::::from(&nonce); /// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); /// # let payload = payload.as_str(); /// # let payload = Payload::from(payload); /// // encrypt a public v2 token /// let token = Paseto::::builder().set_payload(payload).try_encrypt(&key, &nonce)?; /// # let json = Paseto::::try_decrypt(&token, &key, None)?; /// # assert_eq!(payload, json); /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn try_encrypt( &self, key: &PasetoSymmetricKey, nonce: &PasetoNonce, ) -> Result { //setup let footer = self.footer.unwrap_or_default(); //create the blake2 context to generate the nonce let mut blake2 = Blake2bMac::new_from_slice(nonce.as_ref())?; blake2.update(&self.payload); let mut context = [0u8; 24]; blake2.finalize_into((&mut context).into()); //create the nonce let nonce = XNonce::from_slice(&context); //pack preauth let pae = PreAuthenticationEncoding::parse(&[&self.header, nonce, &footer]); //encrypt payload let ciphertext = CipherText::::try_from(key, nonce, &self.payload, &pae)?; //generate appended and base64 encoded payload let raw_payload = RawPayload::::from(&context, &ciphertext); //format as paseto with header and optional footer Ok(self.format_token(&raw_payload)) } } rusty_paseto-0.7.1/src/core/paseto_impl/v2_public.rs000064400000000000000000000050711046102023000206630ustar 00000000000000#![cfg(feature = "v2_public")] use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey}; use crate::core::{Footer, Header, Paseto, PasetoAsymmetricPrivateKey, PasetoAsymmetricPublicKey, PasetoError, Public, V2}; use crate::core::common::{PreAuthenticationEncoding, RawPayload}; impl<'a> Paseto<'a, V2, Public> { /// Attempts to verify a signed V2 Public Paseto /// Fails with a PasetoError if the token is malformed or the token cannot be verified with the /// passed public key pub fn try_verify( signature: &'a str, public_key: &PasetoAsymmetricPublicKey, footer: (impl Into>> + Copy), ) -> Result { let decoded_payload = Self::parse_raw_token(signature, footer, &V2::default(), &Public::default())?; let verifying_key: VerifyingKey = VerifyingKey::from_bytes(<&[u8; 32]>::try_from(public_key.as_ref())?)?; // let public_key = PublicKey::from_bytes(public_key.as_ref()).map_err(|_| PasetoError::InvalidSignature)?; let msg = decoded_payload[..(decoded_payload.len() - ed25519_dalek::SIGNATURE_LENGTH)].as_ref(); let sig = decoded_payload[msg.len()..msg.len() + ed25519_dalek::SIGNATURE_LENGTH].as_ref(); let signature = Signature::try_from(sig).map_err(|_| PasetoError::InvalidSignature)?; let pae = PreAuthenticationEncoding::parse(&[ &Header::::default(), msg, &footer.into().unwrap_or_default(), ]); verifying_key.verify(&pae, &signature)?; // public_key // .verify(&pae, &signature) // .map_err(|_| PasetoError::InvalidSignature)?; Ok(String::from_utf8(Vec::from(msg))?) } /// Attempts to sign a V2 Public Paseto /// Fails with a PasetoError if the token is malformed or the private key can't be parsed pub fn try_sign(&mut self, key: &PasetoAsymmetricPrivateKey) -> Result { let footer = self.footer.unwrap_or_default(); // let keypair = Keypair::from_bytes(key.as_ref())?; let signing_key = SigningKey::from_keypair_bytes(<&[u8; 64]>::try_from(key.as_ref())?)?; let pae = PreAuthenticationEncoding::parse(&[&self.header, &self.payload, &footer]); // let signature = keypair.sign(&pae); let signature = signing_key.sign(&pae); let raw_payload = RawPayload::::from(&self.payload, &signature.to_bytes()); Ok(self.format_token(&raw_payload)) } } rusty_paseto-0.7.1/src/core/paseto_impl/v3_local.rs000064400000000000000000000123311046102023000204750ustar 00000000000000#![cfg(feature = "v3_local")] use std::str; use ring::constant_time::verify_slices_are_equal as ConstantTimeEquals; use crate::core::{Footer, Header, ImplicitAssertion, Key, Local, Paseto, PasetoError, PasetoNonce, PasetoSymmetricKey, V3}; use crate::core::common::{AuthenticationKey, AuthenticationKeySeparator, CipherText, EncryptionKey, EncryptionKeySeparator, PreAuthenticationEncoding, RawPayload, Tag}; impl<'a> Paseto<'a, V3, Local> { /// Attempts to decrypt a PASETO token /// ``` /// # use serde_json::json; /// # use rusty_paseto::core::*; /// # let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); /// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; /// # let nonce = PasetoNonce::::from(&nonce); /// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); /// # let payload = payload.as_str(); /// # let payload = Payload::from(payload); /// # let token = Paseto::::builder().set_payload(payload).try_encrypt(&key, &nonce)?; /// // decrypt a public v3 token /// let json = Paseto::::try_decrypt(&token, &key, None, None)?; /// # assert_eq!(payload, json); /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn try_decrypt( token: &'a str, key: &PasetoSymmetricKey, footer: (impl Into>> + Copy), implicit_assertion: (impl Into>> + Copy), ) -> Result { //get footer let decoded_payload = Self::parse_raw_token(token, footer, &V3::default(), &Local::default())?; let nonce = Key::from(&decoded_payload[..32]); let nonce = PasetoNonce::::from(&nonce); let authentication_key = AuthenticationKey::::try_from(&(AuthenticationKeySeparator::default() + &nonce), key)?; let encryption_key = EncryptionKey::::try_from(&(EncryptionKeySeparator::default() + &nonce), key)?; let ciphertext = &decoded_payload[32..(decoded_payload.len() - 48)]; //pack preauth let pae = PreAuthenticationEncoding::parse(&[ &Header::::default(), nonce.as_ref(), ciphertext, &footer.into().unwrap_or_default(), &implicit_assertion.into().unwrap_or_default(), ]); //generate tags let tag = &decoded_payload[(nonce.len() + ciphertext.len())..]; let tag2 = &Tag::::from(authentication_key, &pae); //compare tags ConstantTimeEquals(tag, tag2)?; //decrypt payload let ciphertext = CipherText::::from(ciphertext, &encryption_key); let decoded_str = str::from_utf8(&ciphertext)?; //return decrypted payload Ok(decoded_str.to_owned()) } /// Attempts to encrypt a PASETO token /// ``` /// # use serde_json::json; /// # use rusty_paseto::core::*; /// # let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); /// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; /// # let nonce = PasetoNonce::::from(&nonce); /// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); /// # let payload = payload.as_str(); /// # let payload = Payload::from(payload); /// // encrypt a public v3 token /// let token = Paseto::::builder().set_payload(payload).try_encrypt(&key, &nonce)?; /// # let json = Paseto::::try_decrypt(&token, &key, None, None)?; /// # assert_eq!(payload, json); /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn try_encrypt( &mut self, key: &PasetoSymmetricKey, nonce: &PasetoNonce, ) -> Result { //setup let footer = self.footer.unwrap_or_default(); let implicit_assertion = self.implicit_assertion.unwrap_or_default(); //split key let authentication_key = AuthenticationKey::::try_from(&(AuthenticationKeySeparator::default() + nonce), key)?; let encryption_key = EncryptionKey::::try_from(&(EncryptionKeySeparator::default() + nonce), key)?; //encrypt payload let ciphertext = CipherText::::from(&self.payload, &encryption_key); //pack preauth let pae = PreAuthenticationEncoding::parse(&[&self.header, nonce.as_ref(), &ciphertext, &footer, &implicit_assertion]); // //generate tag let tag = Tag::::from(authentication_key, &pae); // //generate appended and base64 encoded payload let raw_payload = RawPayload::::from(nonce, &ciphertext, &tag)?; //format as paseto with header and optional footer Ok(self.format_token(&raw_payload)) } } rusty_paseto-0.7.1/src/core/paseto_impl/v3_public.rs000064400000000000000000000062271046102023000206700ustar 00000000000000#![cfg(feature = "v3_public")] use crate::core::{Footer, Header, ImplicitAssertion, Paseto, PasetoAsymmetricPrivateKey, PasetoAsymmetricPublicKey, PasetoError, Public, V3}; use crate::core::common::{PreAuthenticationEncoding, RawPayload}; use p384::ecdsa::{ signature::DigestSigner, signature::DigestVerifier, Signature, SigningKey, VerifyingKey, }; use p384::elliptic_curve::sec1::ToEncodedPoint; use p384::PublicKey; use sha2::Digest; impl<'a> Paseto<'a, V3, Public> { /// Verifies a signed V3 Public Paseto pub fn try_verify( signature: &'a str, public_key: &PasetoAsymmetricPublicKey, footer: (impl Into>> + Copy), implicit_assertion: (impl Into>> + Copy), ) -> Result { let decoded_payload = Self::parse_raw_token(signature, footer, &V3::default(), &Public::default())?; //compress the key let compressed_public_key = PublicKey::from_sec1_bytes(public_key.as_ref()) .map_err(|_| PasetoError::InvalidKey)? .to_encoded_point(true); let verifying_key = VerifyingKey::from_sec1_bytes(compressed_public_key.as_ref()).map_err(|_| PasetoError::InvalidKey)?; let msg = decoded_payload[..(decoded_payload.len() - 96)].as_ref(); let sig = decoded_payload[msg.len()..msg.len() + 96].as_ref(); let signature = Signature::try_from(sig).map_err(|_| PasetoError::Signature)?; let m2 = PreAuthenticationEncoding::parse(&[ compressed_public_key.as_ref(), &Header::::default(), msg, &footer.into().unwrap_or_default(), &implicit_assertion.into().unwrap_or_default(), ]); let mut msg_digest = sha2::Sha384::default(); msg_digest.update(&*m2); verifying_key .verify_digest(msg_digest, &signature) .map_err(|_| PasetoError::InvalidSignature)?; Ok(String::from_utf8(Vec::from(msg))?) } /// Attempts to sign a V3 Public Paseto /// Fails with a PasetoError if the token is malformed or the private key isn't in a valid format pub fn try_sign(&mut self, key: &PasetoAsymmetricPrivateKey) -> Result { let footer = self.footer.unwrap_or_default(); let implicit_assertion = self.implicit_assertion.unwrap_or_default(); let signing_key = SigningKey::from_bytes(key.as_ref().into()).map_err(|_| PasetoError::InvalidKey)?; let public_key = VerifyingKey::from(&signing_key).to_encoded_point(true); let m2 = PreAuthenticationEncoding::parse(&[ public_key.as_ref(), &self.header, &self.payload, &footer, &implicit_assertion, ]); let mut msg_digest = sha2::Sha384::new(); msg_digest.update(&*m2); let signature: Signature = signing_key .try_sign_digest(msg_digest)?; let raw_payload = RawPayload::::from(&self.payload, &signature.to_bytes()); Ok(self.format_token(&raw_payload)) } } rusty_paseto-0.7.1/src/core/paseto_impl/v4_local.rs000064400000000000000000000124331046102023000205010ustar 00000000000000#![cfg(feature = "v4_local")] use std::str; use ring::constant_time::verify_slices_are_equal as ConstantTimeEquals; use crate::core::{Footer, Header, ImplicitAssertion, Key, Local, Paseto, PasetoError, PasetoNonce, PasetoSymmetricKey, V4}; use crate::core::common::{AuthenticationKey, AuthenticationKeySeparator, CipherText, EncryptionKey, EncryptionKeySeparator, PreAuthenticationEncoding, RawPayload, Tag}; impl<'a> Paseto<'a, V4, Local> { /// Attempts to decrypt a PASETO token /// ``` /// # use serde_json::json; /// # use rusty_paseto::core::*; /// # let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); /// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; /// # let nonce = PasetoNonce::::from(&nonce); /// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); /// # let payload = payload.as_str(); /// # let payload = Payload::from(payload); /// # let token = Paseto::::builder().set_payload(payload).try_encrypt(&key, &nonce)?; /// // decrypt a public v4 token /// let json = Paseto::::try_decrypt(&token, &key, None, None)?; /// # assert_eq!(payload, json); /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn try_decrypt( token: &'a str, key: &PasetoSymmetricKey, footer: (impl Into>> + Copy), implicit_assertion: (impl Into>> + Copy), ) -> Result { //get footer let decoded_payload = Self::parse_raw_token(token, footer, &V4::default(), &Local::default())?; let nonce = Key::from(&decoded_payload[..32]); let nonce = PasetoNonce::::from(&nonce); let authentication_key = AuthenticationKey::::from(&(AuthenticationKeySeparator::default() + &nonce), key); let encryption_key = EncryptionKey::::from(&(EncryptionKeySeparator::default() + &nonce), key); let ciphertext = &decoded_payload[32..(decoded_payload.len() - 32)]; //pack preauth let pae = PreAuthenticationEncoding::parse(&[ &Header::::default(), nonce.as_ref(), ciphertext, &footer.into().unwrap_or_default(), &implicit_assertion.into().unwrap_or_default(), ]); //generate tags let tag = &decoded_payload[(nonce.len() + ciphertext.len())..]; let tag2 = &Tag::::from(authentication_key, &pae); //compare tags ConstantTimeEquals(tag, tag2)?; //decrypt payload let ciphertext = CipherText::::from(ciphertext, &encryption_key); let decoded_str = str::from_utf8(&ciphertext)?; //return decrypted payload Ok(decoded_str.to_owned()) } /// Attempts to encrypt a PASETO token /// ``` /// # use serde_json::json; /// # use rusty_paseto::core::*; /// # let key = PasetoSymmetricKey::::from(Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?); /// # let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; /// # // generate a random nonce with /// # // let nonce = Key::<32>::try_new_random()?; /// # let nonce = PasetoNonce::::from(&nonce); /// # let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); /// # let payload = payload.as_str(); /// # let payload = Payload::from(payload); /// //create a public v4 token /// let token = Paseto::::builder().set_payload(payload).try_encrypt(&key, &nonce)?; /// # let json = Paseto::::try_decrypt(&token, &key, None, None)?; /// # assert_eq!(payload, json); /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn try_encrypt( &mut self, key: &PasetoSymmetricKey, nonce: &PasetoNonce, ) -> Result { //setup let footer = self.footer.unwrap_or_default(); let implicit_assertion = self.implicit_assertion.unwrap_or_default(); //split key let authentication_key = AuthenticationKey::::from(&(AuthenticationKeySeparator::default() + nonce), key); let encryption_key = EncryptionKey::::from(&(EncryptionKeySeparator::default() + nonce), key); //encrypt payload let ciphertext = CipherText::::from(&self.payload, &encryption_key); //pack preauth let pae = PreAuthenticationEncoding::parse(&[&self.header, nonce.as_ref(), &ciphertext, &footer, &implicit_assertion]); //generate tag let tag = Tag::::from(authentication_key, &pae); //generate appended and base64 encoded payload let raw_payload = RawPayload::::try_from(nonce, &ciphertext, &tag)?; //format as paseto with header and optional footer Ok(self.format_token(&raw_payload)) } } rusty_paseto-0.7.1/src/core/paseto_impl/v4_public.rs000064400000000000000000000044351046102023000206700ustar 00000000000000#![cfg(feature = "v4_public")] use ed25519_dalek::{Signature, Signer, SigningKey, Verifier, VerifyingKey}; use crate::core::{Footer, Header, ImplicitAssertion, Paseto, PasetoAsymmetricPrivateKey, PasetoAsymmetricPublicKey, PasetoError, Public, V4}; use crate::core::common::{PreAuthenticationEncoding, RawPayload}; impl<'a> Paseto<'a, V4, Public> { pub fn try_verify( signature: &'a str, public_key: &PasetoAsymmetricPublicKey, footer: (impl Into>> + Copy), implicit_assertion: (impl Into>> + Copy), ) -> Result { let decoded_payload = Self::parse_raw_token(signature, footer, &V4::default(), &Public::default())?; let verifying_key: VerifyingKey = VerifyingKey::from_bytes(<&[u8; 32]>::try_from(public_key.as_ref())?)?; let msg = decoded_payload[..(decoded_payload.len() - ed25519_dalek::SIGNATURE_LENGTH)].as_ref(); let sig = decoded_payload[msg.len()..msg.len() + ed25519_dalek::SIGNATURE_LENGTH].as_ref(); let signature = Signature::try_from(sig)?; let pae = PreAuthenticationEncoding::parse(&[ &Header::::default(), msg, &footer.into().unwrap_or_default(), &implicit_assertion.into().unwrap_or_default(), ]); verifying_key.verify(&pae, &signature)?; // public_key.verify(&pae, &signature)?; Ok(String::from_utf8(Vec::from(msg))?) } pub fn try_sign(&mut self, key: &PasetoAsymmetricPrivateKey) -> Result { let footer = self.footer.unwrap_or_default(); let assertion = self.implicit_assertion.unwrap_or_default(); // let secret_key : SecretKey = SecretKey::try_from(key.as_ref())?; let signing_key = SigningKey::from_keypair_bytes(<&[u8; 64]>::try_from(key.as_ref())?)?; // let keypair = Keypair::from_bytes(key.as_ref())?; let pae = PreAuthenticationEncoding::parse(&[&self.header, &self.payload, &footer, &assertion]); let signature = signing_key.sign(&pae); let raw_payload = RawPayload::::from(&self.payload, &signature.to_bytes()); Ok(self.format_token(&raw_payload)) } } rusty_paseto-0.7.1/src/core/payload.rs000064400000000000000000000014321046102023000161100ustar 00000000000000use super::traits::Base64Encodable; use std::fmt; use std::ops::Deref; /// The token payload #[derive(Default, Debug, Clone, Copy)] pub struct Payload<'a>(&'a str); impl Base64Encodable for Payload<'_> {} impl<'a> Deref for Payload<'a> { type Target = [u8]; fn deref(&self) -> &'a Self::Target { self.0.as_bytes() } } impl<'a> AsRef for Payload<'a> { fn as_ref(&self) -> &str { self.0 } } impl<'a> From<&'a str> for Payload<'a> { fn from(s: &'a str) -> Self { Self(s) } } impl<'a, R> PartialEq for Payload<'a> where R: AsRef, { fn eq(&self, other: &R) -> bool { self.as_ref() == other.as_ref() } } impl<'a> fmt::Display for Payload<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } rusty_paseto-0.7.1/src/core/purpose/local.rs000064400000000000000000000007041046102023000172470ustar 00000000000000use crate::core::traits::*; use std::fmt; use std::fmt::Display; /// Symmetric encryption #[derive(Debug, Clone, Copy)] pub struct Local(&'static str); impl PurposeTrait for Local {} impl Default for Local { fn default() -> Self { Self("local") } } impl AsRef for Local { fn as_ref(&self) -> &str { self.0 } } impl Display for Local { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } rusty_paseto-0.7.1/src/core/purpose/mod.rs000064400000000000000000000001051046102023000167270ustar 00000000000000mod local; mod public; pub use local::Local; pub use public::Public; rusty_paseto-0.7.1/src/core/purpose/public.rs000064400000000000000000000007511046102023000174350ustar 00000000000000use crate::core::traits::*; use std::fmt; use std::fmt::Display; /// Asymmetric authentication (public-key signatures) #[derive(Debug, Clone, Copy)] pub struct Public(&'static str); impl PurposeTrait for Public {} impl AsRef for Public { fn as_ref(&self) -> &str { self.0 } } impl Default for Public { fn default() -> Self { Self("public") } } impl Display for Public { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } rusty_paseto-0.7.1/src/core/traits.rs000064400000000000000000000025001046102023000157620ustar 00000000000000use base64::DecodeError; use base64::prelude::*; use ring::constant_time::verify_slices_are_equal as ConstantTimeEquals; use std::fmt::Display; //marker traits /// Used by marker traits to determine at compile time which PASETO version the user is attempting to use pub trait VersionTrait: Display + Default + AsRef {} /// Used by marker traits to determine at compile time which PASETO purpose the user is attempting to use pub trait PurposeTrait: Display + Default + AsRef {} pub trait V1orV3: VersionTrait {} /// A marker trait used to determine if the PASETO token version is capable of using an implicit /// assertion. Currently this applies only to V3/V4 PASETO tokens pub trait ImplicitAssertionCapable: VersionTrait {} pub trait V2orV4: VersionTrait {} /// Enable a type to encode/decode to/from base64 and compare itself to another implementer using /// constant time comparision pub(crate) trait Base64Encodable>: Display + AsRef { fn encode(&self) -> String { BASE64_URL_SAFE_NO_PAD.encode(self.as_ref()) } fn decode(&self) -> Result, DecodeError> { BASE64_URL_SAFE_NO_PAD.decode(self.as_ref()) } fn constant_time_equals(&self, other: B) -> bool where B: AsRef, { ConstantTimeEquals(self.encode().as_ref(), other.as_ref().as_bytes()).is_ok() } } rusty_paseto-0.7.1/src/core/version/mod.rs000064400000000000000000000005511046102023000167240ustar 00000000000000#[cfg(any(feature = "v1", doc))] mod v1; #[cfg(any(feature = "v2", doc))] mod v2; #[cfg(any(feature = "v3", doc))] mod v3; #[cfg(any(feature = "v4", doc))] mod v4; #[cfg(any(feature = "v1", doc))] pub use v1::V1; #[cfg(any(feature = "v2", doc))] pub use v2::V2; #[cfg(any(feature = "v3", doc))] pub use v3::V3; #[cfg(any(feature = "v4", doc))] pub use v4::V4; rusty_paseto-0.7.1/src/core/version/v1.rs000064400000000000000000000033741046102023000165010ustar 00000000000000#![cfg(any(feature = "v1", doc))] use crate::core::traits::*; use std::fmt; use std::fmt::Display; /// ## Version 1: NIST Compatibility /// /// See [the version 1 specification](https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version1.md) for details. At a glance: /// /// * **`v1.local`**: Symmetric Authenticated Encryption: /// * AES-256-CTR + HMAC-SHA384 (Encrypt-then-MAC) /// * Key-splitting: HKDF-SHA384 /// * Info for encryption key: `paseto-encryption-key` /// * Info for authentication key: `paseto-auth-key-for-aead` /// * 32-byte nonce (first half for AES-CTR, latter half for the HKDF salt) /// * The nonce calculated from HMAC-SHA384(message, `random_bytes(32)`) /// truncated to 32 bytes, during encryption only /// * The HMAC covers the header, nonce, and ciphertext /// * It also covers the footer, if provided /// * **`v1.public`**: Asymmetric Authentication (Public-Key Signatures): /// * 2048-bit RSA keys /// * RSASSA-PSS with /// * Hash function: SHA384 as the hash function /// * Mask generation function: MGF1+SHA384 /// * Public exponent: 65537 /// /// Version 1 implements the best possible RSA + AES + SHA2 ciphersuite. We only use /// OAEP and PSS for RSA encryption and RSA signatures (respectively), never PKCS1v1.5. /// /// Version 1 is recommended only for legacy systems that cannot use modern cryptography. #[derive(Debug, Clone, Copy)] pub struct V1(&'static str); impl AsRef for V1 { fn as_ref(&self) -> &str { self.0 } } impl V1orV3 for V1 {} impl VersionTrait for V1 {} impl Default for V1 { fn default() -> Self { Self("v1") } } impl Display for V1 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } rusty_paseto-0.7.1/src/core/version/v2.rs000064400000000000000000000034221046102023000164740ustar 00000000000000#![cfg(any(feature = "v2", doc))] use crate::core::traits::*; use std::fmt; use std::fmt::Display; /// ## Version 2: Sodium Original /// /// See [the version 2 specification](https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version2.md) for details. At a glance: /// /// * **`v2.local`**: Symmetric Encryption: /// * XChaCha20-Poly1305 (192-bit nonce, 256-bit key, 128-bit authentication tag) /// * Encrypting: `sodium_crypto_aead_xchacha20poly1305_ietf_encrypt()` /// * Decrypting: `sodium_crypto_aead_xchacha20poly1305_ietf_decrypt()` /// * The nonce is calculated from `sodium_crypto_generichash()` of the message, /// with a BLAKE2b key provided by `random_bytes(24)` and an output length of 24, /// during encryption only /// * Reference implementation in [Version2.php](https://github.com/paragonie/paseto/blob/master/src/Protocol/Version2.php): /// * See `aeadEncrypt()` for encryption /// * See `aeadDecrypt()` for decryption /// * **`v2.public`**: Asymmetric Authentication (Public-Key Signatures): /// * Ed25519 (EdDSA over Curve25519) /// * Signing: `sodium_crypto_sign_detached()` /// * Verifying: `sodium_crypto_sign_verify_detached()` /// * Reference implementation in [Version2.php](https://github.com/paragonie/paseto/blob/master/src/Protocol/Version2.php): /// * See `sign()` for signature generation /// * See `verify()` for signature verification #[derive(Debug, Clone, Copy)] pub struct V2(&'static str); impl VersionTrait for V2 {} impl AsRef for V2 { fn as_ref(&self) -> &str { self.0 } } impl V2orV4 for V2 {} impl Default for V2 { fn default() -> Self { Self("v2") } } impl Display for V2 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } rusty_paseto-0.7.1/src/core/version/v3.rs000064400000000000000000000035151046102023000165000ustar 00000000000000#![cfg(any(feature = "v3", doc))] use crate::core::traits::*; use std::fmt; use std::fmt::Display; /// ## Version 3: NIST Modern /// /// See [the version 3 specification](https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version3.md) for details. At a glance: /// /// * **`v3.local`**: Symmetric Authenticated Encryption: /// * AES-256-CTR + HMAC-SHA384 (Encrypt-then-MAC) /// * Key-splitting: HKDF-SHA384 /// * Info for encryption key: `paseto-encryption-key` /// The encryption key and implicit counter nonce are both returned /// from HKDF in this version. /// * Info for authentication key: `paseto-auth-key-for-aead` /// * 32-byte nonce (no longer prehashed), passed entirely to HKDF /// (as part of the `info` tag, rather than as a salt). /// * The HMAC covers the header, nonce, and ciphertext /// * It also covers the footer, if provided /// * It also covers the implicit assertions, if provided /// * **`v3.public`**: Asymmetric Authentication (Public-Key Signatures): /// * ECDSA over NIST P-384, with SHA-384, /// using [RFC 6979 deterministic k-values](https://tools.ietf.org/html/rfc6979) /// (if reasonably practical; otherwise a CSPRNG **MUST** be used). /// Hedged signatures are allowed too. /// * The public key is also included in the PAE step, to ensure /// `v3.public` tokens provide Exclusive Ownership. #[derive(Debug, Clone, Copy)] pub struct V3(&'static str); impl VersionTrait for V3 {} impl AsRef for V3 { fn as_ref(&self) -> &str { self.0 } } impl ImplicitAssertionCapable for V3 {} impl V1orV3 for V3 {} impl Default for V3 { fn default() -> Self { Self("v3") } } impl Display for V3 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } rusty_paseto-0.7.1/src/core/version/v4.rs000064400000000000000000000030721046102023000164770ustar 00000000000000#![cfg(any(feature = "v4", doc))] use crate::core::traits::*; use std::fmt; use std::fmt::Display; /// ## Version 4: Sodium Modern /// /// See [the version 4 specification](https://github.com/paseto-standard/paseto-spec/blob/master/docs/01-Protocol-Versions/Version4.md) for details. At a glance: /// /// * **`v4.local`**: Symmetric Authenticated Encryption: /// * XChaCha20 + BLAKE2b-MAC (Encrypt-then-MAC) /// * Key-splitting: BLAKE2b /// * Info for encryption key: `paseto-encryption-key` /// The encryption key and implicit counter nonce are both returned /// from BLAKE2b in this version. /// * Info for authentication key: `paseto-auth-key-for-aead` /// * 32-byte nonce (no longer prehashed), passed entirely to BLAKE2b. /// * The BLAKE2b-MAC covers the header, nonce, and ciphertext /// * It also covers the footer, if provided /// * It also covers the implicit assertions, if provided /// * **`v4.public`**: Asymmetric Authentication (Public-Key Signatures): /// * Ed25519 (EdDSA over Curve25519) /// * Signing: `sodium_crypto_sign_detached()` /// * Verifying: `sodium_crypto_sign_verify_detached()` #[derive(Debug, Clone, Copy)] pub struct V4(&'static str); impl VersionTrait for V4 {} impl ImplicitAssertionCapable for V4 {} impl V2orV4 for V4 {} impl AsRef for V4 { fn as_ref(&self) -> &str { self.0 } } impl Default for V4 { fn default() -> Self { Self("v4") } } impl Display for V4 { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.0) } } rusty_paseto-0.7.1/src/generic/builders/error.rs000064400000000000000000000017441046102023000201130ustar 00000000000000use crate::generic::PasetoClaimError; use thiserror::Error; /// Errors raised by the generic builder when adding claims or encrypting or signing PASETO tokens. #[derive(Debug, Error)] pub enum GenericBuilderError { /// A generic claim error #[error(transparent)] ClaimError { #[from] source: PasetoClaimError, }, ///An error with a invalid malformed iso8601 email address #[error("{0} is an invalid iso8601 (email) string")] BadEmailAddress(String), ///An error indicating a duplicate top level claim in the token #[error("The claim '{0}' appears more than once in the top level payload json")] DuplicateTopLevelPayloadClaim(String), ///A generic cipher error #[error("A paseto cipher error occurred")] CipherError { #[from] source: crate::core::PasetoError, }, ///A JSON serialization error with the token payload #[error("The payload was unable to be serialized into json")] PayloadJsonError { #[from] source: serde_json::Error, }, } rusty_paseto-0.7.1/src/generic/builders/generic_builder.rs000064400000000000000000001725041046102023000221070ustar 00000000000000use core::marker::PhantomData; use std::collections::HashMap; use erased_serde::Serialize; use serde_json::{Map, Value}; use crate::generic::*; ///The GenericBuilder is created at compile time by specifying a PASETO version and purpose and ///providing a key of the same version and purpose. This structure allows setting [PASETO claims](https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/04-Claims.md), ///your own [custom claims](CustomClaim), an optional [footer](Footer) and in the case of V3/V4 tokens, an optional [implicit ///assertion](ImplicitAssertion). /// ///The intent of the GenericBuilder is to allow the user to wrap basic PASETO standard ///functionality with their own custom business rules or ergonomic API. For most users, the batteries-included ///[paseto builder](crate::prelude::PasetoBuilder) will be all they need. More advanced cases can wrap this ///or the [core](Paseto) struct to accomplish custom functionality. /// ///# Usage /// ///``` ///# #[cfg(all(feature = "generic", feature="v2_local"))] ///# { /// use rusty_paseto::generic::*; /// let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); /// let footer = Footer::from("some footer"); /// //create a builder, add some claims and then build the token with the key /// let token = GenericBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .try_encrypt(&key)?; /// //now let's decrypt the token and verify the values /// let json = GenericParser::::default() /// .set_footer(footer) /// .parse(&token, &key)?; /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` pub struct GenericBuilder<'a, 'b, Version, Purpose> { version: PhantomData, purpose: PhantomData, claims: HashMap>, footer: Option>, implicit_assertion: Option>, } impl<'a, 'b, Version, Purpose> GenericBuilder<'a, 'b, Version, Purpose> { pub fn new() -> Self { Self { version: PhantomData::, purpose: PhantomData::, claims: HashMap::with_capacity(10), footer: None, implicit_assertion: None, } } ///Removes a [claim](PasetoClaim) from the internal list of claims by passed key pub fn remove_claim(&mut self, claim_key: &str) -> &mut Self { self.claims.remove(claim_key); self } ///Allows adding multiple [claims](PasetoClaim) at once by passing a Hashmap of claim keys and values pub fn extend_claims(&mut self, value: HashMap>) -> &mut Self { self.claims.extend(value); self } ///Adds a [claim](PasetoClaim) to the token builder pub fn set_claim(&mut self, value: T) -> &mut Self where 'b: 'a, { let key = value.get_key().to_owned(); // Ignore empty keys if key.is_empty() { return self; } // Serialize the claim value to serde_json::Value let mut serialized_value = Vec::new(); let mut serializer = serde_json::Serializer::new(&mut serialized_value); erased_serde::serialize(&value, &mut serializer).unwrap(); let value_json: serde_json::Value = serde_json::from_slice(&serialized_value).unwrap(); // Handle the special case for Null values let value = match value_json { serde_json::Value::Object(mut obj) => { if obj.len() == 1 && obj.contains_key(&key) { obj.remove(&key).unwrap() } else { serde_json::Value::Object(obj) } } other => other, }; // Insert the processed claim into the claims map self.claims.insert(key, Box::new(value)); self } ///Adds an optional [footer](Footer) to the token builder pub fn set_footer(&mut self, footer: Footer<'a>) -> &mut Self { self.footer = Some(footer); self } /// Builds a JSON payload from the claims /// /// # Returns /// A `Result` containing the JSON payload as a `String` or a `serde_json::Error` /// Fixes (issue #39)[https://github.com/rrrodzilla/rusty_paseto/issues/39] reported by @xbb pub fn build_payload_from_claims(&mut self) -> Result { // Take the claims from the builder, replacing it with an empty HashMap let claims = std::mem::take(&mut self.claims); // Serialize each claim to a serde_json::Value let serialized_claims: HashMap = claims .into_iter() .map(|(k, v)| (k, serde_json::to_value(v).unwrap_or(Value::Null))) .collect(); // Wrap the serialized claims to ensure proper nesting let wrapped_claims = wrap_claims(serialized_claims); // Convert the wrapped claims to a JSON string serde_json::to_string(&wrapped_claims) } } // Wrap claims in an outer JSON object to ensure proper nesting // // # Parameters // - `claims`: A `HashMap` containing the claims as `serde_json::Value` // // # Returns // A `serde_json::Value` representing the wrapped claims fn wrap_claims(claims: HashMap) -> Value { // Recursively wrap each claim value let wrapped: HashMap = claims .into_iter() .map(|(k, v)| (k, wrap_value(v))) .collect(); // Return the wrapped claims as a JSON object Value::Object(Map::from_iter(wrapped)) } // Recursively wrap values to ensure all values are valid JSON objects // // # Parameters // - `value`: A `serde_json::Value` to be wrapped // // # Returns // A `serde_json::Value` representing the wrapped value fn wrap_value(value: Value) -> Value { match value { // If the value is an object, check if it's empty Value::Object(map) => { if map.is_empty() { // Wrap empty map as an empty JSON object Value::Object(Map::new()) } else { // Recursively wrap each key-value pair in the map Value::Object(map.into_iter().map(|(k, v)| (k, wrap_value(v))).collect()) } } // If the value is an array, recursively wrap each element Value::Array(arr) => Value::Array(arr.into_iter().map(wrap_value).collect()), // If the value is null, return it as is Value::Null => Value::Null, // For primitive values, return them as is other => other, } } impl<'a, 'b, Version, Purpose> GenericBuilder<'a, 'b, Version, Purpose> where Version: ImplicitAssertionCapable, { ///Adds an optional [implicit assertion](ImplicitAssertion) to the token builder for V3/V4 ///tokens only pub fn set_implicit_assertion(&mut self, implicit_assertion: ImplicitAssertion<'a>) -> &mut Self { self.implicit_assertion = Some(implicit_assertion); self } } impl Default for GenericBuilder<'_, '_, Version, Purpose> { fn default() -> Self { Self::new() } } #[cfg(feature = "v1_local")] impl GenericBuilder<'_, '_, V1, Local> { /// Given a [PasetoSymmetricKey], attempts to encrypt a (V1, Local) PASETO token from the data and /// claims provided to the GenericBuilder. /// /// Returns `Ok(String)` on success, where the String is the encrypted PASETO token, otherwise returns an error. /// /// # Errors /// /// Returns [`GenericBuilderError`] for any errors when building the token string /// for encryption or during ciphertext encryption. /// /// # Example /// /// ///``` ///# #[cfg(all(feature = "generic", feature="v1_local"))] ///# { /// use rusty_paseto::generic::*; /// let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); /// let footer = Footer::from("some footer"); /// //create a builder, add some claims and then build the token with the key /// let token = GenericBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .try_encrypt(&key)?; /// //now let's decrypt the token and verify the values /// let json = GenericParser::::default() /// .set_footer(footer) /// .parse(&token, &key)?; /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn try_encrypt(&mut self, key: &PasetoSymmetricKey) -> Result { let mut token_builder = Paseto::::builder(); let payload = self.build_payload_from_claims()?; token_builder.set_payload(Payload::from(payload.as_str())); if let Some(footer) = &self.footer { token_builder.set_footer(*footer); } let random_nonce = Key::<32>::try_new_random()?; Ok(token_builder.try_encrypt(key, &PasetoNonce::::from(&random_nonce))?) } } #[cfg(feature = "v2_local")] impl GenericBuilder<'_, '_, V2, Local> { /// Given a [PasetoSymmetricKey], attempts to encrypt a (V2, Local) PASETO token from the data and /// claims provided to the GenericBuilder. /// /// Returns `Ok(String)` on success, where the String is the encrypted PASETO token, otherwise returns an error. /// /// # Errors /// /// Returns [`GenericBuilderError`] for any errors when building the token string /// for encryption or during ciphertext encryption. /// /// # Example /// /// ///``` ///# #[cfg(all(feature = "generic", feature="v2_local"))] ///# { /// use rusty_paseto::generic::*; /// let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); /// let footer = Footer::from("some footer"); /// //create a builder, add some claims and then build the token with the key /// let token = GenericBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .try_encrypt(&key)?; /// //now let's decrypt the token and verify the values /// let json = GenericParser::::default() /// .set_footer(footer) /// .parse(&token, &key)?; /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn try_encrypt(&mut self, key: &PasetoSymmetricKey) -> Result { let mut token_builder = Paseto::::builder(); let payload = self.build_payload_from_claims()?; token_builder.set_payload(Payload::from(payload.as_str())); if let Some(footer) = &self.footer { token_builder.set_footer(*footer); } Ok(token_builder.try_encrypt(key, &PasetoNonce::::from(&Key::<24>::try_new_random()?))?) } } #[cfg(feature = "v3_local")] impl GenericBuilder<'_, '_, V3, Local> { /// Given a [PasetoSymmetricKey], attempts to encrypt a (V3, Local) PASETO token from the data and /// claims provided to the GenericBuilder. /// /// Returns `Ok(String)` on success, where the String is the encrypted PASETO token, otherwise returns an error. /// /// # Errors /// /// Returns [`GenericBuilderError`] for any errors when building the token string /// for encryption or during ciphertext encryption. /// /// # Example /// /// ///``` ///# #[cfg(all(feature = "generic", feature="v3_local"))] ///# { /// use rusty_paseto::generic::*; /// let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); /// let footer = Footer::from("some footer"); /// let implicit_assertion = ImplicitAssertion::from("some assertion"); /// //create a builder, add some claims and then build the token with the key /// let token = GenericBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .set_implicit_assertion(implicit_assertion) /// .try_encrypt(&key)?; /// //now let's decrypt the token and verify the values /// let json = GenericParser::::default() /// .set_footer(footer) /// .set_implicit_assertion(implicit_assertion) /// .parse(&token, &key)?; /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn try_encrypt(&mut self, key: &PasetoSymmetricKey) -> Result { let mut token_builder = Paseto::::builder(); let payload = self.build_payload_from_claims()?; token_builder.set_payload(Payload::from(payload.as_str())); if let Some(footer) = &self.footer { token_builder.set_footer(*footer); } if let Some(implicit_assertion) = &self.implicit_assertion { token_builder.set_implicit_assertion(*implicit_assertion); } let nonce = Key::<32>::try_new_random()?; let nonce = PasetoNonce::::from(&nonce); Ok(token_builder.try_encrypt(key, &nonce)?) } } #[cfg(feature = "v4_local")] impl GenericBuilder<'_, '_, V4, Local> { /// Given a [PasetoSymmetricKey], attempts to encrypt a (V4, Local) PASETO token from the data and /// claims provided to the GenericBuilder. /// /// Returns `Ok(String)` on success, where the String is the encrypted PASETO token, otherwise returns an error. /// /// # Errors /// /// Returns [`GenericBuilderError`] for any errors when building the token string /// for encryption or during ciphertext encryption. /// /// # Example /// /// ///``` ///# #[cfg(all(feature = "generic", feature="v4_local"))] ///# { /// use rusty_paseto::generic::*; /// let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); /// let footer = Footer::from("some footer"); /// let implicit_assertion = ImplicitAssertion::from("some assertion"); /// //create a builder, add some claims and then build the token with the key /// let token = GenericBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .set_implicit_assertion(implicit_assertion) /// .try_encrypt(&key)?; /// //now let's decrypt the token and verify the values /// let json = GenericParser::::default() /// .set_footer(footer) /// .set_implicit_assertion(implicit_assertion) /// .parse(&token, &key)?; /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn try_encrypt(&mut self, key: &PasetoSymmetricKey) -> Result { let mut token_builder = Paseto::::builder(); let payload = self.build_payload_from_claims()?; token_builder.set_payload(Payload::from(payload.as_str())); if let Some(footer) = &self.footer { token_builder.set_footer(*footer); } if let Some(implicit_assertion) = &self.implicit_assertion { token_builder.set_implicit_assertion(*implicit_assertion); } let nonce = Key::<32>::try_new_random()?; let nonce = PasetoNonce::::from(&nonce); Ok(token_builder.try_encrypt(key, &nonce)?) } } #[cfg(feature = "v1_public")] impl GenericBuilder<'_, '_, V1, Public> { /// Given a [PasetoAsymmetricPrivateKey], attempts to sign a ([V1], [Public]) PASETO token from the data and /// claims provided to the GenericBuilder with an optional [Footer]. /// /// Returns `Ok(String)` on success, where the String is the signed PASETO token, otherwise returns an error. /// /// # Errors /// /// Returns [`GenericBuilderError`] for any errors when building the token string /// for signing or during signing. /// /// # Example /// ///``` ///# #[cfg(all(feature = "generic", feature="v1_public"))] ///# { /// # use rusty_paseto::generic::*; /// //obtain a private key (pk) /// # let private_key = include_bytes!("../../../tests/v1_public_test_vectors_private_key.pk8"); /// # let pk: &[u8] = private_key; /// let private_key = PasetoAsymmetricPrivateKey::::from(pk); /// let footer = Footer::from("some footer"); /// //sign a public V1 token /// let token = GenericBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .try_sign(&private_key)?; /// //obtain a public key (pubk) /// # let public_key = include_bytes!("../../../tests/v1_public_test_vectors_public_key.der"); /// # let pubk: &[u8] = public_key; /// let public_key = PasetoAsymmetricPublicKey::::from(pubk); /// //now let's try to verify it /// let json = GenericParser::::default() /// .set_footer(footer) /// .check_claim(AudienceClaim::from("customers")) /// .check_claim(SubjectClaim::from("loyal subjects")) /// .check_claim(IssuerClaim::from("me")) /// .check_claim(TokenIdentifierClaim::from("me")) /// .check_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .check_claim(CustomClaim::try_from(("seats", 4))?) /// .check_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .parse(&token, &public_key)?; /// // we can access all the values from the serde Value object returned by the parser /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) ///``` pub fn try_sign(&mut self, key: &PasetoAsymmetricPrivateKey) -> Result { let mut token_builder = Paseto::::builder(); let payload = self.build_payload_from_claims()?; token_builder.set_payload(Payload::from(payload.as_str())); if let Some(footer) = &self.footer { token_builder.set_footer(*footer); } Ok(token_builder.try_sign(key)?) } } #[cfg(feature = "v2_public")] impl GenericBuilder<'_, '_, V2, Public> { /// Given a [PasetoAsymmetricPrivateKey], attempts to sign a ([V2], [Public]) PASETO token from the data and /// claims provided to the GenericBuilder with an optional [Footer]. /// /// Returns `Ok(String)` on success, where the String is the signed PASETO token, otherwise returns an error. /// /// # Errors /// /// Returns [`GenericBuilderError`] for any errors when building the token string /// for signing or during signing. /// /// # Example /// ///``` ///# #[cfg(all(feature = "generic", feature="v1_public"))] ///# { /// # use rusty_paseto::generic::*; /// //obtain a key /// let private_key = Key::<64>::try_from("b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; /// let private_key = PasetoAsymmetricPrivateKey::::from(&private_key); /// let public_key = Key::<32>::try_from("1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; /// let public_key = PasetoAsymmetricPublicKey::::from(&public_key); /// let footer = Footer::from("some footer"); /// //sign a public V2 token /// let token = GenericBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .try_sign(&private_key)?; /// //now let's try to verify it /// let json = GenericParser::::default() /// .set_footer(footer) /// .check_claim(AudienceClaim::from("customers")) /// .check_claim(SubjectClaim::from("loyal subjects")) /// .check_claim(IssuerClaim::from("me")) /// .check_claim(TokenIdentifierClaim::from("me")) /// .check_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .check_claim(CustomClaim::try_from(("seats", 4))?) /// .check_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .parse(&token, &public_key)?; /// // we can access all the values from the serde Value object returned by the parser /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) ///``` pub fn try_sign(&mut self, key: &PasetoAsymmetricPrivateKey) -> Result { let mut token_builder = Paseto::::builder(); let payload = self.build_payload_from_claims()?; token_builder.set_payload(Payload::from(payload.as_str())); if let Some(footer) = &self.footer { token_builder.set_footer(*footer); } Ok(token_builder.try_sign(key)?) } } #[cfg(feature = "v3_public")] impl GenericBuilder<'_, '_, V3, Public> { /// Given a [PasetoAsymmetricPrivateKey], attempts to sign a ([V3], [Public]) PASETO token from the data and /// claims provided to the GenericBuilder with an optional [Footer] and an optional /// [ImplicitAssertion]. /// /// Returns `Ok(String)` on success, where the String is the signed PASETO token, otherwise returns an error. /// /// # Errors /// /// Returns [`GenericBuilderError`] for any errors when building the token string /// for signing or during signing. /// /// # Example /// ///``` ///# #[cfg(all(feature = "generic", feature="v3_public"))] ///# { /// # use rusty_paseto::generic::*; /// //obtain a key /// let private_key = Key::<48>::try_from( /// "20347609607477aca8fbfbc5e6218455f3199669792ef8b466faa87bdc67798144c848dd03661eed5ac62461340cea96", /// )?; /// let private_key = PasetoAsymmetricPrivateKey::::from(&private_key); /// let public_key = Key::<49>::try_from( /// "02fbcb7c69ee1c60579be7a334134878d9c5c5bf35d552dab63c0140397ed14cef637d7720925c44699ea30e72874c72fb", /// )?; /// let public_key = PasetoAsymmetricPublicKey::::try_from(&public_key)?; /// let footer = Footer::from("some footer"); /// let implicit_assertion = ImplicitAssertion::from("some assertion"); /// //sign a public V3 token /// let token = GenericBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .set_implicit_assertion(implicit_assertion) /// .try_sign(&private_key)?; /// //now let's try to verify it /// let json = GenericParser::::default() /// .set_footer(footer) /// .check_claim(AudienceClaim::from("customers")) /// .set_implicit_assertion(implicit_assertion) /// .check_claim(SubjectClaim::from("loyal subjects")) /// .check_claim(IssuerClaim::from("me")) /// .check_claim(TokenIdentifierClaim::from("me")) /// .check_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .check_claim(CustomClaim::try_from(("seats", 4))?) /// .check_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .parse(&token, &public_key)?; /// // we can access all the values from the serde Value object returned by the parser /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) ///``` pub fn try_sign(&mut self, key: &PasetoAsymmetricPrivateKey) -> Result { let mut token_builder = Paseto::::builder(); let payload = self.build_payload_from_claims()?; token_builder.set_payload(Payload::from(payload.as_str())); if let Some(footer) = &self.footer { token_builder.set_footer(*footer); } if let Some(implicit_assertion) = &self.implicit_assertion { token_builder.set_implicit_assertion(*implicit_assertion); } Ok(token_builder.try_sign(key)?) } } #[cfg(feature = "v4_public")] impl GenericBuilder<'_, '_, V4, Public> { /// Given a [PasetoAsymmetricPrivateKey], attempts to sign a ([V4], [Public]) PASETO token from the data and /// claims provided to the GenericBuilder with an optional [Footer] and an optional /// [ImplicitAssertion]. /// /// Returns `Ok(String)` on success, where the String is the signed PASETO token, otherwise returns an error. /// /// # Errors /// /// Returns [`GenericBuilderError`] for any errors when building the token string /// for signing or during signing. /// /// # Example /// ///``` ///# #[cfg(all(feature = "generic", feature="v4_public"))] ///# { /// # use rusty_paseto::generic::*; /// //create a key /// let private_key = Key::<64>::try_from("b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; /// let pk: &[u8] = private_key.as_slice(); /// let private_key = PasetoAsymmetricPrivateKey::::from(pk); /// let public_key = Key::<32>::try_from("1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; /// let public_key = PasetoAsymmetricPublicKey::::from(&public_key); /// let footer = Footer::from("some footer"); /// let implicit_assertion = ImplicitAssertion::from("some assertion"); /// //sign a public V4 token /// let token = GenericBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .set_implicit_assertion(implicit_assertion) /// .try_sign(&private_key)?; /// //now let's try to verify it /// let json = GenericParser::::default() /// .set_footer(footer) /// .set_implicit_assertion(implicit_assertion) /// .check_claim(AudienceClaim::from("customers")) /// .check_claim(SubjectClaim::from("loyal subjects")) /// .check_claim(IssuerClaim::from("me")) /// .check_claim(TokenIdentifierClaim::from("me")) /// .check_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .check_claim(CustomClaim::try_from(("seats", 4))?) /// .check_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .parse(&token, &public_key)?; /// // we can access all the values from the serde Value object returned by the parser /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) ///``` pub fn try_sign(&mut self, key: &PasetoAsymmetricPrivateKey) -> Result { let mut token_builder = Paseto::::builder(); let payload = self.build_payload_from_claims()?; token_builder.set_payload(Payload::from(payload.as_str())); if let Some(footer) = &self.footer { token_builder.set_footer(*footer); } if let Some(implicit_assertion) = &self.implicit_assertion { token_builder.set_implicit_assertion(*implicit_assertion); } Ok(token_builder.try_sign(key)?) } } #[cfg(all(test, feature = "v4_public"))] mod generic_v4_public_builders { use anyhow::Result; use crate::generic::*; use crate::generic::claims::*; #[test] fn full_generic_v4_public_builder_test() -> Result<()> { //create a key let private_key = Key::<64>::try_from("b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; let pk: &[u8] = private_key.as_slice(); let private_key = PasetoAsymmetricPrivateKey::::from(pk); let public_key = Key::<32>::try_from("1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; let public_key = PasetoAsymmetricPublicKey::::from(&public_key); let footer = Footer::from("some footer"); let implicit_assertion = ImplicitAssertion::from("some assertion"); //sign a public V4 token let token = GenericBuilder::::default() .set_claim(AudienceClaim::from("customers")) .set_claim(SubjectClaim::from("loyal subjects")) .set_claim(IssuerClaim::from("me")) .set_claim(TokenIdentifierClaim::from("me")) .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) .set_claim(CustomClaim::try_from(("seats", 4))?) .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) .set_footer(footer) .set_implicit_assertion(implicit_assertion) .try_sign(&private_key)?; //now let's try to verify it let json = GenericParser::::default() .set_footer(footer) .set_implicit_assertion(implicit_assertion) .check_claim(AudienceClaim::from("customers")) .check_claim(SubjectClaim::from("loyal subjects")) .check_claim(IssuerClaim::from("me")) .check_claim(TokenIdentifierClaim::from("me")) .check_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) .check_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) .check_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) .check_claim(CustomClaim::try_from(("data", "this is a secret message"))?) .check_claim(CustomClaim::try_from(("seats", 4))?) .check_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) .parse(&token, &public_key)?; // we can access all the values from the serde Value object returned by the parser assert_eq!(json["aud"], "customers"); assert_eq!(json["jti"], "me"); assert_eq!(json["iss"], "me"); assert_eq!(json["data"], "this is a secret message"); assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["sub"], "loyal subjects"); assert_eq!(json["pi to 6 digits"], 3.141526); assert_eq!(json["seats"], 4); Ok(()) } } #[cfg(all(test, feature = "v3_public"))] mod generic_v3_public_builders { use anyhow::Result; use crate::generic::*; use crate::generic::claims::*; #[test] fn full_generic_v3_public_builder_test() -> Result<()> { //create a key let private_key = Key::<48>::try_from( "20347609607477aca8fbfbc5e6218455f3199669792ef8b466faa87bdc67798144c848dd03661eed5ac62461340cea96", )?; let private_key = PasetoAsymmetricPrivateKey::::from(&private_key); let public_key = Key::<49>::try_from( "02fbcb7c69ee1c60579be7a334134878d9c5c5bf35d552dab63c0140397ed14cef637d7720925c44699ea30e72874c72fb", )?; let public_key = PasetoAsymmetricPublicKey::::try_from(&public_key)?; let footer = Footer::from("some footer"); let implicit_assertion = ImplicitAssertion::from("some assertion"); //sign a public V3 token let token = GenericBuilder::::default() .set_claim(AudienceClaim::from("customers")) .set_claim(SubjectClaim::from("loyal subjects")) .set_claim(IssuerClaim::from("me")) .set_claim(TokenIdentifierClaim::from("me")) .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) .set_claim(CustomClaim::try_from(("seats", 4))?) .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) .set_footer(footer) .set_implicit_assertion(implicit_assertion) .try_sign(&private_key)?; //now let's try to verify it let json = GenericParser::::default() .set_footer(footer) .set_implicit_assertion(implicit_assertion) .check_claim(AudienceClaim::from("customers")) .check_claim(SubjectClaim::from("loyal subjects")) .check_claim(IssuerClaim::from("me")) .check_claim(TokenIdentifierClaim::from("me")) .check_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) .check_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) .check_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) .check_claim(CustomClaim::try_from(("data", "this is a secret message"))?) .check_claim(CustomClaim::try_from(("seats", 4))?) .check_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) .parse(&token, &public_key)?; // we can access all the values from the serde Value object returned by the parser assert_eq!(json["aud"], "customers"); assert_eq!(json["jti"], "me"); assert_eq!(json["iss"], "me"); assert_eq!(json["data"], "this is a secret message"); assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["sub"], "loyal subjects"); assert_eq!(json["pi to 6 digits"], 3.141526); assert_eq!(json["seats"], 4); Ok(()) } } #[cfg(all(test, feature = "v2_public"))] mod generic_v2_public_builders { use anyhow::Result; use crate::generic::*; use crate::generic::claims::*; #[test] fn full_generic_v2_public_builder_test() -> Result<()> { //create a key let private_key = Key::<64>::try_from("b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; let private_key = PasetoAsymmetricPrivateKey::::from(&private_key); let public_key = Key::<32>::try_from("1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; let public_key = PasetoAsymmetricPublicKey::::from(&public_key); let footer = Footer::from("some footer"); //sign a public V2 token let token = GenericBuilder::::default() .set_claim(AudienceClaim::from("customers")) .set_claim(SubjectClaim::from("loyal subjects")) .set_claim(IssuerClaim::from("me")) .set_claim(TokenIdentifierClaim::from("me")) .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) .set_claim(CustomClaim::try_from(("seats", 4))?) .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) .set_footer(footer) .try_sign(&private_key)?; //now let's try to verify it let json = GenericParser::::default() .set_footer(footer) .check_claim(AudienceClaim::from("customers")) .check_claim(SubjectClaim::from("loyal subjects")) .check_claim(IssuerClaim::from("me")) .check_claim(TokenIdentifierClaim::from("me")) .check_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) .check_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) .check_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) .check_claim(CustomClaim::try_from(("data", "this is a secret message"))?) .check_claim(CustomClaim::try_from(("seats", 4))?) .check_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) .parse(&token, &public_key)?; // we can access all the values from the serde Value object returned by the parser assert_eq!(json["aud"], "customers"); assert_eq!(json["jti"], "me"); assert_eq!(json["iss"], "me"); assert_eq!(json["data"], "this is a secret message"); assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["sub"], "loyal subjects"); assert_eq!(json["pi to 6 digits"], 3.141526); assert_eq!(json["seats"], 4); Ok(()) } } #[cfg(all(test, feature = "v1_public"))] mod generic_v1_public_builders { use anyhow::Result; use crate::generic::*; use crate::generic::claims::*; #[test] fn full_generic_v1_public_builder_test() -> Result<()> { //create a key let private_key = include_bytes!("../../../tests/v1_public_test_vectors_private_key.pk8"); let pk: &[u8] = private_key; let private_key = PasetoAsymmetricPrivateKey::::from(pk); let public_key = include_bytes!("../../../tests/v1_public_test_vectors_public_key.der"); let pubk: &[u8] = public_key; let public_key = PasetoAsymmetricPublicKey::::from(pubk); let footer = Footer::from("some footer"); //sign a public V1 token let token = GenericBuilder::::default() .set_claim(AudienceClaim::from("customers")) .set_claim(SubjectClaim::from("loyal subjects")) .set_claim(IssuerClaim::from("me")) .set_claim(TokenIdentifierClaim::from("me")) .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) .set_claim(CustomClaim::try_from(("seats", 4))?) .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) .set_footer(footer) .try_sign(&private_key)?; //now let's try to verify it let json = GenericParser::::default() .set_footer(footer) .check_claim(AudienceClaim::from("customers")) .check_claim(SubjectClaim::from("loyal subjects")) .check_claim(IssuerClaim::from("me")) .check_claim(TokenIdentifierClaim::from("me")) .check_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) .check_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) .check_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) .check_claim(CustomClaim::try_from(("data", "this is a secret message"))?) .check_claim(CustomClaim::try_from(("seats", 4))?) .check_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) .parse(&token, &public_key)?; // we can access all the values from the serde Value object returned by the parser assert_eq!(json["aud"], "customers"); assert_eq!(json["jti"], "me"); assert_eq!(json["iss"], "me"); assert_eq!(json["data"], "this is a secret message"); assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["sub"], "loyal subjects"); assert_eq!(json["pi to 6 digits"], 3.141526); assert_eq!(json["seats"], 4); Ok(()) } } #[cfg(all(test, feature = "v3_local"))] mod generic_v3_local_builders { use std::convert::TryFrom; use anyhow::Result; use crate::generic::*; use crate::generic::claims::*; #[test] fn full_generic_v3_local_builder_test() -> Result<()> { //create a key let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); let footer = Footer::from("some footer"); let implicit_assertion = ImplicitAssertion::from("some assertion"); //create a builder, add some claims and then build the token with the key let token = GenericBuilder::::default() .set_claim(AudienceClaim::from("customers")) .set_claim(SubjectClaim::from("loyal subjects")) .set_claim(IssuerClaim::from("me")) .set_claim(TokenIdentifierClaim::from("me")) .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) .set_claim(CustomClaim::try_from(("seats", 4))?) .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) .set_footer(footer) .set_implicit_assertion(implicit_assertion) .try_encrypt(&key)?; //now let's decrypt the token and verify the values let json = GenericParser::::default() .set_footer(footer) .set_implicit_assertion(implicit_assertion) .parse(&token, &key)?; assert_eq!(json["aud"], "customers"); assert_eq!(json["jti"], "me"); assert_eq!(json["iss"], "me"); assert_eq!(json["data"], "this is a secret message"); assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["sub"], "loyal subjects"); assert_eq!(json["pi to 6 digits"], 3.141526); assert_eq!(json["seats"], 4); Ok(()) } } #[cfg(all(test, feature = "default"))] mod tests { use serde::Serialize; use super::*; #[derive(Serialize)] struct TestStruct { field1: String, field2: i32, } #[test] fn test_custom_claim_serialization() { let mut builder = GenericBuilder::::default(); let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); // Create a custom claim with a serializable struct let custom_claim = CustomClaim::try_from(( "custom", TestStruct { field1: "value1".to_string(), field2: 42, }, )) .unwrap(); // Add the custom claim to the builder builder.set_claim(custom_claim); // Build the token let token = builder.try_encrypt(&key).unwrap(); // Decrypt the token and verify the values let json = GenericParser::::default().parse(&token, &key).unwrap(); assert_eq!(json["custom"]["field1"], "value1"); assert_eq!(json["custom"]["field2"], 42); } #[test] fn test_empty_claims() { let mut builder = GenericBuilder::::default(); let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); // Build the token with no claims let token = builder.try_encrypt(&key).unwrap(); // Decrypt the token and verify the values let json = GenericParser::::default().parse(&token, &key).unwrap(); assert!(json.as_object().unwrap().is_empty()); } #[test] fn test_null_claims() { let mut builder = GenericBuilder::::default(); let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); // Create a custom claim with a null value let custom_claim = CustomClaim::try_from(("custom", Value::Null)).unwrap(); builder.set_claim(custom_claim); // Build the token let token = builder.try_encrypt(&key).unwrap(); // Decrypt the token and verify the values let json = GenericParser::::default().parse(&token, &key).unwrap(); assert_eq!(json["custom"], Value::Null); } #[test] fn test_nested_structures() { let mut builder = GenericBuilder::::default(); let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); // Create nested claims let nested_claim = CustomClaim::try_from(( "nested", serde_json::json!({ "level1": { "level2": { "field": "value" } } }), )) .unwrap(); builder.set_claim(nested_claim); // Build the token let token = builder.try_encrypt(&key).unwrap(); // Decrypt the token and verify the values let json = GenericParser::::default().parse(&token, &key).unwrap(); assert_eq!(json["nested"]["level1"]["level2"]["field"], "value"); } #[test] fn test_multiple_claims() { let mut builder = GenericBuilder::::default(); let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); // Create multiple claims builder.set_claim(CustomClaim::try_from(("claim1", "value1")).unwrap()); builder.set_claim(CustomClaim::try_from(("claim2", 42)).unwrap()); builder.set_claim(CustomClaim::try_from(("claim3", true)).unwrap()); // Build the token let token = builder.try_encrypt(&key).unwrap(); // Decrypt the token and verify the values let json = GenericParser::::default().parse(&token, &key).unwrap(); assert_eq!(json["claim1"], "value1"); assert_eq!(json["claim2"], 42); assert_eq!(json["claim3"], true); } #[test] fn test_different_data_types() { let mut builder = GenericBuilder::::default(); let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); // Create claims with different data types builder.set_claim(CustomClaim::try_from(("string", "value")).unwrap()); builder.set_claim(CustomClaim::try_from(("number", 12345)).unwrap()); builder.set_claim(CustomClaim::try_from(("boolean", true)).unwrap()); builder.set_claim(CustomClaim::try_from(("null", Value::Null)).unwrap()); // Build the token let token = builder.try_encrypt(&key).unwrap(); // Decrypt the token and verify the values let json = GenericParser::::default().parse(&token, &key).unwrap(); assert_eq!(json["string"], "value"); assert_eq!(json["number"], 12345); assert_eq!(json["boolean"], true); assert_eq!(json["null"], Value::Null); } } #[cfg(all(test, feature = "v2_local"))] mod builders { use std::convert::TryFrom; use anyhow::Result; use crate::generic::*; use crate::generic::claims::*; #[test] fn full_builder_test() -> Result<()> { //create a key let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); let footer = Footer::from("some footer"); //create a builder, add some claims and then build the token with the key let token = GenericBuilder::::default() .set_claim(AudienceClaim::from("customers")) .set_claim(SubjectClaim::from("loyal subjects")) .set_claim(IssuerClaim::from("me")) .set_claim(TokenIdentifierClaim::from("me")) .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) .set_claim(CustomClaim::try_from(("seats", 4))?) .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) .set_footer(footer) .try_encrypt(&key)?; //now let's decrypt the token and verify the values let json = GenericParser::::default() .set_footer(footer) .parse(&token, &key)?; assert_eq!(json["aud"], "customers"); assert_eq!(json["jti"], "me"); assert_eq!(json["iss"], "me"); assert_eq!(json["data"], "this is a secret message"); assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["sub"], "loyal subjects"); assert_eq!(json["pi to 6 digits"], 3.141526); assert_eq!(json["seats"], 4); Ok(()) } #[test] fn test_dynamic_claims() -> Result<()> { //create a key let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); //create a builder, add some claims dynamically let mut builder = GenericBuilder::::default(); builder.set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?); for n in 1..10 { builder.set_claim(CustomClaim::try_from((format!("n{}", n), n))?); } //and then build the token with the key let token = builder.try_encrypt(&key)?; //now let's decrypt the token and verify the values let json = GenericParser::::default().parse(&token, &key)?; for n in 1..10 { assert_eq!(json[format!("n{}", n)], n); } assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); Ok(()) } #[test] fn test_no_claims() -> Result<()> { let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); //create a builder, add no claims and then build the token with the key let token = GenericBuilder::::default().try_encrypt(&key)?; //now let's decrypt the token and verify the values let decrypted = GenericParser::::default().parse(&token, &key)?; assert_eq!(decrypted.to_string(), "{}"); Ok(()) } } rusty_paseto-0.7.1/src/generic/builders/mod.rs000064400000000000000000000002161046102023000175320ustar 00000000000000mod error; mod generic_builder; mod traits; pub use error::GenericBuilderError; pub use generic_builder::GenericBuilder; //pub use traits::*; rusty_paseto-0.7.1/src/generic/builders/traits.rs000064400000000000000000000000001046102023000202500ustar 00000000000000rusty_paseto-0.7.1/src/generic/claims/audience_claim.rs000064400000000000000000000022061046102023000213350ustar 00000000000000use super::PasetoClaim; #[cfg(feature = "serde")] use serde::ser::SerializeMap; ///The reserved ['aud'](https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/04-Claims.md) PASETO claim #[derive(Clone)] pub struct AudienceClaim<'a>((&'a str, &'a str)); impl<'a> PasetoClaim for AudienceClaim<'a> { fn get_key(&self) -> &str { self.0 .0 } } impl<'a> Default for AudienceClaim<'a> { fn default() -> Self { Self(("aud", "")) } } //created using the From trait impl<'a> From<&'a str> for AudienceClaim<'a> { fn from(s: &'a str) -> Self { Self(("aud", s)) } } //want to receive a reference as a tuple impl<'a> AsRef<(&'a str, &'a str)> for AudienceClaim<'a> { fn as_ref(&self) -> &(&'a str, &'a str) { &self.0 } } #[cfg(feature = "serde")] impl<'a> serde::Serialize for AudienceClaim<'a> { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let mut map = serializer.serialize_map(Some(2))?; map.serialize_key(&self.0 .0)?; map.serialize_value(&self.0 .1)?; //map.serialize_entry(self.0 .0, self.0 .1)?; map.end() } } rusty_paseto-0.7.1/src/generic/claims/custom_claim.rs000064400000000000000000000173501046102023000211000ustar 00000000000000use super::{PasetoClaim, PasetoClaimError}; #[cfg(feature = "serde")] use serde::ser::SerializeMap; ///A custom PASETO claim which can be created with a key and a value T /// ## Setting your own Custom Claims /// /// The CustomClaim struct takes a tuple in the form of `(key: String, value: T)` where T is any /// serializable type /// #### Note: *CustomClaims use the TryFrom trait and return a Result<(), PasetoClaimError> if you attempt to use one of the [reserved PASETO keys](https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/04-Claims.md) in your CustomClaim* /// /// ```rust /// # use rusty_paseto::prelude::*; /// # #[cfg(feature = "default")] /// # { /// # // must include /// # use std::convert::TryFrom; /// # let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); /// let token = PasetoBuilder::::default() /// .set_claim(CustomClaim::try_from(("Co-star", "Morty Smith"))?) /// .set_claim(CustomClaim::try_from(("Universe", 137))?) /// .build(&key)?; /// # } /// # Ok::<(),GenericBuilderError>(()) /// ``` /// /// This throws an error: /// ```should_panic /// # use rusty_paseto::prelude::*; /// # #[cfg(feature = "default")] /// # { /// # // must include /// # use std::convert::TryFrom; /// # let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); /// // "exp" is a reserved PASETO claim key, you should use the ExpirationClaim type /// let token = PasetoBuilder::::default() /// .set_claim(CustomClaim::try_from(("exp", "Some expiration value"))?) /// .build(&key)?; /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` /// # Validating claims /// rusty_paseto allows for flexible claim validation at parse time /// /// ## Checking claims /// /// Let's see how we can check particular claims exist with expected values. /// ``` /// # #[cfg(feature = "default")] /// # { /// # use rusty_paseto::prelude::*; /// # use std::convert::TryFrom; /// /// # // create a key specifying the PASETO version and purpose /// # let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); /// // use a default token builder with the same PASETO version and purpose /// let token = PasetoBuilder::::default() /// .set_claim(SubjectClaim::from("Get schwifty")) /// .set_claim(CustomClaim::try_from(("Contestant", "Earth"))?) /// .set_claim(CustomClaim::try_from(("Universe", 137))?) /// .build(&key)?; /// /// PasetoParser::::default() /// // you can check any claim even custom claims /// .check_claim(SubjectClaim::from("Get schwifty")) /// .check_claim(CustomClaim::try_from(("Contestant", "Earth"))?) /// .check_claim(CustomClaim::try_from(("Universe", 137))?) /// .parse(&token, &key)?; /// /// // no need for the assertions below since the check_claim methods /// // above accomplish the same but at parse time! /// /// //assert_eq!(json_value["sub"], "Get schwifty"); /// //assert_eq!(json_value["Contestant"], "Earth"); /// //assert_eq!(json_value["Universe"], 137); /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` /// /// # Custom validation /// /// What if we have more complex validation requirements? You can pass in a reference to a closure which receives /// the key and value of the claim you want to validate so you can implement any validation logic /// you choose. /// /// Let's see how we can validate our tokens only contain universes with prime numbers: /// /// ``` /// # use rusty_paseto::prelude::*; /// # #[cfg(feature = "default")] /// # { /// # use std::convert::TryFrom; /// /// # // create a key specifying the PASETO version and purpose /// # let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); /// // use a default token builder with the same PASETO version and purpose /// let token = PasetoBuilder::::default() /// .set_claim(SubjectClaim::from("Get schwifty")) /// .set_claim(CustomClaim::try_from(("Contestant", "Earth"))?) /// .set_claim(CustomClaim::try_from(("Universe", 137))?) /// .build(&key)?; /// /// PasetoParser::::default() /// .check_claim(SubjectClaim::from("Get schwifty")) /// .check_claim(CustomClaim::try_from(("Contestant", "Earth"))?) /// .validate_claim(CustomClaim::try_from("Universe")?, &|key, value| { /// //let's get the value /// let universe = value /// .as_u64() /// .ok_or(PasetoClaimError::Unexpected(key.to_string()))?; /// // we only accept prime universes in this app /// if primes::is_prime(universe) { /// Ok(()) /// } else { /// Err(PasetoClaimError::CustomValidation(key.to_string())) /// } /// }) /// .parse(&token, &key)?; /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` /// /// This token will fail to parse with the validation code above: /// ```should_panic /// # #[cfg(feature = "default")] /// # { /// # use rusty_paseto::prelude::*; /// # use std::convert::TryFrom; /// /// # // create a key specifying the PASETO version and purpose /// # let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); /// // 136 is not a prime number /// let token = PasetoBuilder::::default() /// .set_claim(CustomClaim::try_from(("Universe", 136))?) /// .build(&key)?; /// ///# let json_value = PasetoParser::::default() ///# // you can check any claim even custom claims ///# .validate_claim(CustomClaim::try_from("Universe")?, &|key, value| { ///# //let's get the value ///# let universe = value ///# .as_u64() ///# .ok_or(PasetoClaimError::Unexpected(key.to_string()))?; ///# // we only accept prime universes in this token ///# if primes::is_prime(universe) { ///# Ok(()) ///# } else { ///# Err(PasetoClaimError::CustomValidation(key.to_string())) ///# } ///# }) /// ///# .parse(&token, &key)?; /// /// # assert_eq!(json_value["Universe"], 136); /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` #[derive(Clone, Debug)] pub struct CustomClaim((String, T)); impl CustomClaim { //TODO: this needs to be refactored to be configurable for eventual compressed token //implementations pub(self) const RESERVED_CLAIMS: [&'static str; 7] = ["iss", "sub", "aud", "exp", "nbf", "iat", "jti"]; fn check_if_reserved_claim_key(key: &str) -> Result<(), PasetoClaimError> { match key { key if Self::RESERVED_CLAIMS.contains(&key) => Err(PasetoClaimError::Reserved(key.into())), _ => Ok(()), } } } #[cfg(feature = "serde")] impl PasetoClaim for CustomClaim { fn get_key(&self) -> &str { &self.0 .0 } } impl TryFrom<&str> for CustomClaim<&str> { type Error = PasetoClaimError; fn try_from(key: &str) -> Result { Self::check_if_reserved_claim_key(key)?; Ok(Self((String::from(key), ""))) } } impl TryFrom<(String, T)> for CustomClaim { type Error = PasetoClaimError; fn try_from(val: (String, T)) -> Result { Self::check_if_reserved_claim_key(val.0.as_str())?; Ok(Self((val.0, val.1))) } } impl TryFrom<(&str, T)> for CustomClaim { type Error = PasetoClaimError; fn try_from(val: (&str, T)) -> Result { Self::check_if_reserved_claim_key(val.0)?; Ok(Self((String::from(val.0), val.1))) } } //we want to receive a reference as a tuple impl AsRef<(String, T)> for CustomClaim { fn as_ref(&self) -> &(String, T) { &self.0 } } #[cfg(feature = "serde")] impl serde::Serialize for CustomClaim { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let mut map = serializer.serialize_map(Some(2))?; map.serialize_key(&self.0 .0)?; map.serialize_value(&self.0 .1)?; map.end() } } rusty_paseto-0.7.1/src/generic/claims/error.rs000064400000000000000000000032131046102023000175430ustar 00000000000000use thiserror::Error; /// Errors from validating claims in a parsed token #[derive(Debug, Error)] pub enum PasetoClaimError { /// Occurs during an attempt to parse an expired token #[error("This token is expired")] Expired, /// Occurs during an attempt to parse a token before its Not Before claim time #[error("The token cannot be used before {0}")] UseBeforeAvailable(String), /// Occurs if a date time is passed that is not a valid RFC3339 date - "2019-01-01T00:00:00+00:00" #[error("The value {0} is a malformed RFC3339 date")] RFC3339Date(String), /// Occurs if a claim was expected but wasn't found in the payload #[error("The expected claim '{0}' was not found in the payload")] Missing(String), /// Occurs during claim validation if a claim value was unable to be converted to its expected type #[error("Could not convert claim '{0}' to the expected data type")] Unexpected(String), /// Occurs when a custom claim fails validation #[error("The claim '{0}' failed custom validation")] CustomValidation(String), /// Occurs when a claim fails validation #[error("The claim '{0}' failed validation. Expected '{1}' but received '{2}'")] Invalid(String, String, String), /// Occurs when a user attempts to create a custom claim using a reserved claim key #[error("The key {0} is a reserved for use within PASETO. To set a reserved claim, use the strong type: e.g - ExpirationClaimClaim")] Reserved(String), /// Occurs when a user attempts to use a top level claim more than once in the payload #[error("The claim '{0}' appears more than once in the top level payload json")] DuplicateTopLevelPayloadClaim(String), } rusty_paseto-0.7.1/src/generic/claims/expiration_claim.rs000064400000000000000000000031571046102023000217500ustar 00000000000000use super::{PasetoClaim, PasetoClaimError}; #[cfg(feature = "serde")] use serde::ser::SerializeMap; ///The reserved ['exp'](https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/04-Claims.md) PASETO claim #[derive(Clone)] pub struct ExpirationClaim((String, String)); impl PasetoClaim for ExpirationClaim { fn get_key(&self) -> &str { self.0 .0.as_str() } } impl Default for ExpirationClaim { fn default() -> Self { Self(("exp".to_string(), "2019-01-01T00:00:00+00:00".to_string())) } } impl TryFrom for ExpirationClaim { type Error = PasetoClaimError; fn try_from(value: String) -> Result { match iso8601::datetime(&value) { Ok(_) => Ok(Self(("exp".to_string(), value))), Err(_) => Err(PasetoClaimError::RFC3339Date(value.to_string())), } } } impl TryFrom<&str> for ExpirationClaim { type Error = PasetoClaimError; fn try_from(value: &str) -> Result { match iso8601::datetime(value) { Ok(_) => Ok(Self(("exp".to_string(), value.to_string()))), Err(_) => Err(PasetoClaimError::RFC3339Date(value.to_string())), } } } //want to receive a reference as a tuple impl AsRef<(String, String)> for ExpirationClaim { fn as_ref(&self) -> &(String, String) { &self.0 } } #[cfg(feature = "serde")] impl serde::Serialize for ExpirationClaim { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let mut map = serializer.serialize_map(Some(2))?; map.serialize_key(&self.0 .0)?; map.serialize_value(&self.0 .1)?; map.end() } } rusty_paseto-0.7.1/src/generic/claims/issued_at_claim.rs000064400000000000000000000031311046102023000215360ustar 00000000000000use super::{PasetoClaim, PasetoClaimError}; #[cfg(feature = "serde")] use serde::ser::SerializeMap; ///The reserved ['iat'](https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/04-Claims.md) PASETO claim #[derive(Clone)] pub struct IssuedAtClaim((String, String)); impl PasetoClaim for IssuedAtClaim { fn get_key(&self) -> &str { &self.0 .0 } } impl Default for IssuedAtClaim { fn default() -> Self { Self(("iat".to_string(), "2019-01-01T00:00:00+00:00".to_string())) } } impl TryFrom<&str> for IssuedAtClaim { type Error = PasetoClaimError; fn try_from(value: &str) -> Result { match iso8601::datetime(value) { Ok(_) => Ok(Self(("iat".to_string(), value.to_string()))), Err(_) => Err(PasetoClaimError::RFC3339Date(value.to_string())), } } } //want to receive a reference as a tuple impl AsRef<(String, String)> for IssuedAtClaim { fn as_ref(&self) -> &(String, String) { &self.0 } } impl TryFrom for IssuedAtClaim { type Error = PasetoClaimError; fn try_from(value: String) -> Result { match iso8601::datetime(&value) { Ok(_) => Ok(Self(("iat".to_string(), value))), Err(_) => Err(PasetoClaimError::RFC3339Date(value.to_string())), } } } #[cfg(feature = "serde")] impl serde::Serialize for IssuedAtClaim { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let mut map = serializer.serialize_map(Some(2))?; map.serialize_key(&self.0 .0)?; map.serialize_value(&self.0 .1)?; map.end() } } rusty_paseto-0.7.1/src/generic/claims/issuer_claim.rs000064400000000000000000000020571046102023000210760ustar 00000000000000use super::PasetoClaim; #[cfg(feature = "serde")] use serde::ser::SerializeMap; ///The reserved ['iss'](https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/04-Claims.md) PASETO claim #[derive(Clone)] pub struct IssuerClaim<'a>((&'a str, &'a str)); impl<'a> PasetoClaim for IssuerClaim<'a> { fn get_key(&self) -> &str { self.0 .0 } } impl<'a> Default for IssuerClaim<'a> { fn default() -> Self { Self(("iss", "")) } } //created using the From trait impl<'a> From<&'a str> for IssuerClaim<'a> { fn from(s: &'a str) -> Self { Self(("iss", s)) } } //want to receive a reference as a tuple impl<'a> AsRef<(&'a str, &'a str)> for IssuerClaim<'a> { fn as_ref(&self) -> &(&'a str, &'a str) { &self.0 } } #[cfg(feature = "serde")] impl<'a> serde::Serialize for IssuerClaim<'a> { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let mut map = serializer.serialize_map(Some(2))?; map.serialize_entry(self.0 .0, self.0 .1)?; map.end() } } rusty_paseto-0.7.1/src/generic/claims/mod.rs000064400000000000000000000077401046102023000172020ustar 00000000000000use serde_json::Value; use std::collections::HashMap; mod audience_claim; mod custom_claim; mod error; mod expiration_claim; mod issued_at_claim; mod issuer_claim; mod not_before_claim; mod subject_claim; mod token_identifier_claim; mod traits; pub use audience_claim::AudienceClaim; pub use custom_claim::CustomClaim; pub use error::PasetoClaimError; pub use expiration_claim::ExpirationClaim; pub use issued_at_claim::IssuedAtClaim; pub use issuer_claim::IssuerClaim; pub use not_before_claim::NotBeforeClaim; pub use subject_claim::SubjectClaim; pub use token_identifier_claim::TokenIdentifierClaim; pub use traits::PasetoClaim; ///A type for creating generic claim validation functions pub type ValidatorFn = dyn Fn(&str, &Value) -> Result<(), PasetoClaimError>; ///A type for tracking claims in a token pub type ValidatorMap = HashMap>; #[cfg(test)] mod unit_tests { //TODO: need more comprehensive tests than these to flesh out the additionl error types use super::*; use anyhow::Result; //use chrono::prelude::*; use std::convert::TryFrom; use time::format_description::well_known::Rfc3339; #[test] fn test_expiration_claim() -> Result<()> { // setup // a good time format let now = time::OffsetDateTime::now_utc().format(&Rfc3339)?; assert!(ExpirationClaim::try_from("hello").is_err()); let claim = ExpirationClaim::try_from(now); assert!(claim.is_ok()); let claim = claim.unwrap(); assert_eq!(claim.get_key(), "exp"); Ok(()) } #[test] fn test_not_before_claim() -> Result<()> { // setup // a good time format let now = time::OffsetDateTime::now_utc().format(&Rfc3339)?; assert!(NotBeforeClaim::try_from("hello").is_err()); let claim = NotBeforeClaim::try_from(now); assert!(claim.is_ok()); let claim = claim.unwrap(); assert_eq!(claim.get_key(), "nbf"); Ok(()) } #[test] fn test_issued_at_claim() -> Result<()> { // setup // a good time format let now = time::OffsetDateTime::now_utc().format(&Rfc3339)?; assert!(IssuedAtClaim::try_from("hello").is_err()); let claim = IssuedAtClaim::try_from(now); assert!(claim.is_ok()); let claim = claim.unwrap(); assert_eq!(claim.get_key(), "iat"); Ok(()) } #[test] fn test_token_identifier_claim() { // setup let borrowed_str = String::from("hello world"); let claim = TokenIdentifierClaim::from(borrowed_str.as_str()); //verify assert_eq!("jti", claim.get_key()); } #[test] fn test_audience_claim() { // setup let borrowed_str = String::from("hello world"); let claim = AudienceClaim::from(borrowed_str.as_str()); //verify assert_eq!("aud", claim.get_key()); } #[test] fn test_subject_claim() { // setup let borrowed_str = String::from("hello world"); let claim = SubjectClaim::from(borrowed_str.as_str()); //verify assert_eq!("sub", claim.get_key()); } #[test] fn test_iss_claim() { // setup let borrowed_str = String::from("hello world"); let claim = IssuerClaim::from(borrowed_str.as_str()); //verify assert_eq!("iss", claim.get_key()); } #[test] fn test_basic_custom_claim() -> Result<()> { let borrowed_str = String::from("universe"); let claim = CustomClaim::try_from((borrowed_str.as_str(), 137))?; // setup //verify assert_eq!(claim.get_key(), "universe"); let (_, v) = claim.as_ref(); assert_eq!(v, &137); Ok(()) } #[test] fn test_restricted_custom_claim() { // setup //verify assert!(CustomClaim::try_from(("iss", 137)).is_err()); assert!(CustomClaim::try_from(("sub", 137)).is_err()); assert!(CustomClaim::try_from(("aud", 137)).is_err()); assert!(CustomClaim::try_from(("exp", 137)).is_err()); assert!(CustomClaim::try_from(("nbf", 137)).is_err()); assert!(CustomClaim::try_from(("iat", 137)).is_err()); assert!(CustomClaim::try_from(("jti", 137)).is_err()); assert!(CustomClaim::try_from(("i'm good tho", true)).is_ok()); } } rusty_paseto-0.7.1/src/generic/claims/not_before_claim.rs000064400000000000000000000031401046102023000217000ustar 00000000000000use super::{PasetoClaim, PasetoClaimError}; #[cfg(feature = "serde")] use serde::ser::SerializeMap; ///The reserved ['nbf'](https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/04-Claims.md) PASETO claim #[derive(Clone)] pub struct NotBeforeClaim((String, String)); impl PasetoClaim for NotBeforeClaim { fn get_key(&self) -> &str { &self.0 .0 } } impl Default for NotBeforeClaim { fn default() -> Self { Self(("nbf".to_string(), "2019-01-01T00:00:00+00:00".to_string())) } } impl TryFrom for NotBeforeClaim { type Error = PasetoClaimError; fn try_from(value: String) -> Result { match iso8601::datetime(&value) { Ok(_) => Ok(Self(("nbf".to_string(), value))), Err(_) => Err(PasetoClaimError::RFC3339Date(value.to_string())), } } } impl TryFrom<&str> for NotBeforeClaim { type Error = PasetoClaimError; fn try_from(value: &str) -> Result { match iso8601::datetime(value) { Ok(_) => Ok(Self(("nbf".to_string(), value.to_string()))), Err(_) => Err(PasetoClaimError::RFC3339Date(value.to_string())), } } } //want to receive a reference as a tuple impl AsRef<(String, String)> for NotBeforeClaim { fn as_ref(&self) -> &(String, String) { &self.0 } } #[cfg(feature = "serde")] impl serde::Serialize for NotBeforeClaim { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let mut map = serializer.serialize_map(Some(2))?; map.serialize_key(&self.0 .0)?; map.serialize_value(&self.0 .1)?; map.end() } } rusty_paseto-0.7.1/src/generic/claims/subject_claim.rs000064400000000000000000000020651046102023000212220ustar 00000000000000use super::PasetoClaim; #[cfg(feature = "serde")] use serde::ser::SerializeMap; ///The reserved ['sub'](https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/04-Claims.md) PASETO claim #[derive(Clone)] pub struct SubjectClaim<'a>((&'a str, &'a str)); impl<'a> PasetoClaim for SubjectClaim<'a> { fn get_key(&self) -> &str { self.0 .0 } } impl<'a> Default for SubjectClaim<'a> { fn default() -> Self { Self(("sub", "")) } } //created using the From trait impl<'a> From<&'a str> for SubjectClaim<'a> { fn from(s: &'a str) -> Self { Self(("sub", s)) } } //want to receive a reference as a tuple impl<'a> AsRef<(&'a str, &'a str)> for SubjectClaim<'a> { fn as_ref(&self) -> &(&'a str, &'a str) { &self.0 } } #[cfg(feature = "serde")] impl<'a> serde::Serialize for SubjectClaim<'a> { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let mut map = serializer.serialize_map(Some(2))?; map.serialize_entry(self.0 .0, self.0 .1)?; map.end() } } rusty_paseto-0.7.1/src/generic/claims/token_identifier_claim.rs000064400000000000000000000022601046102023000231020ustar 00000000000000use super::PasetoClaim; #[cfg(feature = "serde")] use serde::ser::SerializeMap; ///The reserved ['jti'](https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/04-Claims.md) PASETO claim #[derive(Clone)] pub struct TokenIdentifierClaim<'a>((&'a str, &'a str)); impl<'a> PasetoClaim for TokenIdentifierClaim<'a> { fn get_key(&self) -> &str { self.0 .0 } } impl<'a> Default for TokenIdentifierClaim<'a> { fn default() -> Self { Self(("jti", "")) } } //created using the From trait impl<'a> From<&'a str> for TokenIdentifierClaim<'a> { fn from(s: &'a str) -> Self { Self(("jti", s)) } } //want to receive a reference as a tuple impl<'a> AsRef<(&'a str, &'a str)> for TokenIdentifierClaim<'a> { fn as_ref(&self) -> &(&'a str, &'a str) { &self.0 } } #[cfg(feature = "serde")] impl<'a> serde::Serialize for TokenIdentifierClaim<'a> { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { let mut map = serializer.serialize_map(Some(2))?; map.serialize_key(&self.0 .0)?; map.serialize_value(&self.0 .1)?; //map.serialize_entry(self.0 .0, self.0 .1)?; map.end() } } rusty_paseto-0.7.1/src/generic/claims/traits.rs000064400000000000000000000001751046102023000177240ustar 00000000000000/// a simple marker trait to identify claims pub trait PasetoClaim: erased_serde::Serialize { fn get_key(&self) -> &str; } rusty_paseto-0.7.1/src/generic/mod.rs000064400000000000000000000032031046102023000157200ustar 00000000000000//! The generic architectural and feature layer allows you to create your own custom version of the batteries_included layer by following the same pattern I've used in the source code to create your own custom builder and parser. This is probably not what you need as it is for advanced usage. The feature includes a generic builder and parser along with claims for you to extend. //! //! ![paseto_generic_small](https://user-images.githubusercontent.com/24578097/147881907-a765ede6-c8e5-44ff-9845-db53f0634f07.png) //! //! It includes all the PASETO and custom claims but allows you to create different default claims in your custom builder and parser or use a different time crate or make up your own default business rules. As with the batteries_included layer, parsed tokens get returned as a serder_json Value. Again, specify the version and purpose to include in the crypto core: //! //! //! ```toml //! ## Includes only v4 modern sodium cipher crypto core and local (symmetric) //! ## key types with all claims and default business rules. //! //! rusty_paseto = {version = "latest", features = ["generic", "v4_local"] } //! ``` //! ``` //! # #[cfg(feature = "default")] //! # { //! // at the top of your source file //! use rusty_paseto::generic::*; //! # } //! ``` //! # Registered Claims //! Refer to the [PASETO specification](https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/04-Claims.md) to review reserved claims for use within PASETO. mod builders; mod claims; mod parsers; pub use crate::generic::claims::*; pub use crate::core::*; pub use crate::generic::builders::*; pub use crate::generic::parsers::*; rusty_paseto-0.7.1/src/generic/parsers/error.rs000064400000000000000000000013331046102023000177530ustar 00000000000000use crate::generic::claims::PasetoClaimError; use thiserror::Error; /// Errors raised by the generic parser when validating claims or parsing a PASETO token. #[derive(Debug, Error)] pub enum GenericParserError { /// An error from the existence or non-existence or validation of a claim #[error(transparent)] ClaimError { #[from] source: PasetoClaimError, }, /// An error decrypting or validating a token #[error("A paseto cipher error occurred")] CipherError { #[from] source: crate::core::PasetoError, }, /// A JSON deserialization error for the token payload #[error("The payload was unable to be serialized into json")] PayloadJsonError { #[from] source: serde_json::Error, }, } rusty_paseto-0.7.1/src/generic/parsers/generic_parser.rs000064400000000000000000001334601046102023000216210ustar 00000000000000use super::GenericParserError; use crate::generic::*; use core::marker::PhantomData; use serde_json::Value; use std::collections::HashMap; ///The GenericParser is created at compile time by specifying a PASETO version and purpose and ///providing a key of the same version and purpose. This structure allows parsing an untrusted token string ///and either decrypting (Local) or verifying the signtature of (Public) PASETO tokens and then ///parsing and validating [PASETO claims](https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/04-Claims.md). /// ///The intent of the GenericParser is to allow the user to wrap basic PASETO standard ///functionality with their own custom business rules or ergonomic API. For most users, the batteries-included ///[paseto parser](crate::prelude::PasetoParser) will be all they need. More advanced cases can wrap this ///or the [core](Paseto) struct to accomplish custom functionality. /// /// ///# Usage /// ///``` ///# #[cfg(all(feature = "generic", feature="v2_local"))] ///# { /// use rusty_paseto::generic::*; /// let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); /// let footer = Footer::from("some footer"); /// //create a builder, add some claims and then build the token with the key /// let token = GenericBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .try_encrypt(&key)?; /// //now let's decrypt the token and verify the values /// let json = GenericParser::::default() /// .set_footer(footer) /// .parse(&token, &key)?; /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` pub struct GenericParser<'a, 'b, Version, Purpose> { version: PhantomData, purpose: PhantomData, claims: HashMap>, claim_validators: ValidatorMap, footer: Footer<'a>, implicit_assertion: ImplicitAssertion<'a>, } impl<'a, 'b, Version, Purpose> GenericParser<'a, 'b, Version, Purpose> { ///Creates a new parser for building. Called by [Default] pub fn new() -> Self { GenericParser:: { version: PhantomData::, purpose: PhantomData::, claims: HashMap::new(), claim_validators: HashMap::new(), footer: Default::default(), implicit_assertion: Default::default(), } } ///Allows adding multiple [claims](PasetoClaim) at once to be checked during parsing by passing a Hashmap of claim keys and values pub fn extend_check_claims(&mut self, value: HashMap>) -> &mut Self { self.claims.extend(value); self } ///Allows adding multiple [validators](PasetoClaim) at once to be checked during parsing by passing a [ValidatorMap] of claim keys and values pub fn extend_validation_claims(&mut self, value: ValidatorMap) -> &mut Self { self.claim_validators.extend(value); self } #[cfg(feature = "serde")] fn set_validation_claim( &mut self, value: T, validation_closure: Option<&'static ValidatorFn>, ) -> &mut Self { let key = value.get_key().to_string(); //first store the claim self.claims.insert(key.clone(), Box::new(value)); //if there's a closure, then store that if let Some(closure) = validation_closure { self.claim_validators.insert(key, Box::new(closure)); } self } ///Allows user to pass a [PasetoClaim] along with a [custom function](ValidatorFn) to enable ///custom validation algorithms on the claim. The claim can be a reserved claim or a custom ///claim. #[cfg(feature = "serde")] pub fn validate_claim( &mut self, value: T, validation_closure: &'static ValidatorFn, ) -> &mut Self { self.set_validation_claim(value, Some(validation_closure)) } ///Verifies a passed [PasetoClaim] exists #[cfg(feature = "serde")] pub fn check_claim(&mut self, value: T) -> &mut Self { self.set_validation_claim(value, None) } /// Gets an optional [Footer] set during parser building pub fn get_footer(&self) -> Footer { self.footer } ///Sets an optional [Footer] to use during parsing pub fn set_footer(&mut self, footer: Footer<'a>) -> &mut Self { self.footer = footer; self } } impl<'a, 'b, Version: ImplicitAssertionCapable, Purpose> GenericParser<'a, 'b, Version, Purpose> { ///Sets an optional [ImplicitAssertion] to use during parsing ([V3], [V4] tokens only) pub fn set_implicit_assertion(&mut self, implicit_assertion: ImplicitAssertion<'a>) -> &mut Self { self.implicit_assertion = implicit_assertion; self } ///Gets an optional [ImplicitAssertion] to use during parsing ([V3], [V4] tokens only) pub fn get_implicit_assertion(&self) -> ImplicitAssertion { self.implicit_assertion } } impl<'a, 'b, Version, Purpose> GenericParser<'a, 'b, Version, Purpose> { fn verify_claims(&self, token: &str) -> Result { let json: Value = serde_json::from_str(token)?; // here we want to traverse all of the claims to validate and verify their values for (key, box_val) in &self.claims { //ensure the claim exists //get the raw value of the claim let raw = serde_json::to_value(box_val)?; //now let's run any custom validation if there is any if self.claim_validators.contains_key(key) { let box_validator = &self.claim_validators[key]; let validator = box_validator.as_ref(); validator(key, &json[&key])?; } else { //otherwise, simply verify the claim exists and matches the value passed in if json[&key] == Value::Null { return Err(PasetoClaimError::Missing(key.to_string()).into()); } if raw[&key] != json[&key] { return Err( PasetoClaimError::Invalid( key.to_string(), json[&key] .as_str() .ok_or_else(|| PasetoClaimError::Unexpected(key.to_string()))? .into(), raw[&key] .as_str() .ok_or_else(|| PasetoClaimError::Unexpected(key.to_string()))? .into(), ) .into(), ); } } } Ok(json) } } #[cfg(feature = "v1_local")] impl<'a, 'b> GenericParser<'a, 'b, V1, Local> { /// Given a [PasetoSymmetricKey], attempts to decrypt a (V1, Local) encrypted PASETO token string and then validate /// claims provided to the GenericParser. /// /// Returns a serde_json [Value] with the decrypted [claims](PasetoClaim) and payload on success. /// /// # Errors /// /// Returns [`GenericParserError`] for any errors when decrypting the encrypted payload or when validating claims. /// /// # Example /// /// ///``` ///# #[cfg(all(feature = "generic", feature="v1_local"))] ///# { /// use rusty_paseto::generic::*; /// let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); /// let footer = Footer::from("some footer"); /// //create a builder, add some claims and then build the token with the key /// let token = GenericBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .try_encrypt(&key)?; /// //now let's decrypt the token and verify the values /// let json = GenericParser::::default() /// .set_footer(footer) /// .parse(&token, &key)?; /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn parse( &self, potential_token: &'a str, key: &'a PasetoSymmetricKey, ) -> Result { //decrypt, then validate let token = Paseto::::try_decrypt(potential_token, key, self.get_footer())?; self.verify_claims(&token) } } #[cfg(feature = "v2_local")] impl<'a, 'b> GenericParser<'a, 'b, V2, Local> { /// Given a [PasetoSymmetricKey], attempts to decrypt a (V2, Local) encrypted PASETO token string and then validate /// claims provided to the GenericParser. /// /// Returns a serde_json [Value] with the decrypted [claims](PasetoClaim) and payload on success. /// /// # Errors /// /// Returns [`GenericParserError`] for any errors when decrypting the encrypted payload or when validating claims. /// /// # Example /// /// ///``` ///# #[cfg(all(feature = "generic", feature="v2_local"))] ///# { /// use rusty_paseto::generic::*; /// let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); /// let footer = Footer::from("some footer"); /// //create a builder, add some claims and then build the token with the key /// let token = GenericBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .try_encrypt(&key)?; /// //now let's decrypt the token and verify the values /// let json = GenericParser::::default() /// .set_footer(footer) /// .parse(&token, &key)?; /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn parse( &mut self, potential_token: &'a str, key: &'a PasetoSymmetricKey, ) -> Result { //first we need to verify the token let token = Paseto::::try_decrypt(potential_token, key, self.get_footer())?; self.verify_claims(&token) } } #[cfg(feature = "v3_local")] impl<'a, 'b> GenericParser<'a, 'b, V3, Local> { /// Given a [PasetoSymmetricKey], attempts to decrypt a (V3, Local) encrypted PASETO token string and then validate /// claims provided to the GenericParser. /// /// Returns a serde_json [Value] with the decrypted [claims](PasetoClaim) and payload on success. /// /// # Errors /// /// Returns [`GenericParserError`] for any errors when decrypting the encrypted payload or when validating claims. /// /// # Example /// /// ///``` ///# #[cfg(all(feature = "generic", feature="v3_local"))] ///# { /// use rusty_paseto::generic::*; /// let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); /// let footer = Footer::from("some footer"); /// let implicit_assertion = ImplicitAssertion::from("some assertion"); /// //create a builder, add some claims and then build the token with the key /// let token = GenericBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .set_implicit_assertion(implicit_assertion) /// .try_encrypt(&key)?; /// //now let's decrypt the token and verify the values /// let json = GenericParser::::default() /// .set_footer(footer) /// .set_implicit_assertion(implicit_assertion) /// .parse(&token, &key)?; /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn parse( &mut self, potential_token: &'a str, key: &'a PasetoSymmetricKey, ) -> Result { //first we need to verify the token let token = Paseto::::try_decrypt(potential_token, key, self.get_footer(), self.get_implicit_assertion())?; self.verify_claims(&token) } } #[cfg(feature = "v4_local")] impl<'a, 'b> GenericParser<'a, 'b, V4, Local> { /// Given a [PasetoSymmetricKey], attempts to decrypt a (V4, Local) encrypted PASETO token string and then validate /// claims provided to the GenericParser. /// /// Returns a serde_json [Value] with the decrypted [claims](PasetoClaim) and payload on success. /// /// # Errors /// /// Returns [`GenericParserError`] for any errors when decrypting the encrypted payload or when validating claims. /// /// # Example /// /// ///``` ///# #[cfg(all(feature = "generic", feature="v4_local"))] ///# { /// use rusty_paseto::generic::*; /// let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); /// let footer = Footer::from("some footer"); /// let implicit_assertion = ImplicitAssertion::from("some assertion"); /// //create a builder, add some claims and then build the token with the key /// let token = GenericBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .set_implicit_assertion(implicit_assertion) /// .try_encrypt(&key)?; /// //now let's decrypt the token and verify the values /// let json = GenericParser::::default() /// .set_footer(footer) /// .set_implicit_assertion(implicit_assertion) /// .parse(&token, &key)?; /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn parse( &mut self, potential_token: &'a str, key: &'a PasetoSymmetricKey, ) -> Result { //first we need to verify the token let token = Paseto::::try_decrypt(potential_token, key, self.get_footer(), self.get_implicit_assertion())?; self.verify_claims(&token) } } #[cfg(feature = "v1_public")] impl<'a, 'b> GenericParser<'a, 'b, V1, Public> { /// Given a [PasetoAsymmetricPublicKey], attempts to verify a (V1, Public) signed PASETO token string and then validate /// claims provided to the GenericParser. /// /// Returns a serde_json [Value] with the verified [claims](PasetoClaim) and payload on success. /// /// # Errors /// /// Returns [`GenericParserError`] for any errors when decrypting the encrypted payload or when validating claims. /// /// # Example /// ///``` ///# #[cfg(all(feature = "generic", feature="v1_public"))] ///# { /// # use rusty_paseto::generic::*; /// //obtain a private key (pk) /// # let private_key = include_bytes!("../../../tests/v1_public_test_vectors_private_key.pk8"); /// # let pk: &[u8] = private_key; /// let private_key = PasetoAsymmetricPrivateKey::::from(pk); /// let footer = Footer::from("some footer"); /// //sign a public V1 token /// let token = GenericBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .try_sign(&private_key)?; /// //obtain a public key (pubk) /// # let public_key = include_bytes!("../../../tests/v1_public_test_vectors_public_key.der"); /// # let pubk: &[u8] = public_key; /// let public_key = PasetoAsymmetricPublicKey::::from(pubk); /// //now let's try to verify it /// let json = GenericParser::::default() /// .set_footer(footer) /// .check_claim(AudienceClaim::from("customers")) /// .check_claim(SubjectClaim::from("loyal subjects")) /// .check_claim(IssuerClaim::from("me")) /// .check_claim(TokenIdentifierClaim::from("me")) /// .check_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .check_claim(CustomClaim::try_from(("seats", 4))?) /// .check_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .parse(&token, &public_key)?; /// // we can access all the values from the serde Value object returned by the parser /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) ///``` pub fn parse( &mut self, potential_token: &'a str, key: &'a PasetoAsymmetricPublicKey, ) -> Result { //first we need to verify the token let token = Paseto::::try_verify(potential_token, key, self.get_footer())?; self.verify_claims(&token) } } #[cfg(feature = "v2_public")] impl<'a, 'b> GenericParser<'a, 'b, V2, Public> { /// Given a [PasetoAsymmetricPublicKey], attempts to verify a (V2, Public) signed PASETO token string and then validate /// claims provided to the GenericParser. /// /// Returns a serde_json [Value] with the verified [claims](PasetoClaim) and payload on success. /// /// # Errors /// /// Returns [`GenericParserError`] for any errors when decrypting the encrypted payload or when validating claims. /// /// # Example /// ///``` ///# #[cfg(all(feature = "generic", feature="v1_public"))] ///# { /// # use rusty_paseto::generic::*; /// //obtain a key /// let private_key = Key::<64>::try_from("b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; /// let private_key = PasetoAsymmetricPrivateKey::::from(&private_key); /// let public_key = Key::<32>::try_from("1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; /// let public_key = PasetoAsymmetricPublicKey::::from(&public_key); /// let footer = Footer::from("some footer"); /// //sign a public V2 token /// let token = GenericBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .try_sign(&private_key)?; /// //now let's try to verify it /// let json = GenericParser::::default() /// .set_footer(footer) /// .check_claim(AudienceClaim::from("customers")) /// .check_claim(SubjectClaim::from("loyal subjects")) /// .check_claim(IssuerClaim::from("me")) /// .check_claim(TokenIdentifierClaim::from("me")) /// .check_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .check_claim(CustomClaim::try_from(("seats", 4))?) /// .check_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .parse(&token, &public_key)?; /// // we can access all the values from the serde Value object returned by the parser /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) ///``` pub fn parse( &mut self, potential_token: &'a str, key: &'a PasetoAsymmetricPublicKey, ) -> Result { //first we need to verify the token let token = Paseto::::try_verify(potential_token, key, self.get_footer())?; self.verify_claims(&token) } } #[cfg(feature = "v3_public")] impl<'a, 'b> GenericParser<'a, 'b, V3, Public> { /// Given a [PasetoAsymmetricPublicKey], attempts to verify a (V3, Public) signed PASETO token string and then validate /// claims provided to the GenericParser. /// /// Returns a serde_json [Value] with the verified [claims](PasetoClaim) and payload on success. /// /// # Errors /// /// Returns [`GenericParserError`] for any errors when decrypting the encrypted payload or when validating claims. /// /// # Example /// ///``` ///# #[cfg(all(feature = "generic", feature="v3_public"))] ///# { /// # use rusty_paseto::generic::*; /// //obtain a key /// let private_key = Key::<48>::try_from( /// "20347609607477aca8fbfbc5e6218455f3199669792ef8b466faa87bdc67798144c848dd03661eed5ac62461340cea96", /// )?; /// let private_key = PasetoAsymmetricPrivateKey::::from(&private_key); /// let public_key = Key::<49>::try_from( /// "02fbcb7c69ee1c60579be7a334134878d9c5c5bf35d552dab63c0140397ed14cef637d7720925c44699ea30e72874c72fb", /// )?; /// let public_key = PasetoAsymmetricPublicKey::::try_from(&public_key)?; /// let footer = Footer::from("some footer"); /// let implicit_assertion = ImplicitAssertion::from("some assertion"); /// //sign a public V3 token /// let token = GenericBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .set_implicit_assertion(implicit_assertion) /// .try_sign(&private_key)?; /// //now let's try to verify it /// let json = GenericParser::::default() /// .set_footer(footer) /// .check_claim(AudienceClaim::from("customers")) /// .set_implicit_assertion(implicit_assertion) /// .check_claim(SubjectClaim::from("loyal subjects")) /// .check_claim(IssuerClaim::from("me")) /// .check_claim(TokenIdentifierClaim::from("me")) /// .check_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .check_claim(CustomClaim::try_from(("seats", 4))?) /// .check_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .parse(&token, &public_key)?; /// // we can access all the values from the serde Value object returned by the parser /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) ///``` pub fn parse( &mut self, potential_token: &'a str, key: &'a PasetoAsymmetricPublicKey, ) -> Result { //first we need to verify the token let token = Paseto::::try_verify(potential_token, key, self.get_footer(), self.get_implicit_assertion())?; self.verify_claims(&token) } } #[cfg(feature = "v4_public")] impl<'a, 'b> GenericParser<'a, 'b, V4, Public> { /// Given a [PasetoAsymmetricPublicKey], attempts to verify a (V4, Public) signed PASETO token string and then validate /// claims provided to the GenericParser. /// /// Returns a serde_json [Value] with the verified [claims](PasetoClaim) and payload on success. /// /// # Errors /// /// Returns [`GenericParserError`] for any errors when decrypting the encrypted payload or when validating claims. /// /// # Example /// ///``` ///# #[cfg(all(feature = "generic", feature="v4_public"))] ///# { /// # use rusty_paseto::generic::*; /// //create a key /// let private_key = Key::<64>::try_from("b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; /// let pk: &[u8] = private_key.as_slice(); /// let private_key = PasetoAsymmetricPrivateKey::::from(pk); /// let public_key = Key::<32>::try_from("1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; /// let public_key = PasetoAsymmetricPublicKey::::from(&public_key); /// let footer = Footer::from("some footer"); /// let implicit_assertion = ImplicitAssertion::from("some assertion"); /// //sign a public V4 token /// let token = GenericBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .set_implicit_assertion(implicit_assertion) /// .try_sign(&private_key)?; /// //now let's try to verify it /// let json = GenericParser::::default() /// .set_footer(footer) /// .set_implicit_assertion(implicit_assertion) /// .check_claim(AudienceClaim::from("customers")) /// .check_claim(SubjectClaim::from("loyal subjects")) /// .check_claim(IssuerClaim::from("me")) /// .check_claim(TokenIdentifierClaim::from("me")) /// .check_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .check_claim(CustomClaim::try_from(("seats", 4))?) /// .check_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .parse(&token, &public_key)?; /// // we can access all the values from the serde Value object returned by the parser /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) ///``` pub fn parse( &mut self, potential_token: &'a str, key: &'a PasetoAsymmetricPublicKey, ) -> Result { //first we need to verify the token let token = Paseto::::try_verify(potential_token, key, self.get_footer(), self.get_implicit_assertion())?; self.verify_claims(&token) } } impl<'a, 'b, Version, Purpose> Default for GenericParser<'a, 'b, Version, Purpose> { fn default() -> Self { Self::new() } } #[cfg(all(test, feature = "v2"))] mod parsers { use std::convert::TryFrom; use crate::generic::claims::*; use crate::generic::*; use anyhow::Result; #[cfg(all(test, feature="v2_public"))] #[test] fn full_parser_test_v2_public() -> Result<()> { //create a key let private_key = Key::<64>::try_from("b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; let private_key = PasetoAsymmetricPrivateKey::::from(&private_key); let public_key = Key::<32>::try_from("1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; let public_key = PasetoAsymmetricPublicKey::::from(&public_key); // let key = Key::::from(*b"wubbalubbadubdubwubbalubbadubdub"); let footer = Footer::from("some footer"); //create a builder, add some claims and then build the token with the key let token = GenericBuilder::::default() .set_claim(AudienceClaim::from("customers")) .set_claim(SubjectClaim::from("loyal subjects")) .set_claim(IssuerClaim::from("me")) .set_claim(TokenIdentifierClaim::from("me")) .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) .set_claim(CustomClaim::try_from(("seats", 4))?) .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) .set_footer(footer) .try_sign(&private_key)?; //now let's decrypt the token and verify the values let json = GenericParser::::default() .check_claim(AudienceClaim::from("customers")) .check_claim(SubjectClaim::from("loyal subjects")) .check_claim(IssuerClaim::from("me")) .check_claim(TokenIdentifierClaim::from("me")) .check_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) .check_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) .check_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) .check_claim(CustomClaim::try_from(("data", "this is a secret message"))?) .check_claim(CustomClaim::try_from(("seats", 4))?) .check_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) .set_footer(footer) .parse(&token, &public_key)?; // we can access all the values from the serde Value object returned by the parser assert_eq!(json["aud"], "customers"); assert_eq!(json["jti"], "me"); assert_eq!(json["iss"], "me"); assert_eq!(json["data"], "this is a secret message"); assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["sub"], "loyal subjects"); assert_eq!(json["pi to 6 digits"], 3.141526); assert_eq!(json["seats"], 4); Ok(()) } #[cfg(all(test, feature="v2_local"))] #[test] fn full_parser_test() -> Result<()> { //create a key let key = PasetoSymmetricKey::::from(Key::from(*b"wubbalubbadubdubwubbalubbadubdub")); let footer = Footer::from("some footer"); //create a builder, add some claims and then build the token with the key let token = GenericBuilder::::default() .set_claim(AudienceClaim::from("customers")) .set_claim(SubjectClaim::from("loyal subjects")) .set_claim(IssuerClaim::from("me")) .set_claim(TokenIdentifierClaim::from("me")) .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) .set_claim(CustomClaim::try_from(("seats", 4))?) .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) .set_footer(footer) .try_encrypt(&key)?; //now let's decrypt the token and verify the values let json = GenericParser::::default() .check_claim(AudienceClaim::from("customers")) .check_claim(SubjectClaim::from("loyal subjects")) .check_claim(IssuerClaim::from("me")) .check_claim(TokenIdentifierClaim::from("me")) .check_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) .check_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) .check_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) .check_claim(CustomClaim::try_from(("data", "this is a secret message"))?) .check_claim(CustomClaim::try_from(("seats", 4))?) .check_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) .set_footer(footer) .parse(&token, &key)?; // we can access all the values from the serde Value object returned by the parser assert_eq!(json["aud"], "customers"); assert_eq!(json["jti"], "me"); assert_eq!(json["iss"], "me"); assert_eq!(json["data"], "this is a secret message"); assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["sub"], "loyal subjects"); assert_eq!(json["pi to 6 digits"], 3.141526); assert_eq!(json["seats"], 4); Ok(()) } #[cfg(feature="v2_local")] #[test] fn basic_claim_validation_test() -> Result<()> { //create a key let key = PasetoSymmetricKey::::from(Key::from(*b"wubbalubbadubdubwubbalubbadubdub")); //create a builder, add some claims and then build the token with the key let token = GenericBuilder::::default() .set_claim(AudienceClaim::from("customers")) .try_encrypt(&key)?; //now let's decrypt the token and verify the values let actual_error_kind = format!( "{}", GenericParser::::default() .check_claim(AudienceClaim::from("not the same customers")) .parse(&token, &key) .unwrap_err() ); let expected_error_kind = "The claim 'aud' failed validation. Expected 'customers' but received 'not the same customers'"; assert_eq!(expected_error_kind, actual_error_kind); Ok(()) } #[cfg(feature="v2_local")] #[test] fn claim_custom_validator_test() -> Result<()> { //create a key let key = PasetoSymmetricKey::::from(Key::from(*b"wubbalubbadubdubwubbalubbadubdub")); //create a builder, add some claims and then build the token with the key let token = GenericBuilder::::default() .set_claim(AudienceClaim::from("customers")) .try_encrypt(&key)?; //now let's decrypt the token and verify the values with a custom validation closure let json = GenericParser::::default() .validate_claim( //no need to provide a value to check against for the claim when we are using //a custom closure since the value will be passed to the closure for evaluation by your //validation function AudienceClaim::default(), &|key, value| { //we receive the value of the claim so we can do whatever we like with it //get the value of the claim let val = value.as_str().ok_or(PasetoClaimError::Unexpected(key.to_string()))?; match val { "customers" => Ok(()), _ => Err(PasetoClaimError::Invalid(key.to_string(), String::from("customers"), val.to_string()).into()), } }, ) .parse(&token, &key)?; assert_eq!(json["aud"], "customers"); Ok(()) } #[cfg(feature="v2_local")] #[test] fn claim_custom_validator_failure_test() -> Result<()> { //create a key let key = PasetoSymmetricKey::::from(Key::from(*b"wubbalubbadubdubwubbalubbadubdub")); //create a builder, add some claims and then build the token with the key let token = GenericBuilder::::default() .set_claim(AudienceClaim::from("customers")) .try_encrypt(&key)?; //now let's decrypt the token and verify the values with a custom validation closure let actual_error_kind = format!( "{}", GenericParser::::default() .validate_claim( //no need to provide a value to check against for the claim when we are using //a custom closure since the value will be passed to the closure for evaluation by your //validation function AudienceClaim::default(), &|key, value| { //we receive the value of the claim so we can do whatever we like with it //get the value of the claim let val = value.as_str().ok_or(PasetoClaimError::Unexpected(key.to_string()))?; //let's fail on purpose Err(PasetoClaimError::Invalid(key.to_string(), "".to_string(), val.to_string()).into()) } ) .parse(&token, &key) .unwrap_err() ); let expected_error_kind = "The claim 'aud' failed validation. Expected '' but received 'customers'"; assert_eq!(expected_error_kind, actual_error_kind); Ok(()) } #[cfg(feature="v2_local")] #[test] fn custom_claim_custom_validator_test() -> Result<()> { //create a key let key = PasetoSymmetricKey::::from(Key::from(*b"wubbalubbadubdubwubbalubbadubdub")); //create a builder, add some claims and then build the token with the key let token = GenericBuilder::::default() .set_claim(CustomClaim::try_from(("seats", 4))?) .try_encrypt(&key)?; //now let's decrypt the token and verify the values with a custom validation closure let actual_error_kind = format!( "{}", GenericParser::::default() .validate_claim( //no need to provide a value to check against for the claim when we are using //a custom closure since the value will be passed to the closure for evaluation by your //validation function CustomClaim::try_from("seats")?, &|key, value| { //we receive the value of the claim so we can do whatever we like with it //get the value of the claim let val = value.as_u64().ok_or(PasetoClaimError::Unexpected(key.to_string()))?; //let's fail on purpose Err(PasetoClaimError::Invalid(key.to_string(), "".to_string(), val.to_string()).into()) } ) .parse(&token, &key) .unwrap_err() ); let expected_error_kind = "The claim 'seats' failed validation. Expected '' but received '4'"; assert_eq!(expected_error_kind, actual_error_kind); Ok(()) } #[cfg(feature="v2_local")] #[test] fn missing_claim_validation_test() -> Result<()> { //create a key let key = PasetoSymmetricKey::::from(Key::from(*b"wubbalubbadubdubwubbalubbadubdub")); //create a builder, add no claims and then build the token with the key let token = GenericBuilder::::default().try_encrypt(&key)?; //now let's decrypt the token and verify the values let actual_error_kind = format!( "{}", GenericParser::::default() .check_claim(AudienceClaim::from("this claim doesn't exist")) .parse(&token, &key) .unwrap_err() ); let expected_error_kind = "The expected claim 'aud' was not found in the payload"; //the claim we're looking for was not in the original token so we receive an error assert_eq!(expected_error_kind, actual_error_kind); Ok(()) } } rusty_paseto-0.7.1/src/generic/parsers/mod.rs000064400000000000000000000001521046102023000173770ustar 00000000000000mod error; mod generic_parser; pub use error::GenericParserError; pub use generic_parser::GenericParser; rusty_paseto-0.7.1/src/lib.rs000064400000000000000000000613541046102023000143060ustar 00000000000000//#![deny(missing_docs)] // #![doc(html_no_source)] // #![deny(rustdoc::missing_crate_level_docs)] // #![warn(missing_docs)] #![forbid(unsafe_code)] // #![warn(rustdoc::missing_doc_code_examples)] #![doc(html_logo_url = "https://github.com/rrrodzilla/rusty_paseto/raw/main/assets/RustyPasetoCoreArchitecture.png")] //! Secure stateless [PASETO: Platform-Agnostic Security Tokens](https://github.com/paseto-standard/paseto-spec) //! //! This crate is a type-driven, ergonomic implementation of the [PASETO](https://github.com/paseto-standard/paseto-spec) protocol for secure stateless tokens. //! //! > "Paseto is everything you love about JOSE (JWT, JWE, JWS) without any of the //! [many design deficits that plague the JOSE standards](https://paragonie.com/blog/2017/03/jwt-json-web-tokens-is-bad-standard-that-everyone-should-avoid)." //! > -- [PASETO Specification](https://github.com/paseto-standard/paseto-spec) //! //! //! # Usage //!rusty_paseto is meant to be flexible and configurable for your specific use case. Whether you want to get started quickly with sensible defaults, create your own version of rusty_paseto in order to customize your own defaults and functionality or just want to use the core PASETO crypto features, the crate is heavily feature gated to allow for your needs. //! ## Architecture //! The rusty_paseto crate architecture is composed of three layers (batteries_included, generic and core) which can be further refined by the PASETO version(s) and purpose(s) required for your needs. All layers use a common crypto core which includes various cipher crates depending on the version and purpose you choose. The crate is heavily featured gated to allow you to use only the versions and purposes you need for your app which minimizes download compile times for using rusty_paseto. A description of each architectural layer, their uses and limitations and how to minimize your required dependencies based on your required PASETO version and purpose follows: //! //! //! //! //! batteries_included --> generic --> core //! //! ### default //! The default feature is the quickest way to get started using rusty_paseto. //! //! //! //! //! The default feature includes the outermost architectural layer called batteries_included (described below) as well as the two latest PASETO versions (V3 - NIST MODERN, V4 - SODIUM MODERN) and the Public (Asymmetric) and Local (Symmetric) purposed key types for each of these versions. That should be four specific version and purpose combinations however at the time of this writing I have yet to implement the V3 - Public combination, so there are 3 in the default feature. Additionally, this feature includes JWT style claims and business rules for your PASETO token (default, but customizable expiration, issued at, not-before times, etc as described in the usage documentation and examples further below). //! //! ```toml //! ## Includes V3 (local) and V4 (local, public) versions, purposes and ciphers. //! //! rusty_paseto = "latest" //! ``` //! ``` //! # #[cfg(feature = "default")] //! # { //! // at the top of your source file //! use rusty_paseto::prelude::*; //! # } //! ``` //! //! ### batteries_included //! //! The outermost architectural layer is called batteries_included. This is what most people will need. This feature includes JWT style claims and business rules for your PASETO token (default, but customizable expiration, issued at, not-before times, etc as described in the usage documentation and examples below). //! //! //! //! You must specify a version and purpose with this feature in order to reduce the size of your dependencies like in the following Cargo.toml entry which only includes the V4 - Local types with batteries_included functionality: //! //! ```toml //! ## Includes only v4 modern sodium cipher crypto core and local (symmetric) //! ## key types with all claims and default business rules. //! //! rusty_paseto = {version = "latest", features = ["batteries_included", "v4_local"] } //! ``` //! //! //! #### Feature gates //! Valid version/purpose feature combinations are as follows: //! - "v1_local" (NIST Original Symmetric Encryption) //! - "v2_local" (Sodium Original Symmetric Encryption) //! - "v3_local" (NIST Modern Symmetric Encryption) //! - "v4_local" (Sodium Modern Symmetric Encryption) //! - "v1_public" (NIST Original Asymmetric Authentication) //! - "v2_public" (Sodium Original Asymmetric Authentication) //! - "v3_public" (NIST Modern Asymmetric Authentication) //! - "v4_public" (Sodium Modern Asymmetric Authentication) //! //! ``` //! # #[cfg(feature = "default")] //! # { //! // at the top of your source file //! use rusty_paseto::prelude::*; //! # } //! ``` //! ### generic //! //! The generic architectural and feature layer allows you to create your own custom version of the batteries_included layer by following the same pattern I've used in the source code to create your own custom builder and parser. This is probably not what you need as it is for advanced usage. The feature includes a generic builder and parser along with claims for you to extend. //! //! //! //! It includes all the PASETO and custom claims but allows you to create different default claims in your custom builder and parser or use a different time crate or make up your own default business rules. As with the batteries_included layer, parsed tokens get returned as a serder_json Value. Again, specify the version and purpose to include in the crypto core: //! //! //! ```toml //! ## Includes only v4 modern sodium cipher crypto core and local (symmetric) //! ## key types with all claims and default business rules. //! //! rusty_paseto = {version = "latest", features = ["generic", "v4_local"] } //! ``` //! ``` //! # #[cfg(feature = "default")] //! # { //! // at the top of your source file //! use rusty_paseto::generic::*; //! # } //! ``` //! ### core //! //! The core architectural layer is the most basic PASETO implementation as it accepts a Payload, optional Footer and (if v3 or v4) an optional Implicit Assertion along with the appropriate key to encrypt/sign and decrypt/verify basic strings. //! //! //! //! There are no default claims or included claim structures, business rules or anything other than basic PASETO crypto functions. Serde crates are not included in this feature so it is extremely lightweight. You can use this when you don't need JWT-esque functionality but still want to leverage the safe cipher combinations and algorithm lucidity afforded by the PASETO specification. //! //! ```toml //! ## Includes only v4 modern sodium cipher crypto core and local (symmetric) //! ## key types with NO claims, defaults or validation, just basic PASETO //! ## encrypt/signing and decrypt/verification. //! //! rusty_paseto = {version = "latest", features = ["core", "v4_local"] } //! ``` //! //! //! ``` //! // at the top of your source file //! use rusty_paseto::core::*; //! ``` //! # Examples //! //! ## Building and parsing tokens with batteries_included //! //! Here's a basic, default token: //! ``` //! # #[cfg(feature = "default")] //! # { //! use rusty_paseto::prelude::*; //! //! // create a key specifying the PASETO version and purpose //! let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); //! // use a default token builder with the same PASETO version and purpose //! let token = PasetoBuilder::::default().build(&key)?; //! // token is a String in the form: "v4.local.encoded-payload" //! //! # } //! # Ok::<(),anyhow::Error>(()) //! ``` //! //! ## A default token //! //! * Has no [footer](https://github.com/paseto-standard/paseto-spec/tree/master/docs) //! * Has no [implicit assertion](https://github.com/paseto-standard/paseto-spec/tree/master/docs) //! for V3 or V4 versioned tokens //! * Expires in **1 hour** after creation (due to an included default ExpirationClaim) //! * Contains an IssuedAtClaim defaulting to the current utc time the token was created //! * Contains a NotBeforeClaim defaulting to the current utc time the token was created //! //! //! You can parse and validate an existing token with the following: //! ``` //! # #[cfg(feature = "default")] //! # { //! # use rusty_paseto::prelude::*; //! let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); //! # // use a default token builder with the same PASETO version and purpose //! # let token = PasetoBuilder::::default().build(&key)?; //! // now we can parse and validate the token with a parser that returns a serde_json::Value //! let json_value = PasetoParser::::default().parse(&token, &key)?; //! //! //the ExpirationClaim //! assert!(json_value["exp"].is_string()); //! //the IssuedAtClaim //! assert!(json_value["iat"].is_string()); //! //! # } //! # Ok::<(),anyhow::Error>(()) //! ``` //! //! ## A default parser //! //! * Validates the token structure and decryptes the payload or verifies the signature of the content //! * Validates the [footer](https://github.com/paseto-standard/paseto-spec/tree/master/docs) if //! one was provided //! * Validates the [implicit assertion](https://github.com/paseto-standard/paseto-spec/tree/master/docs) if one was provided (for V3 or V4 versioned tokens only) //! //! ## A token with a footer //! //! PASETO tokens can have an [optional footer](https://github.com/paseto-standard/paseto-spec/tree/master/docs). In rusty_paseto we have strict types for most things. //! So we can extend the previous example to add a footer to the token by using code like the //! following: //! ```rust //! # #[cfg(feature = "default")] //! # { //! use rusty_paseto::prelude::*; //! let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); //! let token = PasetoBuilder::::default() //! // note how we set the footer here //! .set_footer(Footer::from("Sometimes science is more art than science")) //! .build(&key)?; //! //! // token is now a String in the form: "v4.local.encoded-payload.footer" //! # } //! //! # Ok::<(),anyhow::Error>(()) //! ``` //! And parse it by passing in the same expected footer //! ``` //! # #[cfg(feature = "default")] //! # { //! # use rusty_paseto::prelude::*; //! # let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); //! # // use a default token builder with the same PASETO version and purpose //! # let token = PasetoBuilder::::default() //! # .set_footer(Footer::from("Sometimes science is more art than science")) //! # .build(&key)?; //! // now we can parse and validate the token with a parser that returns a serde_json::Value //! let json_value = PasetoParser::::default() //! .set_footer(Footer::from("Sometimes science is more art than science")) //! .parse(&token, &key)?; //! //! //the ExpirationClaim //! assert!(json_value["exp"].is_string()); //! //the IssuedAtClaim //! assert!(json_value["iat"].is_string()); //! # } //! //! # Ok::<(),anyhow::Error>(()) //! ``` //! //! //! ## A token with an implicit assertion (V3 or V4 versioned tokens only) //! //! Version 3 (V3) and Version 4 (V4) PASETO tokens can have an [optional implicit assertion](https://github.com/paseto-standard/paseto-spec/tree/master/docs). //! So we can extend the previous example to add an implicit assertion to the token by using code like the //! following: //! ```rust //! # #[cfg(feature = "default")] //! # { //! # use rusty_paseto::prelude::*; //! let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); //! let token = PasetoBuilder::::default() //! .set_footer(Footer::from("Sometimes science is more art than science")) //! // note how we set the implicit assertion here //! .set_implicit_assertion(ImplicitAssertion::from("There’s a lesson here, and I’m not going to be the one to figure it out.")) //! .build(&key)?; //! //! // token is now a String in the form: "v4.local.encoded-payload.footer" //! # } //! //! # Ok::<(),anyhow::Error>(()) //! ``` //! And parse it by passing in the same expected implicit assertion //! ``` //! # #[cfg(feature = "default")] //! # { //! # use rusty_paseto::prelude::*; //! # let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); //! # let token = PasetoBuilder::::default() //! # .set_footer(Footer::from("Sometimes science is more art than science")) //! # .set_implicit_assertion(ImplicitAssertion::from("There’s a lesson here, and I’m not going to be the one to figure it out.")) //! # .build(&key)?; //! // now we can parse and validate the token with a parser that returns a serde_json::Value //! let json_value = PasetoParser::::default() //! .set_footer(Footer::from("Sometimes science is more art than science")) //! .set_implicit_assertion(ImplicitAssertion::from("There’s a lesson here, and I’m not going to be the one to figure it out.")) //! .parse(&token, &key)?; //! //! # //the ExpirationClaim //! # assert!(json_value["exp"].is_string()); //! # //the IssuedAtClaim //! # assert!(json_value["iat"].is_string()); //! # } //! # Ok::<(),anyhow::Error>(()) //! ``` //! //! ## Setting a different expiration time //! //! As mentioned, default tokens expire **1 hour** from creation time. You can set your own //! expiration time by adding an ExpirationClaim which takes an ISO 8601 compliant datetime string. //! #### Note: *claims taking an ISO 8601 string use the TryFrom trait and return a Result<(),PasetoClaimError>* //! ```rust //! # #[cfg(feature = "default")] //! # { //! # use rusty_paseto::prelude::*; //! // must include //! use std::convert::TryFrom; //! let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); //! // real-world example using the time crate to expire 5 minutes from now //! # use time::format_description::well_known::Rfc3339; //! # let in_5_minutes = (time::OffsetDateTime::now_utc() + time::Duration::minutes(5)).format(&Rfc3339)?; //! //! let token = PasetoBuilder::::default() //! // note the TryFrom implmentation for ExpirationClaim //! //.set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) //! .set_claim(ExpirationClaim::try_from(in_5_minutes)?) //! .set_footer(Footer::from("Sometimes science is more art than science")) //! .build(&key)?; //! //! // token is a String in the form: "v4.local.encoded-payload.footer" //! # } //! # Ok::<(),anyhow::Error>(()) //! ``` //! //! ## Tokens that never expire //! //! A **1 hour** ExpirationClaim is set by default because the use case for non-expiring tokens in the world of security tokens is fairly limited. //! Omitting an expiration claim or forgetting to require one when processing them //! is almost certainly an oversight rather than a deliberate choice. //! //! When it is a deliberate choice, you have the opportunity to deliberately remove this claim from the Builder. //! The method call required to do so ensures readers of the code understand the implicit risk. //! ```rust //! # #[cfg(feature = "default")] //! # { //! # use rusty_paseto::prelude::*; //! # use time::format_description::well_known::Rfc3339; //! # use std::convert::TryFrom; //! # let in_5_minutes = (time::OffsetDateTime::now_utc() + time::Duration::minutes(5)).format(&Rfc3339)?; //! # let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); //! let token = PasetoBuilder::::default() //! .set_claim(ExpirationClaim::try_from(in_5_minutes)?) //! // even if you set an expiration claim (as above) it will be ignored //! // due to the method call below //! .set_no_expiration_danger_acknowledged() //! .build(&key)?; //! # } //! # Ok::<(),anyhow::Error>(()) //! ``` //! //! ## Setting PASETO Claims //! //! The PASETO specification includes [seven reserved claims](https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/04-Claims.md) which you can set with their explicit types: //! ```rust //! # #[cfg(all(test,feature = "v4_local"))] //! # { //! # use rusty_paseto::prelude::*; //! # use time::format_description::well_known::Rfc3339; //! # // must include //! # use std::convert::TryFrom; //! # let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); //! # // real-world example using the time crate to expire 5 minutes from now //! # let in_5_minutes = (time::OffsetDateTime::now_utc() + time::Duration::minutes(5)).format(&Rfc3339)?; //! // real-world example using the time crate to prevent the token from being used before 2 //! // minutes from now //! let in_2_minutes = (time::OffsetDateTime::now_utc() + time::Duration::minutes(2)).format(&Rfc3339)?; //! //! let token = PasetoBuilder::::default() //! //json payload key: "exp" //! .set_claim(ExpirationClaim::try_from(in_5_minutes)?) //! //json payload key: "iat" //! // the IssueAtClaim is automatically set to UTC NOW by default //! // but you can override it here //! // .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) //! //json payload key: "nbf" //! //don't use this token before two minutes after UTC NOW //! .set_claim(NotBeforeClaim::try_from(in_2_minutes)?) //! //json payload key: "aud" //! .set_claim(AudienceClaim::from("Cromulons")) //! //json payload key: "sub" //! .set_claim(SubjectClaim::from("Get schwifty")) //! //json payload key: "iss" //! .set_claim(IssuerClaim::from("Earth Cesium-137")) //! //json payload key: "jti" //! .set_claim(TokenIdentifierClaim::from("Planet Music - Season 988")) //! .build(&key)?; //! # } //! # Ok::<(),anyhow::Error>(()) //! ``` //! //! ## Setting your own Custom Claims //! //! The CustomClaim struct takes a tuple in the form of `(key: String, value: T)` where T is any //! serializable type //! #### Note: *CustomClaims use the TryFrom trait and return a Result<(), PasetoClaimError> if you attempt to use one of the [reserved PASETO keys](https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/04-Claims.md) in your CustomClaim* //! //! ```rust //! # #[cfg(all(test, feature = "v4_local"))] //! # { //! # use rusty_paseto::prelude::*; //! # use rusty_paseto::core::{V4,Local, Key}; //! # use rusty_paseto::generic::GenericBuilderError; //! # // must include //! # use std::convert::TryFrom; //! # use rusty_paseto::core::PasetoSymmetricKey; //! # let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); //! let token = PasetoBuilder::::default() //! .set_claim(CustomClaim::try_from(("Co-star", "Morty Smith"))?) //! .set_claim(CustomClaim::try_from(("Universe", 137))?) //! .build(&key)?; //! # Ok::<(),rusty_paseto::generic::GenericBuilderError>(()) //! # } //! ``` //! //! This throws an error: //! ```no_compile //! # #[cfg(feature = "v4_local")] //! # { //! # use rusty_paseto::prelude::*; //! # // must include //! # use std::convert::TryFrom; //! # let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); //! // "exp" is a reserved PASETO claim key, you should use the ExpirationClaim type //! let token = PasetoBuilder::::default() //! .set_claim(CustomClaim::try_from(("exp", "Some expiration value"))?) //! .build(&key)?; //! # } //! # Ok::<(),anyhow::Error>(()) //! ``` //! # Validating claims //! rusty_paseto allows for flexible claim validation at parse time //! //! ## Checking claims //! //! Let's see how we can check particular claims exist with expected values. //! ``` //! # #[cfg(feature = "default")] //! # { //! # use rusty_paseto::prelude::*; //! # use std::convert::TryFrom; //! //! # // create a key specifying the PASETO version and purpose //! # let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); //! // use a default token builder with the same PASETO version and purpose //! let token = PasetoBuilder::::default() //! .set_claim(SubjectClaim::from("Get schwifty")) //! .set_claim(CustomClaim::try_from(("Contestant", "Earth"))?) //! .set_claim(CustomClaim::try_from(("Universe", 137))?) //! .build(&key)?; //! //! PasetoParser::::default() //! // you can check any claim even custom claims //! .check_claim(SubjectClaim::from("Get schwifty")) //! .check_claim(CustomClaim::try_from(("Contestant", "Earth"))?) //! .check_claim(CustomClaim::try_from(("Universe", 137))?) //! .parse(&token, &key)?; //! //! // no need for the assertions below since the check_claim methods //! // above accomplish the same but at parse time! //! //! //assert_eq!(json_value["sub"], "Get schwifty"); //! //assert_eq!(json_value["Contestant"], "Earth"); //! //assert_eq!(json_value["Universe"], 137); //! # } //! # Ok::<(),anyhow::Error>(()) //! ``` //! //! # Custom validation //! //! What if we have more complex validation requirements? You can pass in a reference to a closure which receives //! the key and value of the claim you want to validate so you can implement any validation logic //! you choose. //! //! Let's see how we can validate our tokens only contain universes with prime numbers: //! //! ``` //! # #[cfg(feature = "default")] //! # { //! # use rusty_paseto::prelude::*; //! # use std::convert::TryFrom; //! //! # // create a key specifying the PASETO version and purpose //! # let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); //! // use a default token builder with the same PASETO version and purpose //! let token = PasetoBuilder::::default() //! .set_claim(SubjectClaim::from("Get schwifty")) //! .set_claim(CustomClaim::try_from(("Contestant", "Earth"))?) //! .set_claim(CustomClaim::try_from(("Universe", 137))?) //! .build(&key)?; //! //! PasetoParser::::default() //! .check_claim(SubjectClaim::from("Get schwifty")) //! .check_claim(CustomClaim::try_from(("Contestant", "Earth"))?) //! .validate_claim(CustomClaim::try_from("Universe")?, &|key, value| { //! //let's get the value //! let universe = value //! .as_u64() //! .ok_or(PasetoClaimError::Unexpected(key.to_string()))?; //! // we only accept prime universes in this app //! if primes::is_prime(universe) { //! Ok(()) //! } else { //! Err(PasetoClaimError::CustomValidation(key.to_string())) //! } //! }) //! .parse(&token, &key)?; //! # } //! # Ok::<(),anyhow::Error>(()) //! ``` //! //! This token will fail to parse with the validation code above: //! //! ```no_compile //! # #[cfg(feature = "v4_local")] //! # { //! use rusty_paseto::prelude::*; //! use std::convert::TryFrom; //! //! // create a key specifying the PASETO version and purpose //! let key = PasetoSymmetricKey::::from(Key::from(b"wubbalubbadubdubwubbalubbadubdub")); //! // 136 is not a prime number //! let token = PasetoBuilder::::default() //! .set_claim(CustomClaim::try_from(("Universe", 136))?) //! .build(&key)?; //! //! let json_value = PasetoParser::::default() //! // you can check any claim even custom claims //! .validate_claim(CustomClaim::try_from("Universe")?, &|key, value| { //! // let's get the value //! let universe = value //! .as_u64() //! .ok_or(PasetoClaimError::Unexpected(key.to_string()))?; //! // we only accept prime universes in this token //! if primes::is_prime(universe) { //! Ok(()) //! } else { //! Err(PasetoClaimError::CustomValidation(key.to_string())) //! } //! }) //! .parse(&token, &key)?; //! //! assert_eq!(json_value["Universe"], 136); //! # Ok::<(),anyhow::Error>(()) //! # } //! ``` //! //! # Acknowledgments //! //! If the API of this crate doesn't suit your tastes, check out the other PASETO implementations //! in the Rust ecosystem which inspired rusty_paseto: //! //! - [paseto](https://crates.io/crates/paseto) - by [Cynthia Coan](https://crates.io/users/Mythra) //! - [pasetors](https://crates.io/crates/pasetors) - by [Johannes](https://crates.io/users/brycx) //! //! //public interface #[cfg(feature = "core")] pub mod core; #[cfg(feature = "generic")] pub mod generic; #[cfg(feature = "batteries_included")] pub mod prelude; rusty_paseto-0.7.1/src/prelude/error.rs000064400000000000000000000007651046102023000163300ustar 00000000000000use thiserror::Error; /// Errors from validating claims in a parsed token #[derive(Debug, Error)] pub enum GeneralPasetoError { ///A general, unspecified paseto error #[error("A general paseto error occurred")] PasetoError(Box), #[error("An infallible error occurred")] Infallible { ///An infallible error #[from] source: std::convert::Infallible, }, ///An error with the data format #[error(transparent)] RFC3339Date(#[from] time::error::Format), } rusty_paseto-0.7.1/src/prelude/mod.rs000064400000000000000000000035511046102023000157520ustar 00000000000000//! The outermost architectural layer is called batteries_included. This layer is implemented in the [prelude](self) module. This is what most people will need. //! This feature includes JWT style claims and business rules for your PASETO token (default, but customizable expiration, issued at, not-before times, etc as described in the usage documentation and examples). //! //! ![paseto_batteries_included_small](https://user-images.githubusercontent.com/24578097/147881895-36878b22-bf17-49e4-98d7-f94920353368.png) //! //! You must specify a version and purpose with this feature in order to reduce the size of your dependencies like in the following Cargo.toml entry which only includes the V4 - Local types with batteries_included functionality: //! //! ```toml //! ## Includes only v4 modern sodium cipher crypto core and local (symmetric) //! ## key types with all claims and default business rules. //! //! rusty_paseto = {version = "latest", features = ["batteries_included", "v4_local"] } //! ``` //! ![paseto_batteries_included_v4_local_small](https://user-images.githubusercontent.com/24578097/147882822-46dac1d1-a922-4301-be45-d3341dabfee1.png) //! //! #### Feature gates //! Valid version/purpose feature combinations are as follows: //! - "v1_local" (NIST Original Symmetric Encryption) //! - "v2_local" (Sodium Original Symmetric Encryption) //! - "v3_local" (NIST Modern Symmetric Encryption) //! - "v4_local" (Sodium Modern Symmetric Encryption) //! - "v1_public" (NIST Original Asymmetric Authentication) //! - "v2_public" (Sodium Original Asymmetric Authentication) //! - "v3_public" (NIST Modern Asymmetric Authentication) //! - "v4_public" (Sodium Modern Asymmetric Authentication) mod error; mod paseto_builder; mod paseto_parser; pub use crate::generic::*; pub use error::GeneralPasetoError; pub use paseto_builder::PasetoBuilder; pub use paseto_parser::PasetoParser; rusty_paseto-0.7.1/src/prelude/paseto_builder.rs000064400000000000000000001245261046102023000202020ustar 00000000000000use crate::generic::*; use core::marker::PhantomData; use std::collections::HashSet; use std::convert::TryFrom; use time::format_description::well_known::Rfc3339; ///The PasetoBuilder is created at compile time by specifying a PASETO version and purpose and ///providing a key of the same version and purpose. This structure allows setting [PASETO claims](https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/04-Claims.md), ///your own [custom claims](CustomClaim), an optional [footer](Footer) and in the case of V3/V4 tokens, an optional [implicit ///assertion](ImplicitAssertion). /// ///The PasetoBuilder wraps the [GenericBuilder] with JWT style claims and business rules which align ///with the PASETO standard. /// For most users, this batteries-included struct /// will be all they need. /// ///# Usage /// ///``` ///# #[cfg(all(feature = "prelude", feature="v2_local"))] ///# { /// use rusty_paseto::prelude::*; /// let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); /// let footer = Footer::from("some footer"); /// //create a builder, add some claims and then build the token with the key /// let token = PasetoBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .try_encrypt(&key)?; /// //now let's decrypt the token and verify the values /// let json = PasetoParser::::default() /// .set_footer(footer) /// .parse(&token, &key)?; /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` pub struct PasetoBuilder<'a, Version, Purpose> { version: PhantomData, purpose: PhantomData, builder: GenericBuilder<'a, 'a, Version, Purpose>, top_level_claims: HashSet, dup_top_level_found: (bool, String), non_expiring_token: bool, } impl<'a, Version, Purpose> PasetoBuilder<'a, Version, Purpose> { fn new() -> Self { PasetoBuilder:: { version: PhantomData::, purpose: PhantomData::, builder: GenericBuilder::default(), top_level_claims: HashSet::new(), non_expiring_token: false, dup_top_level_found: (false, String::default()), } } /// Given a [PasetoClaim], attempts to add it to the builder for inclusion in the payload of the /// token. /// claims provided to the GenericBuilder. Overwrites the default 'nbf' (not before) claim if /// provided. Prevents duplicate claims from being added. /// /// Returns a mutable reference to the builder on success. /// /// # Errors /// /// none /// /// # Example ///``` ///# #[cfg(all(feature = "prelude", feature="v2_local"))] ///# { /// use rusty_paseto::prelude::*; /// let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); /// //create a builder, add some claims and then build the token with the key /// let token = PasetoBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .try_encrypt(&key)?; /// # //now let's decrypt the token and verify the values /// # let json = PasetoParser::::default().parse(&token, &key)?; /// # assert_eq!(json["aud"], "customers"); /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn set_claim(&mut self, value: T) -> &mut Self { //we need to inspect all the claims and verify there are no duplicates //overwrite nbf default claim if provided if value.get_key() == "nbf" { //remove the existing claim self.builder.remove_claim(value.get_key()); } if !self.top_level_claims.insert(value.get_key().to_string()) { self.dup_top_level_found = (true, value.get_key().to_string()); } self.builder.set_claim(value); self } /// Sets the token to have no expiration date. /// A **1 hour** ExpirationClaim is set by default because the use case for non-expiring tokens in the world of security tokens is fairly limited. /// Omitting an expiration claim or forgetting to require one when processing them /// is almost certainly an oversight rather than a deliberate choice. /// When it is a deliberate choice, you have the opportunity to deliberately remove this claim from the Builder. /// This method call ensures readers of the code understand the implicit risk. /// /// Returns a mutable reference to the builder on success. /// /// # Errors /// none /// /// # Example ///``` ///# #[cfg(all(feature = "prelude", feature="v2_local"))] ///# { /// use rusty_paseto::prelude::*; /// let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); /// let token = PasetoBuilder::::default() /// .set_claim(ExpirationClaim::try_from(in_5_minutes)?) /// // even if you set an expiration claim (as above) it will be ignored /// // due to the method call below /// .set_no_expiration_danger_acknowledged() /// .build(&key)?; /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn set_no_expiration_danger_acknowledged(&mut self) -> &mut Self { self.top_level_claims.insert("exp".to_string()); self.non_expiring_token = true; self } /// Sets an optional [Footer] on the token. /// /// Returns a mutable reference to the builder on success. /// /// # Errors ///none /// /// # Example ///``` ///# #[cfg(all(feature = "prelude", feature="v2_local"))] ///# { /// use rusty_paseto::prelude::*; /// let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); /// let token = PasetoBuilder::::default() /// .set_footer(Footer::from("Some footer")) /// .build(&key)?; /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn set_footer(&mut self, footer: Footer<'a>) -> &mut Self { self.builder.set_footer(footer); self } fn verify_ready_to_build(&mut self) -> Result<(), GenericBuilderError> { if self.non_expiring_token { self.builder.remove_claim("exp"); } // //raise an error if there were duplicates let (dup_found, dup_key) = &self.dup_top_level_found; if *dup_found { return Err(GenericBuilderError::DuplicateTopLevelPayloadClaim(dup_key.to_string())); } Ok(()) } } impl<'a, Version, Purpose> PasetoBuilder<'a, Version, Purpose> where Version: ImplicitAssertionCapable, { /// Sets an optional [ImplicitAssertion] on the token. ([V3] or [V4] tokens only) /// /// Returns a mutable reference to the builder on success. /// /// # Errors ///none /// /// # Example ///``` ///# #[cfg(all(feature = "prelude", feature="v3_local"))] ///# { /// use rusty_paseto::prelude::*; /// let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); /// let token = PasetoBuilder::::default() /// .set_implicit_assertion(ImplicitAssertion::from("Some assertion")) /// .build(&key)?; /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn set_implicit_assertion(&mut self, implicit_assertion: ImplicitAssertion<'a>) -> &mut Self { self.builder.set_implicit_assertion(implicit_assertion); self } } impl<'a, Version, Purpose> Default for PasetoBuilder<'a, Version, Purpose> { fn default() -> Self { //the unwraps in this function should be Infallible let mut new_builder = Self::new(); let now = time::OffsetDateTime::now_utc(); let in_one_hour = now + time::Duration::hours(1); let expiration_time = in_one_hour.format(&Rfc3339).unwrap(); let current_time = now.format(&Rfc3339).unwrap(); //set some defaults new_builder .builder .set_claim(ExpirationClaim::try_from(expiration_time).unwrap()) .set_claim(IssuedAtClaim::try_from(current_time.clone()).unwrap()) .set_claim(NotBeforeClaim::try_from(current_time).unwrap()); new_builder } } #[cfg(feature = "v1_local")] impl PasetoBuilder<'_, V1, Local> { /// Attempts to validate claims meet PASETO standard requirements and then encrypt the token. /// /// Returns Ok(String) where the string is the encrypted PASETO token. /// /// # Errors /// [GenericBuilderError] if there are [claim](PasetoClaim) or encryption issues. /// /// # Example ///``` ///# #[cfg(all(feature = "prelude", feature="v1_local"))] ///# { /// use rusty_paseto::prelude::*; /// let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); /// let footer = Footer::from("some footer"); /// //create a builder, add some claims and then build the token with the key /// let token = PasetoBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .build(&key)?; /// //now let's decrypt the token and verify the values /// let json = PasetoParser::::default() /// .set_footer(footer) /// .parse(&token, &key)?; /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn build(&mut self, key: &PasetoSymmetricKey) -> Result { self.verify_ready_to_build()?; self.builder.try_encrypt(key) } } #[cfg(feature = "v2_local")] impl PasetoBuilder<'_, V2, Local> { /// Attempts to validate claims meet PASETO standard requirements and then encrypt the token. /// /// Returns Ok(String) where the string is the encrypted PASETO token. /// /// # Errors /// [GenericBuilderError] if there are [claim](PasetoClaim) or encryption issues. /// /// # Example /// /// ///``` ///# #[cfg(all(feature = "prelude", feature="v2_local"))] ///# { /// use rusty_paseto::prelude::*; /// let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); /// let footer = Footer::from("some footer"); /// //create a builder, add some claims and then build the token with the key /// let token = PasetoBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .try_encrypt(&key)?; /// //now let's decrypt the token and verify the values /// let json = PasetoParser::::default() /// .set_footer(footer) /// .parse(&token, &key)?; /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn build(&mut self, key: &PasetoSymmetricKey) -> Result { self.verify_ready_to_build()?; self.builder.try_encrypt(key) } } #[cfg(feature = "v3_local")] impl PasetoBuilder<'_, V3, Local> { /// Attempts to validate claims meet PASETO standard requirements and then encrypt the token. /// /// Returns Ok(String) where the string is the encrypted PASETO token. /// /// # Errors /// [GenericBuilderError] if there are [claim](PasetoClaim) or encryption issues. /// /// # Example /// ///``` ///# #[cfg(all(feature = "prelude", feature="v3_local"))] ///# { /// use rusty_paseto::prelude::*; /// let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); /// let footer = Footer::from("some footer"); /// let implicit_assertion = ImplicitAssertion::from("some assertion"); /// //create a builder, add some claims and then build the token with the key /// let token = PasetoBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .set_implicit_assertion(implicit_assertion) /// .try_encrypt(&key)?; /// //now let's decrypt the token and verify the values /// let json = PasetoParser::::default() /// .set_footer(footer) /// .set_implicit_assertion(implicit_assertion) /// .parse(&token, &key)?; /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn build(&mut self, key: &PasetoSymmetricKey) -> Result { self.verify_ready_to_build()?; self.builder.try_encrypt(key) } } #[cfg(feature = "v4_local")] impl PasetoBuilder<'_, V4, Local> { /// Attempts to validate claims meet PASETO standard requirements and then encrypt the token. /// /// Returns Ok(String) where the string is the encrypted PASETO token. /// /// # Errors /// [GenericBuilderError] if there are [claim](PasetoClaim) or encryption issues. /// /// # Example /// ///``` ///# #[cfg(all(feature = "prelude", feature="v4_local"))] ///# { /// use rusty_paseto::prelude::*; /// let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); /// let footer = Footer::from("some footer"); /// let implicit_assertion = ImplicitAssertion::from("some assertion"); /// //create a builder, add some claims and then build the token with the key /// let token = PasetoBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .set_implicit_assertion(implicit_assertion) /// .try_encrypt(&key)?; /// //now let's decrypt the token and verify the values /// let json = PasetoParser::::default() /// .set_footer(footer) /// .set_implicit_assertion(implicit_assertion) /// .parse(&token, &key)?; /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn build(&mut self, key: &PasetoSymmetricKey) -> Result { self.verify_ready_to_build()?; self.builder.try_encrypt(key) } } #[cfg(feature = "v1_public")] impl PasetoBuilder<'_, V1, Public> { /// Given a [PasetoAsymmetricPrivateKey], attempts to validate claims meet PASETO standard requirements and then sign the token. /// /// Returns Ok(String) where the string is the signed PASETO token. /// /// # Errors /// [GenericBuilderError] if there are [claim](PasetoClaim) or signing issues. /// /// # Example /// ///``` ///# #[cfg(all(feature = "prelude", feature="v1_public"))] ///# { /// # use rusty_paseto::prelude::*; /// //obtain a private key (pk) /// # let private_key = include_bytes!("../../../tests/v1_public_test_vectors_private_key.pk8"); /// # let pk: &[u8] = private_key; /// let private_key = PasetoAsymmetricPrivateKey::::from(pk); /// let footer = Footer::from("some footer"); /// //sign a public V1 token /// let token = PasetoBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .try_sign(&private_key)?; /// //obtain a public key (pubk) /// # let public_key = include_bytes!("../../../tests/v1_public_test_vectors_public_key.der"); /// # let pubk: &[u8] = public_key; /// let public_key = PasetoAsymmetricPublicKey::::from(pubk); /// //now let's try to verify it /// let json = PasetoParser::::default() /// .set_footer(footer) /// .check_claim(AudienceClaim::from("customers")) /// .check_claim(SubjectClaim::from("loyal subjects")) /// .check_claim(IssuerClaim::from("me")) /// .check_claim(TokenIdentifierClaim::from("me")) /// .check_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .check_claim(CustomClaim::try_from(("seats", 4))?) /// .check_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .parse(&token, &public_key)?; /// // we can access all the values from the serde Value object returned by the parser /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) ///``` pub fn build(&mut self, key: &PasetoAsymmetricPrivateKey) -> Result { self.verify_ready_to_build()?; self.builder.try_sign(key) } } #[cfg(feature = "v2_public")] impl PasetoBuilder<'_, V2, Public> { /// Given a [PasetoAsymmetricPrivateKey], attempts to validate claims meet PASETO standard requirements and then sign the token. /// /// Returns Ok(String) where the string is the signed PASETO token. /// /// # Errors /// [GenericBuilderError] if there are [claim](PasetoClaim) or signing issues. /// /// # Example ///``` ///# #[cfg(all(feature = "prelude", feature="v2_public"))] ///# { /// # use rusty_paseto::prelude::*; /// //obtain a key /// let private_key = Key::<64>::try_from("b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; /// let private_key = PasetoAsymmetricPrivateKey::::from(&private_key); /// let public_key = Key::<32>::try_from("1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; /// let public_key = PasetoAsymmetricPublicKey::::from(&public_key); /// let footer = Footer::from("some footer"); /// //sign a public V2 token /// let token = PasetoBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .try_sign(&private_key)?; /// //now let's try to verify it /// let json = PasetoParser::::default() /// .set_footer(footer) /// .check_claim(AudienceClaim::from("customers")) /// .check_claim(SubjectClaim::from("loyal subjects")) /// .check_claim(IssuerClaim::from("me")) /// .check_claim(TokenIdentifierClaim::from("me")) /// .check_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .check_claim(CustomClaim::try_from(("seats", 4))?) /// .check_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .parse(&token, &public_key)?; /// // we can access all the values from the serde Value object returned by the parser /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) ///``` pub fn build(&mut self, key: &PasetoAsymmetricPrivateKey) -> Result { self.verify_ready_to_build()?; self.builder.try_sign(key) } } #[cfg(feature = "v3_public")] impl PasetoBuilder<'_, V3, Public> { /// Given a [PasetoAsymmetricPrivateKey], attempts to validate claims meet PASETO standard requirements and then sign the token. /// /// Returns Ok(String) where the string is the signed PASETO token. /// /// # Errors /// [GenericBuilderError] if there are [claim](PasetoClaim) or signing issues. /// /// # Example ///``` ///# #[cfg(all(feature = "prelude", feature="v3_public"))] ///# { /// # use rusty_paseto::prelude::*; /// //obtain a key /// let private_key = Key::<48>::try_from( /// "20347609607477aca8fbfbc5e6218455f3199669792ef8b466faa87bdc67798144c848dd03661eed5ac62461340cea96", /// )?; /// let private_key = PasetoAsymmetricPrivateKey::::from(&private_key); /// let public_key = Key::<49>::try_from( /// "02fbcb7c69ee1c60579be7a334134878d9c5c5bf35d552dab63c0140397ed14cef637d7720925c44699ea30e72874c72fb", /// )?; /// let public_key = PasetoAsymmetricPublicKey::::try_from(&public_key)?; /// let footer = Footer::from("some footer"); /// let implicit_assertion = ImplicitAssertion::from("some assertion"); /// //sign a public V3 token /// let token = PasetoBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .set_implicit_assertion(implicit_assertion) /// .try_sign(&private_key)?; /// //now let's try to verify it /// let json = PasetoParser::::default() /// .set_footer(footer) /// .check_claim(AudienceClaim::from("customers")) /// .set_implicit_assertion(implicit_assertion) /// .check_claim(SubjectClaim::from("loyal subjects")) /// .check_claim(IssuerClaim::from("me")) /// .check_claim(TokenIdentifierClaim::from("me")) /// .check_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .check_claim(CustomClaim::try_from(("seats", 4))?) /// .check_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .parse(&token, &public_key)?; /// // we can access all the values from the serde Value object returned by the parser /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) ///``` pub fn build(&mut self, key: &PasetoAsymmetricPrivateKey) -> Result { self.verify_ready_to_build()?; self.builder.try_sign(key) } } #[cfg(feature = "v4_public")] impl PasetoBuilder<'_, V4, Public> { /// Given a [PasetoAsymmetricPrivateKey], attempts to validate claims meet PASETO standard requirements and then sign the token. /// /// Returns Ok(String) where the string is the signed PASETO token. /// /// # Errors /// [GenericBuilderError] if there are [claim](PasetoClaim) or signing issues. /// /// # Example ///``` ///# #[cfg(all(feature = "prelude", feature="v4_public"))] ///# { /// # use rusty_paseto::prelude::*; /// //create a key /// let private_key = Key::<64>::try_from("b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; /// let pk: &[u8] = private_key.as_slice(); /// let private_key = PasetoAsymmetricPrivateKey::::from(pk); /// let public_key = Key::<32>::try_from("1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; /// let public_key = PasetoAsymmetricPublicKey::::from(&public_key); /// let footer = Footer::from("some footer"); /// let implicit_assertion = ImplicitAssertion::from("some assertion"); /// //sign a public V4 token /// let token = PasetoBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .set_implicit_assertion(implicit_assertion) /// .try_sign(&private_key)?; /// //now let's try to verify it /// let json = PasetoParser::::default() /// .set_footer(footer) /// .set_implicit_assertion(implicit_assertion) /// .check_claim(AudienceClaim::from("customers")) /// .check_claim(SubjectClaim::from("loyal subjects")) /// .check_claim(IssuerClaim::from("me")) /// .check_claim(TokenIdentifierClaim::from("me")) /// .check_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .check_claim(CustomClaim::try_from(("seats", 4))?) /// .check_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .parse(&token, &public_key)?; /// // we can access all the values from the serde Value object returned by the parser /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) ///``` pub fn build(&mut self, key: &PasetoAsymmetricPrivateKey) -> Result { self.verify_ready_to_build()?; self.builder.try_sign(key) } } #[cfg(all(test, feature = "v2_local"))] mod paseto_builder { use crate::prelude::*; use anyhow::Result; use std::convert::TryFrom; use time::format_description::well_known::Rfc3339; #[test] fn duplicate_top_level_claim_test() -> Result<()> { //create a key let key = PasetoSymmetricKey::::from(Key::from(*b"wubbalubbadubdubwubbalubbadubdub")); let tomorrow = (time::OffsetDateTime::now_utc() + time::Duration::days(1)).format(&Rfc3339)?; //let tomorrow = (Utc::now() + Duration::days(1)).to_rfc3339(); //create a builder, with default IssuedAtClaim let expected_error = format!( "{}", PasetoBuilder::::default() .set_claim(IssuedAtClaim::try_from(tomorrow.as_str()).unwrap()) .set_claim(IssuedAtClaim::try_from(tomorrow.as_str()).unwrap()) .build(&key) .unwrap_err() ); assert_eq!( expected_error, "The claim 'iat' appears more than once in the top level payload json" ); Ok(()) } #[test] fn update_default_not_before_claim_test() -> Result<()> { //create a key let key = PasetoSymmetricKey::::from(Key::from(*b"wubbalubbadubdubwubbalubbadubdub")); let tomorrow = (time::OffsetDateTime::now_utc() + time::Duration::days(1)).format(&Rfc3339)?; //create a builder, with default IssuedAtClaim let token = PasetoBuilder::::default() .set_claim(NotBeforeClaim::try_from(tomorrow).unwrap()) .build(&key)?; //now let's decrypt the token and verify the values //the IssuedAtClaim should exist and the date should be set to tomorrow let token_error = PasetoParser::::default().parse(&token, &key).err().unwrap(); assert!(token_error.to_string().starts_with("The token cannot be used before ")); Ok(()) } #[test] fn update_default_issued_at_claim_test() -> Result<()> { //create a key let key = PasetoSymmetricKey::::from(Key::from(*b"wubbalubbadubdubwubbalubbadubdub")); let tomorrow = (time::OffsetDateTime::now_utc() + time::Duration::days(1)).format(&Rfc3339)?; //create a builder, with default IssuedAtClaim let token = PasetoBuilder::::default() .set_claim(IssuedAtClaim::try_from(tomorrow.as_str()).unwrap()) .build(&key)?; //now let's decrypt the token and verify the values //the IssuedAtClaim should exist and the date should be set to tomorrow GenericParser::::default() .validate_claim(IssuedAtClaim::default(), &|key, value| { //let's get the value let val = value .as_str() .ok_or_else(|| PasetoClaimError::Unexpected(key.to_string()))?; let datetime = iso8601::datetime(val).unwrap(); let tomorrow = (time::OffsetDateTime::now_utc() + time::Duration::days(1)) .date() .to_string(); //the claimm should exist assert_eq!(key, "iat"); //date should be tomorrow assert_eq!(datetime.date.to_string(), tomorrow); Ok(()) }) .parse(&token, &key)?; Ok(()) } #[test] fn check_for_default_issued_at_claim_test() -> Result<()> { //create a key let key = PasetoSymmetricKey::::from(Key::from(*b"wubbalubbadubdubwubbalubbadubdub")); //create a builder, with default IssuedAtClaim let token = PasetoBuilder::::default().build(&key)?; //now let's decrypt the token and verify the values //the IssuedAtClaim should exist GenericParser::::default() .validate_claim(IssuedAtClaim::default(), &|key, value| { //let's get the value let val = value .as_str() .ok_or_else(|| PasetoClaimError::Unexpected(key.to_string()))?; let datetime = iso8601::datetime(val).unwrap(); let now = time::OffsetDateTime::now_utc().date().to_string(); //the claimm should exist assert_eq!(key, "iat"); //date should be today assert_eq!(datetime.date.to_string(), now); Ok(()) }) .parse(&token, &key)?; Ok(()) } #[test] fn update_default_expiration_claim_test() -> Result<()> { //create a key let key = PasetoSymmetricKey::::from(Key::from(*b"wubbalubbadubdubwubbalubbadubdub")); //let in_4_days = (Utc::now() + Duration::days(4)).to_rfc3339(); let in_4_days = (time::OffsetDateTime::now_utc() + time::Duration::days(4)).format(&Rfc3339)?; //create a builder, with default IssuedAtClaim let token = PasetoBuilder::::default() .set_claim(ExpirationClaim::try_from(in_4_days).unwrap()) .build(&key)?; //now let's decrypt the token and verify the values //the IssuedAtClaim should exist and the date should be set to tomorrow GenericParser::::default() .validate_claim(ExpirationClaim::default(), &|key, value| { //let's get the value let val = value .as_str() .ok_or_else(|| PasetoClaimError::Unexpected(key.to_string()))?; let datetime = iso8601::datetime(val).unwrap(); let in_4_days = (time::OffsetDateTime::now_utc() + time::Duration::days(4)) .date() .to_string(); //let in_4_days = Utc::now() + Duration::days(4); //the claimm should exist assert_eq!(key, "exp"); //date should be tomorrow assert_eq!(datetime.date.to_string(), in_4_days); Ok(()) }) .parse(&token, &key)?; Ok(()) } #[test] fn check_for_default_expiration_claim_test() -> Result<()> { //create a key let key = PasetoSymmetricKey::::from(Key::from(*b"wubbalubbadubdubwubbalubbadubdub")); //create a builder, with default IssuedAtClaim let token = PasetoBuilder::::default().build(&key)?; //now let's decrypt the token and verify the values //the IssuedAtClaim should exist GenericParser::::default() .validate_claim(ExpirationClaim::default(), &|key, value| { //let's get the value let val = value .as_str() .ok_or_else(|| PasetoClaimError::Unexpected(key.to_string()))?; let datetime = iso8601::datetime(val).unwrap(); let expires = (time::OffsetDateTime::now_utc() + time::Duration::hours(1)) .date() .to_string(); //let tomorrow = Utc::now() + Duration::hours(1); //the claimm should exist assert_eq!(key, "exp"); //date should be today assert_eq!(datetime.date.to_string(), expires); Ok(()) }) .parse(&token, &key)?; Ok(()) } #[test] fn full_paseto_builder_test() -> Result<()> { //create a key let key = PasetoSymmetricKey::::from(Key::from(*b"wubbalubbadubdubwubbalubbadubdub")); let footer = Footer::from("some footer"); //create a builder, add some claims and then build the token with the key let token = PasetoBuilder::::default() .set_claim(AudienceClaim::from("customers")) .set_claim(SubjectClaim::from("loyal subjects")) .set_claim(IssuerClaim::from("me")) .set_claim(TokenIdentifierClaim::from("me")) .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) .set_claim(CustomClaim::try_from(("seats", 4))?) .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) .set_footer(footer) .build(&key)?; //now let's decrypt the token and verify the values let json = GenericParser::::default() .check_claim(AudienceClaim::from("customers")) .check_claim(SubjectClaim::from("loyal subjects")) .check_claim(IssuerClaim::from("me")) .check_claim(TokenIdentifierClaim::from("me")) .check_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) .check_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) .check_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) .check_claim(CustomClaim::try_from(("data", "this is a secret message"))?) .check_claim(CustomClaim::try_from(("seats", 4))?) .check_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) .set_footer(footer) .parse(&token, &key)?; // we can access all the values from the serde Value object returned by the parser assert_eq!(json["aud"], "customers"); assert_eq!(json["jti"], "me"); assert_eq!(json["iss"], "me"); assert_eq!(json["data"], "this is a secret message"); assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["sub"], "loyal subjects"); assert_eq!(json["pi to 6 digits"], 3.141526); assert_eq!(json["seats"], 4); Ok(()) } } rusty_paseto-0.7.1/src/prelude/paseto_parser.rs000064400000000000000000001457361046102023000200560ustar 00000000000000use crate::generic::*; use core::marker::PhantomData; use serde_json::Value; use time::format_description::well_known::Rfc3339; ///The PasetoParser is created at compile time by specifying a PASETO version and purpose and ///providing a key of the same version and purpose. This structure allows setting [PASETO claims](https://github.com/paseto-standard/paseto-spec/blob/master/docs/02-Implementation-Guide/04-Claims.md), ///your own [custom claims](CustomClaim), an optional [footer](Footer) and in the case of V3/V4 tokens, an optional [implicit ///assertion](ImplicitAssertion). ///Additionally, a user can /// ///The PasetoBuilder wraps the [GenericBuilder] with JWT style claims and business rules which align ///with the PASETO standard. /// For most users, this batteries-included struct /// will be all they need. /// ///# Usage /// ///``` ///# #[cfg(all(feature = "prelude", feature="v2_local"))] ///# { /// use rusty_paseto::prelude::*; /// let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); /// let footer = Footer::from("some footer"); /// //create a builder, add some claims and then build the token with the key /// let token = PasetoBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .try_encrypt(&key)?; /// //now let's decrypt the token and verify the values /// let json = PasetoParser::::default() /// .set_footer(footer) /// .parse(&token, &key)?; /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` pub struct PasetoParser<'a, Version, Purpose> { version: PhantomData, purpose: PhantomData, parser: GenericParser<'a, 'a, Version, Purpose>, } impl<'a, Version, Purpose> PasetoParser<'a, Version, Purpose> { /// Creates a new parser. Called by [Default] /// /// Returns a new PASETO parser builder for construction. /// /// # Errors /// none /// /// # Example (use default) ///``` ///# #[cfg(all(feature = "prelude", feature="v2_local"))] ///# { /// use rusty_paseto::prelude::*; /// # let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); /// # let footer = Footer::from("some footer"); /// # //create a builder, add some claims and then build the token with the key /// # let token = PasetoBuilder::::default() /// # .try_encrypt(&key)?; /// //decrypt and parse claims from the PASETO token string /// let json = PasetoParser::::default() /// .parse(&token, &key)?; /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn new() -> Self { PasetoParser::<'a, Version, Purpose> { version: PhantomData::, purpose: PhantomData::, parser: GenericParser::default(), } } /// Takes a [PasetoClaim] and a [ValidatorFn] and uses the function to validate the claim during /// parsing and after decryption or signature verification. /// /// Returns a mutable reference to the parser /// /// # Errors /// none /// /// # Example ///``` ///# #[cfg(all(feature = "prelude", feature="v2_local"))] ///# { /// use rusty_paseto::prelude::*; /// # let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); /// # let footer = Footer::from("some footer"); /// # //create a builder, add some claims and then build the token with the key /// # let token = PasetoBuilder::::default() /// # .try_encrypt(&key)?; /// //decrypt and parse claims from the PASETO token string /// let json = PasetoParser::::default() /// .validate_claim(ExpirationClaim::default(), &|key, value| { /// //let's get the value /// let val = value.as_str().ok_or(PasetoClaimError::Unexpected(key.to_string()))?; /// let datetime = iso8601::datetime(val).unwrap(); /// let in_an_hour = (time::OffsetDateTime::now_utc() + time::Duration::hours(1)) /// .time() /// .hour() /// .to_string(); /// //the claimm should exist /// assert_eq!(key, "exp"); /// //date should be today /// assert_eq!(datetime.time.hour.to_string(), in_an_hour); /// Ok(()) /// }) /// .parse(&token, &key)?; /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn validate_claim( &mut self, value: T, validation_closure: &'static ValidatorFn, ) -> &mut Self { self.parser.validate_claim(value, validation_closure); self } /// Takes a [PasetoClaim] to ensure existence of the claim and it's value during /// parsing and after decryption or signature verification. /// /// Returns a mutable reference to the parser /// /// # Errors /// none /// /// # Example ///``` ///# #[cfg(all(feature = "prelude", feature="v2_local"))] ///# { /// use rusty_paseto::prelude::*; /// # let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); /// # let footer = Footer::from("some footer"); /// # //create a builder, add some claims and then build the token with the key /// # let token = PasetoBuilder::::default() /// # .set_claim(AudienceClaim::from("customers")) /// # .try_encrypt(&key)?; /// //decrypt and parse claims from the PASETO token string /// let json = PasetoParser::::default() /// .check_claim(AudienceClaim::from("customers")) /// .parse(&token, &key)?; /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn check_claim(&mut self, value: T) -> &mut Self { self.parser.check_claim(value); self } ///Sets an optional [Footer] to use during parsing pub fn set_footer(&mut self, footer: Footer<'a>) -> &mut Self { self.parser.set_footer(footer); self } } impl<'a, Version, Purpose> PasetoParser<'a, Version, Purpose> where Version: ImplicitAssertionCapable, { ///Sets an optional [ImplicitAssertion] to use during parsing ([V3], [V4] tokens only) pub fn set_implicit_assertion(&mut self, implicit_assertion: ImplicitAssertion<'a>) -> &mut Self { self.parser.set_implicit_assertion(implicit_assertion); self } } impl<'a, Version, Purpose> Default for PasetoParser<'a, Version, Purpose> { fn default() -> Self { let mut me = Self::new(); me.validate_claim(ExpirationClaim::default(), &|_, value| { //let's get the expiration claim value let val = value.as_str().unwrap_or_default(); //check if this is a non-expiring token if val.is_empty() { //this means the claim wasn't found, which means this is a non-expiring token //and we can just skip this validation return Ok(()); } //turn the value into a datetime let datetime = time::OffsetDateTime::parse(val, &Rfc3339).map_err(|_| PasetoClaimError::RFC3339Date(val.to_string()))?; //get the current datetime let now = time::OffsetDateTime::now_utc(); //here we do the actual validation check for the expiration claim if datetime <= now { Err(PasetoClaimError::Expired) } else { Ok(()) } }) .validate_claim(NotBeforeClaim::default(), &|_, value| { //let's get the expiration claim value let val = value.as_str().unwrap_or_default(); //if there is no value here, then the user didn't provide the claim so we just move on if val.is_empty() { return Ok(()); } //otherwise let's continue with the validation //turn the value into a datetime let not_before_time = time::OffsetDateTime::parse(val, &Rfc3339).map_err(|_| PasetoClaimError::RFC3339Date(val.to_string()))?; //get the current datetime let now = time::OffsetDateTime::now_utc(); //here we do the actual validation check for the expiration claim if now <= not_before_time { Err(PasetoClaimError::UseBeforeAvailable(not_before_time.to_string())) } else { Ok(()) } }); me } } #[cfg(feature = "v1_local")] impl<'a> PasetoParser<'a, V1, Local> { /// Given a [PasetoSymmetricKey], attempts to decrypt a (V1, Local) encrypted PASETO token string and then validate /// claims provided when building the PasetoParser. /// /// Returns a serde_json [Value] with the decrypted [claims](PasetoClaim). /// /// # Errors /// /// Returns [`GenericParserError`] for any errors when decrypting the encrypted payload or when validating claims. /// /// # Example /// /// ///``` ///# #[cfg(all(feature = "prelude", feature="v1_local"))] ///# { /// use rusty_paseto::prelude::*; /// let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); /// let footer = Footer::from("some footer"); /// //create a builder, add some claims and then build the token with the key /// let token = ParserBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .try_encrypt(&key)?; /// //now let's decrypt the token and verify the values /// let json = ParserParser::::default() /// .set_footer(footer) /// .parse(&token, &key)?; /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn parse(&mut self, token: &'a str, key: &'a PasetoSymmetricKey) -> Result { //return the full json value to the user self.parser.parse(token, key) } } #[cfg(feature = "v2_local")] impl<'a> PasetoParser<'a, V2, Local> { /// Given a [PasetoSymmetricKey], attempts to decrypt a (V2, Local) encrypted PASETO token string and then validate /// claims provided when building the PasetoParser. /// /// Returns a serde_json [Value] with the decrypted [claims](PasetoClaim). /// /// # Errors /// /// Returns [`GenericParserError`] for any errors when decrypting the encrypted payload or when validating claims. /// /// # Example ///``` ///# #[cfg(all(feature = "prelude", feature="v2_local"))] ///# { /// use rusty_paseto::prelude::*; /// let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); /// let footer = Footer::from("some footer"); /// //create a builder, add some claims and then build the token with the key /// let token = PasetoBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .try_encrypt(&key)?; /// //now let's decrypt the token and verify the values /// let json = PasetoParser::::default() /// .set_footer(footer) /// .parse(&token, &key)?; /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn parse(&mut self, token: &'a str, key: &'a PasetoSymmetricKey) -> Result { //return the full json value to the user self.parser.parse(token, key) } } #[cfg(feature = "v3_local")] impl<'a> PasetoParser<'a, V3, Local> { /// Given a [PasetoSymmetricKey], attempts to decrypt a (V1, Local) encrypted PASETO token string and then validate /// claims provided when building the PasetoParser. /// /// Returns a serde_json [Value] with the decrypted [claims](PasetoClaim). /// /// # Errors /// /// Returns [`GenericParserError`] for any errors when decrypting the encrypted payload or when validating claims. /// /// # Example ///``` ///# #[cfg(all(feature = "prelude", feature="v3_local"))] ///# { /// use rusty_paseto::prelude::*; /// let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); /// let footer = Footer::from("some footer"); /// let implicit_assertion = ImplicitAssertion::from("some assertion"); /// //create a builder, add some claims and then build the token with the key /// let token = PasetoBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .set_implicit_assertion(implicit_assertion) /// .try_encrypt(&key)?; /// //now let's decrypt the token and verify the values /// let json = PasetoParser::::default() /// .set_footer(footer) /// .set_implicit_assertion(implicit_assertion) /// .parse(&token, &key)?; /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn parse(&mut self, token: &'a str, key: &'a PasetoSymmetricKey) -> Result { //return the full json value to the user self.parser.parse(token, key) } } #[cfg(feature = "v4_local")] impl<'a> PasetoParser<'a, V4, Local> { /// Given a [PasetoSymmetricKey], attempts to decrypt a (V4, Local) encrypted PASETO token string and then validate /// claims provided when building the PasetoParser. /// /// Returns a serde_json [Value] with the decrypted [claims](PasetoClaim). /// /// # Errors /// /// Returns [`GenericParserError`] for any errors when decrypting the encrypted payload or when validating claims. /// /// # Example ///``` ///# #[cfg(all(feature = "prelude", feature="v4_local"))] ///# { /// use rusty_paseto::prelude::*; /// let key = PasetoSymmetricKey::::from(Key::<32>::from(*b"wubbalubbadubdubwubbalubbadubdub")); /// let footer = Footer::from("some footer"); /// let implicit_assertion = ImplicitAssertion::from("some assertion"); /// //create a builder, add some claims and then build the token with the key /// let token = PasetoBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .set_implicit_assertion(implicit_assertion) /// .try_encrypt(&key)?; /// //now let's decrypt the token and verify the values /// let json = PasetoParser::::default() /// .set_footer(footer) /// .set_implicit_assertion(implicit_assertion) /// .parse(&token, &key)?; /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) /// ``` pub fn parse(&mut self, token: &'a str, key: &'a PasetoSymmetricKey) -> Result { //return the full json value to the user self.parser.parse(token, key) } } #[cfg(feature = "v1_public")] impl<'a> PasetoParser<'a, V1, Public> { /// Given a [PasetoAsymmetricPrivateKey], attempts to verify a signed (V1, Public) PASETO token string and then validate /// claims provided when building the PasetoParser. /// /// Returns a serde_json [Value] with the verified [claims](PasetoClaim). /// /// # Errors /// /// Returns [`GenericParserError`] for any errors when decrypting the encrypted payload or when validating claims. /// /// # Example ///``` ///# #[cfg(all(feature = "prelude", feature="v1_public"))] ///# { /// # use rusty_paseto::prelude::*; /// //obtain a private key (pk) /// # let private_key = include_bytes!("../../../tests/v1_public_test_vectors_private_key.pk8"); /// # let pk: &[u8] = private_key; /// let private_key = PasetoAsymmetricPrivateKey::::from(pk); /// let footer = Footer::from("some footer"); /// //sign a public V1 token /// let token = PasetoBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .try_sign(&private_key)?; /// //obtain a public key (pubk) /// # let public_key = include_bytes!("../../../tests/v1_public_test_vectors_public_key.der"); /// # let pubk: &[u8] = public_key; /// let public_key = PasetoAsymmetricPublicKey::::from(pubk); /// //now let's try to verify it /// let json = PasetoParser::::default() /// .set_footer(footer) /// .check_claim(AudienceClaim::from("customers")) /// .check_claim(SubjectClaim::from("loyal subjects")) /// .check_claim(IssuerClaim::from("me")) /// .check_claim(TokenIdentifierClaim::from("me")) /// .check_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .check_claim(CustomClaim::try_from(("seats", 4))?) /// .check_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .parse(&token, &public_key)?; /// // we can access all the values from the serde Value object returned by the parser /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) ///``` pub fn parse( &mut self, token: &'a str, key: &'a PasetoAsymmetricPublicKey, ) -> Result { //return the full json value to the user self.parser.parse(token, key) } } #[cfg(feature = "v2_public")] impl<'a> PasetoParser<'a, V2, Public> { /// Given a [PasetoAsymmetricPrivateKey], attempts to verify a signed (V2, Public) PASETO token string and then validate /// claims provided when building the PasetoParser. /// /// Returns a serde_json [Value] with the verified [claims](PasetoClaim). /// /// # Errors /// /// Returns [`GenericParserError`] for any errors when decrypting the encrypted payload or when validating claims. /// /// # Example ///``` ///# #[cfg(all(feature = "prelude", feature="v1_public"))] ///# { /// # use rusty_paseto::prelude::*; /// //obtain a key /// let private_key = Key::<64>::try_from("b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; /// let private_key = PasetoAsymmetricPrivateKey::::from(&private_key); /// let public_key = Key::<32>::try_from("1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; /// let public_key = PasetoAsymmetricPublicKey::::from(&public_key); /// let footer = Footer::from("some footer"); /// //sign a public V2 token /// let token = PasetoBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .try_sign(&private_key)?; /// //now let's try to verify it /// let json = PasetoParser::::default() /// .set_footer(footer) /// .check_claim(AudienceClaim::from("customers")) /// .check_claim(SubjectClaim::from("loyal subjects")) /// .check_claim(IssuerClaim::from("me")) /// .check_claim(TokenIdentifierClaim::from("me")) /// .check_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .check_claim(CustomClaim::try_from(("seats", 4))?) /// .check_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .parse(&token, &public_key)?; /// // we can access all the values from the serde Value object returned by the parser /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) ///``` pub fn parse( &mut self, token: &'a str, key: &'a PasetoAsymmetricPublicKey, ) -> Result { //return the full json value to the user self.parser.parse(token, key) } } #[cfg(feature = "v3_public")] impl<'a> PasetoParser<'a, V3, Public> { /// Given a [PasetoAsymmetricPrivateKey], attempts to verify a signed (V3, Public) PASETO token string and then validate /// claims provided when building the PasetoParser. /// /// Returns a serde_json [Value] with the verified [claims](PasetoClaim). /// /// # Errors /// /// Returns [`GenericParserError`] for any errors when decrypting the encrypted payload or when validating claims. /// /// # Example ///``` ///# #[cfg(all(feature = "prelude", feature="v3_public"))] ///# { /// # use rusty_paseto::prelude::*; /// //obtain a key /// let private_key = Key::<48>::try_from( /// "20347609607477aca8fbfbc5e6218455f3199669792ef8b466faa87bdc67798144c848dd03661eed5ac62461340cea96", /// )?; /// let private_key = PasetoAsymmetricPrivateKey::::from(&private_key); /// let public_key = Key::<49>::try_from( /// "02fbcb7c69ee1c60579be7a334134878d9c5c5bf35d552dab63c0140397ed14cef637d7720925c44699ea30e72874c72fb", /// )?; /// let public_key = PasetoAsymmetricPublicKey::::try_from(&public_key)?; /// let footer = Footer::from("some footer"); /// let implicit_assertion = ImplicitAssertion::from("some assertion"); /// //sign a public V3 token /// let token = PasetoBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .set_implicit_assertion(implicit_assertion) /// .try_sign(&private_key)?; /// //now let's try to verify it /// let json = PasetoParser::::default() /// .set_footer(footer) /// .check_claim(AudienceClaim::from("customers")) /// .set_implicit_assertion(implicit_assertion) /// .check_claim(SubjectClaim::from("loyal subjects")) /// .check_claim(IssuerClaim::from("me")) /// .check_claim(TokenIdentifierClaim::from("me")) /// .check_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .check_claim(CustomClaim::try_from(("seats", 4))?) /// .check_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .parse(&token, &public_key)?; /// // we can access all the values from the serde Value object returned by the parser /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) ///``` pub fn parse( &mut self, token: &'a str, key: &'a PasetoAsymmetricPublicKey, ) -> Result { //return the full json value to the user self.parser.parse(token, key) } } #[cfg(feature = "v4_public")] impl<'a> PasetoParser<'a, V4, Public> { /// Given a [PasetoAsymmetricPrivateKey], attempts to verify a signed (V4, Public) PASETO token string and then validate /// claims provided when building the PasetoParser. /// /// Returns a serde_json [Value] with the verified [claims](PasetoClaim). /// /// # Errors /// /// Returns [`GenericParserError`] for any errors when decrypting the encrypted payload or when validating claims. /// /// # Example ///``` ///# #[cfg(all(feature = "prelude", feature="v4_public"))] ///# { /// # use rusty_paseto::prelude::*; /// //create a key /// let private_key = Key::<64>::try_from("b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; /// let pk: &[u8] = private_key.as_slice(); /// let private_key = PasetoAsymmetricPrivateKey::::from(pk); /// let public_key = Key::<32>::try_from("1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; /// let public_key = PasetoAsymmetricPublicKey::::from(&public_key); /// let footer = Footer::from("some footer"); /// let implicit_assertion = ImplicitAssertion::from("some assertion"); /// //sign a public V4 token /// let token = PasetoBuilder::::default() /// .set_claim(AudienceClaim::from("customers")) /// .set_claim(SubjectClaim::from("loyal subjects")) /// .set_claim(IssuerClaim::from("me")) /// .set_claim(TokenIdentifierClaim::from("me")) /// .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .set_claim(CustomClaim::try_from(("seats", 4))?) /// .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .set_footer(footer) /// .set_implicit_assertion(implicit_assertion) /// .try_sign(&private_key)?; /// //now let's try to verify it /// let json = PasetoParser::::default() /// .set_footer(footer) /// .set_implicit_assertion(implicit_assertion) /// .check_claim(AudienceClaim::from("customers")) /// .check_claim(SubjectClaim::from("loyal subjects")) /// .check_claim(IssuerClaim::from("me")) /// .check_claim(TokenIdentifierClaim::from("me")) /// .check_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(ExpirationClaim::try_from("2019-01-01T00:00:00+00:00")?) /// .check_claim(CustomClaim::try_from(("data", "this is a secret message"))?) /// .check_claim(CustomClaim::try_from(("seats", 4))?) /// .check_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) /// .parse(&token, &public_key)?; /// // we can access all the values from the serde Value object returned by the parser /// assert_eq!(json["aud"], "customers"); /// assert_eq!(json["jti"], "me"); /// assert_eq!(json["iss"], "me"); /// assert_eq!(json["data"], "this is a secret message"); /// assert_eq!(json["exp"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); /// assert_eq!(json["sub"], "loyal subjects"); /// assert_eq!(json["pi to 6 digits"], 3.141526); /// assert_eq!(json["seats"], 4); /// # } /// # Ok::<(),anyhow::Error>(()) ///``` pub fn parse( &mut self, token: &'a str, key: &'a PasetoAsymmetricPublicKey, ) -> Result { //return the full json value to the user self.parser.parse(token, key) } } #[cfg(all(test, feature = "v3_public"))] mod paseto_parser_v3_unit_tests { use std::convert::TryFrom; use crate::prelude::*; use anyhow::Result; #[cfg(feature = "v3_public")] #[test] fn basic_paseto_parser_test_v3_public() -> Result<()> { //setup let public_key = Key::<49>::try_from( "02fbcb7c69ee1c60579be7a334134878d9c5c5bf35d552dab63c0140397ed14cef637d7720925c44699ea30e72874c72fb", )?; let public_key = PasetoAsymmetricPublicKey::::try_from(&public_key)?; let private_key = Key::<48>::try_from( "20347609607477aca8fbfbc5e6218455f3199669792ef8b466faa87bdc67798144c848dd03661eed5ac62461340cea96", )?; let private_key = PasetoAsymmetricPrivateKey::::from(&private_key); //create a default builder let token = PasetoBuilder::::default().build(&private_key)?; //default parser let json = PasetoParser::::default().parse(&token, &public_key)?; //verify the default claims and no others are in the token assert!(json["exp"].is_string()); assert!(json["iat"].is_string()); assert!(json["nbf"].is_string()); assert!(json["sub"].is_null()); assert!(json["iss"].is_null()); assert!(json["jti"].is_null()); assert!(json["aud"].is_null()); assert!(!json["aud"].is_string()); Ok(()) } } #[cfg(all(test, feature = "v2_local"))] mod paseto_parser_unit_tests { use std::convert::TryFrom; use crate::prelude::*; use anyhow::Result; #[cfg(feature="v2_local")] use time::format_description::well_known::Rfc3339; #[cfg(feature="v2_local")] #[test] fn usage_before_ready_test() -> Result<()> { //create a key let key = PasetoSymmetricKey::::from(Key::from(*b"wubbalubbadubdubwubbalubbadubdub")); //let not_before = Utc::now() + Duration::hours(1); let not_before = (time::OffsetDateTime::now_utc() + time::Duration::hours(1)).format(&Rfc3339)?; //create a default builder let token = PasetoBuilder::::default() .set_claim(NotBeforeClaim::try_from(not_before)?) .build(&key)?; let expected_error = format!( "{}", PasetoParser::::default().parse(&token, &key).unwrap_err() ); assert!(expected_error.starts_with("The token cannot be used before ")); Ok(()) } #[cfg(feature="v2_local")] #[test] fn non_expiring_token_claim_test() -> Result<()> { //create a key let key = PasetoSymmetricKey::::from(Key::from(*b"wubbalubbadubdubwubbalubbadubdub")); //we're going to set a token expiration date to 10 minutes ago let expired = (time::OffsetDateTime::now_utc() + time::Duration::minutes(-10)).format(&Rfc3339)?; //create a default builder let token = PasetoBuilder::::default() //setting our claim .set_claim(ExpirationClaim::try_from(expired)?) //by setting this we ensure we won't fail .set_no_expiration_danger_acknowledged() //without the line above this would have errored as an expired token .build(&key)?; let token = PasetoParser::::default().parse(&token, &key)?; assert!(token["iat"].is_string()); assert!(token["exp"].is_null()); Ok(()) } #[cfg(feature="v2_local")] #[test] fn expired_token_claim_test() -> Result<()> { //create a key let key = PasetoSymmetricKey::::from(Key::from(*b"wubbalubbadubdubwubbalubbadubdub")); let expired = (time::OffsetDateTime::now_utc() + time::Duration::minutes(-10)).format(&Rfc3339)?; //create a default builder let token = PasetoBuilder::::default() .set_claim(ExpirationClaim::try_from(expired)?) .build(&key)?; let expected_error = format!( "{}", PasetoParser::::default().parse(&token, &key).unwrap_err() ); assert_eq!(expected_error, "This token is expired"); Ok(()) } #[cfg(feature="v2_public")] #[test] fn basic_paseto_parser_test_v2_public() -> Result<()> { //setup let public_key = Key::<32>::try_from("1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; let public_key = PasetoAsymmetricPublicKey::::from(&public_key); let private_key = Key::<64>::try_from( "b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2" )?; let private_key = PasetoAsymmetricPrivateKey::::from(&private_key); //create a default builder let token = PasetoBuilder::::default().build(&private_key)?; //default parser let json = PasetoParser::::default().parse(&token, &public_key)?; //verify the default claims and no others are in the token assert!(json["exp"].is_string()); assert!(json["iat"].is_string()); assert!(json["nbf"].is_string()); assert!(json["sub"].is_null()); assert!(json["iss"].is_null()); assert!(json["jti"].is_null()); assert!(json["aud"].is_null()); assert!(!json["aud"].is_string()); Ok(()) } #[cfg(feature="v2_local")] #[test] fn github_issue_29_test() -> Result<()> { //create a key let key = PasetoSymmetricKey::::from(Key::from(*b"wubbalubbadubdubwubbalubbadubdub")); //create a default builder let bad_token = "v4.local.1234"; //default parser let json = PasetoParser::::default().parse(&bad_token, &key); assert!(json.is_err()); Ok(()) } #[cfg(feature="v2_local")] #[test] fn basic_paseto_parser_test() -> Result<()> { //create a key let key = PasetoSymmetricKey::::from(Key::from(*b"wubbalubbadubdubwubbalubbadubdub")); //create a default builder let token = PasetoBuilder::::default().build(&key)?; //default parser let json = PasetoParser::::default().parse(&token, &key)?; //verify the default claims and no others are in the token assert!(json["exp"].is_string()); assert!(json["iat"].is_string()); assert!(json["nbf"].is_string()); assert!(json["sub"].is_null()); assert!(json["iss"].is_null()); assert!(json["jti"].is_null()); assert!(json["aud"].is_null()); assert!(!json["aud"].is_string()); Ok(()) } #[cfg(feature="v2_local")] #[test] fn update_default_issued_at_claim_test() -> Result<()> { //create a key let key = PasetoSymmetricKey::::from(Key::from(*b"wubbalubbadubdubwubbalubbadubdub")); let tomorrow = (time::OffsetDateTime::now_utc() + time::Duration::days(1)).format(&Rfc3339)?; //create a builder, with default IssuedAtClaim let token = PasetoBuilder::::default() .set_claim(IssuedAtClaim::try_from(tomorrow).unwrap()) .build(&key)?; //now let's decrypt the token and verify the values //the IssuedAtClaim should exist and the date should be set to tomorrow PasetoParser::::default() .validate_claim(IssuedAtClaim::default(), &|key, value| { //let's get the value let val = value.as_str().ok_or(PasetoClaimError::Unexpected(key.to_string()))?; let datetime = iso8601::datetime(val).unwrap(); //let tomorrow = Utc::now() + Duration::days(1); let tomorrow = (time::OffsetDateTime::now_utc() + time::Duration::days(1)) .date() .to_string(); //the claimm should exist assert_eq!(key, "iat"); //date should be tomorrow assert_eq!(datetime.date.to_string(), tomorrow); Ok(()) }) .parse(&token, &key)?; Ok(()) } #[cfg(feature="v2_local")] #[test] fn check_for_default_issued_at_claim_test() -> Result<()> { //create a key let key = PasetoSymmetricKey::::from(Key::from(*b"wubbalubbadubdubwubbalubbadubdub")); //create a builder, with default IssuedAtClaim let token = PasetoBuilder::::default().build(&key)?; //now let's decrypt the token and verify the values //the IssuedAtClaim should exist PasetoParser::::default() .validate_claim(IssuedAtClaim::default(), &|key, value| { //let's get the value let val = value.as_str().ok_or(PasetoClaimError::Unexpected(key.to_string()))?; let datetime = iso8601::datetime(val).unwrap(); //the claimm should exist let now = time::OffsetDateTime::now_utc().date().to_string(); assert_eq!(key, "iat"); //date should be today assert_eq!(datetime.date.to_string(), now); Ok(()) }) .parse(&token, &key)?; Ok(()) } #[cfg(feature="v2_local")] #[test] fn update_default_expiration_claim_test() -> Result<()> { //create a key let key = PasetoSymmetricKey::::from(Key::from(*b"wubbalubbadubdubwubbalubbadubdub")); let in_4_days = (time::OffsetDateTime::now_utc() + time::Duration::days(4)).format(&Rfc3339)?; //create a builder, with default IssuedAtClaim let token = PasetoBuilder::::default() .set_claim(ExpirationClaim::try_from(in_4_days).unwrap()) .build(&key)?; //now let's decrypt the token and verify the values //the IssuedAtClaim should exist and the date should be set to tomorrow PasetoParser::::default() .validate_claim(ExpirationClaim::default(), &|key, value| { //let's get the value let val = value.as_str().ok_or(PasetoClaimError::Unexpected(key.to_string()))?; let datetime = iso8601::datetime(val).unwrap(); //let in_4_days = Utc::now() + Duration::days(4); let in_4_days = (time::OffsetDateTime::now_utc() + time::Duration::days(4)) .date() .to_string(); //the claimm should exist assert_eq!(key, "exp"); //date should be tomorrow assert_eq!(datetime.date.to_string(), in_4_days); Ok(()) }) .parse(&token, &key)?; Ok(()) } #[cfg(feature="v2_local")] #[test] fn check_for_default_expiration_claim_test() -> Result<()> { //create a key let key = PasetoSymmetricKey::::from(Key::from(*b"wubbalubbadubdubwubbalubbadubdub")); //create a builder, with default ExpirationClaim let token = PasetoBuilder::::default().build(&key)?; //now let's decrypt the token and verify the values //the IssuedAtClaim should exist PasetoParser::::default() .validate_claim(ExpirationClaim::default(), &|key, value| { //let's get the value let val = value.as_str().ok_or(PasetoClaimError::Unexpected(key.to_string()))?; let datetime = iso8601::datetime(val).unwrap(); let in_an_hour = (time::OffsetDateTime::now_utc() + time::Duration::hours(1)) .time() .hour() .to_string(); //the claimm should exist assert_eq!(key, "exp"); //date should be today assert_eq!(datetime.time.hour.to_string(), in_an_hour); Ok(()) }) .parse(&token, &key)?; Ok(()) } #[cfg(feature="v2_local")] #[test] fn full_paseto_parser_test() -> Result<()> { //create a key let key = PasetoSymmetricKey::::from(Key::from(*b"wubbalubbadubdubwubbalubbadubdub")); let footer = Footer::from("some footer"); //create a builder, add some claims and then build the token with the key let token = PasetoBuilder::::default() .set_claim(AudienceClaim::from("customers")) .set_claim(SubjectClaim::from("loyal subjects")) .set_claim(IssuerClaim::from("me")) .set_claim(TokenIdentifierClaim::from("me")) .set_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) .set_claim(ExpirationClaim::try_from("2050-01-01T00:00:00+00:00")?) .set_claim(CustomClaim::try_from(("data", "this is a secret message"))?) .set_claim(CustomClaim::try_from(("seats", 4))?) .set_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) .set_footer(footer) .build(&key)?; //now let's decrypt the token and verify the values let json = PasetoParser::::default() .check_claim(AudienceClaim::from("customers")) .check_claim(SubjectClaim::from("loyal subjects")) .check_claim(IssuerClaim::from("me")) .check_claim(TokenIdentifierClaim::from("me")) .check_claim(IssuedAtClaim::try_from("2019-01-01T00:00:00+00:00")?) .check_claim(NotBeforeClaim::try_from("2019-01-01T00:00:00+00:00")?) .check_claim(ExpirationClaim::try_from("2050-01-01T00:00:00+00:00")?) .check_claim(CustomClaim::try_from(("data", "this is a secret message"))?) .check_claim(CustomClaim::try_from(("seats", 4))?) .check_claim(CustomClaim::try_from(("pi to 6 digits", 3.141526))?) .set_footer(footer) .parse(&token, &key)?; // we can access all the values from the serde Value object returned by the parser assert_eq!(json["aud"], "customers"); assert_eq!(json["jti"], "me"); assert_eq!(json["iss"], "me"); assert_eq!(json["data"], "this is a secret message"); assert_eq!(json["exp"], "2050-01-01T00:00:00+00:00"); assert_eq!(json["iat"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["nbf"], "2019-01-01T00:00:00+00:00"); assert_eq!(json["sub"], "loyal subjects"); assert_eq!(json["pi to 6 digits"], 3.141526); assert_eq!(json["seats"], 4); Ok(()) } } rusty_paseto-0.7.1/tests/build_payload_from_claims_prop_test.proptest-regressions000064400000000000000000000006641046102023000271670ustar 00000000000000# Seeds for failure cases proptest has generated in the past. It is # automatically read and these particular cases re-run before any # novel cases are generated. # # It is recommended to check this file in to source control so that # everyone who runs the test benefits from these saved cases. cc fcb4492f76b8f1e307be0b2be5e8d5fd157cbb850ad73a9b73af634f3fdf843a # shrinks to claims = {"A": Object {"A": Number(5.155313317789283e132)}} rusty_paseto-0.7.1/tests/build_payload_from_claims_prop_test.rs000064400000000000000000000215451046102023000233730ustar 00000000000000/*! * Property Tests for build_payload_from_claims Function * * This file contains property tests designed to validate the correctness and robustness * of the `build_payload_from_claims` function. The `build_payload_from_claims` function * is responsible for constructing JSON payloads from claims, ensuring they are serialized * and wrapped correctly. * * The primary goals of these tests are: * 1. **Validation**: Ensure the `build_payload_from_claims` function correctly handles different types of claims. * 2. **Robustness**: Identify and address potential edge cases that may not be covered by unit tests. * 3. **Consistency**: Verify that the function maintains the expected structure and behavior for all inputs. * * ## Test Strategy * * The property tests leverage the `proptest` crate to generate a wide range of claims, * including nested structures. The generated claims are then passed to the `build_payload_from_claims` * function, and the resulting payloads are compared against the expected outcomes. * * ## Key Test Scenarios * * - **Null Values**: Ensure null values remain null and are not wrapped unnecessarily. * - **Empty Objects**: Verify that empty maps are wrapped as empty JSON objects. * - **Primitive Values**: Confirm that primitive values (e.g., strings, numbers) remain unchanged. * - **Arrays**: Ensure arrays, including empty arrays, are wrapped correctly and consistently. * - **Nested Structures**: Validate the recursive wrapping and serialization of nested JSON objects and arrays. * * ## Findings * * - The `build_payload_from_claims` function correctly handles most input values and passes the associated unit tests. * - A specific floating-point corner case was identified during the testing process. This case involves minor * discrepancies in floating-point precision, which is a common issue in many systems. The identified corner * case has been documented and is not critical for most practical use cases. * * ## Conclusion * * The property tests demonstrate that the `build_payload_from_claims` function is robust and reliable for most practical * use cases. While a specific floating-point corner case remains, the function's behavior is consistent with the expected * outcomes for a wide range of input values. * * To run these tests, use the following command: * * ```sh * cargo test -- --ignored * ``` * * This approach ensures comprehensive validation of the `build_payload_from_claims` function, contributing to the overall * stability and reliability of the system. */ use std::collections::HashMap; use proptest::prelude::*; use erased_serde::Serialize; use serde_json::{Map, Number, Value}; // Define a strategy to generate arbitrary JSON values fn arb_json() -> impl Strategy { let leaf = prop_oneof![ Just(Json::Null), any::().prop_map(Json::Bool), any::().prop_map(Json::Number), "[a-zA-Z0-9_]+".prop_map(Json::String), ]; leaf.prop_recursive( 3, // 3 levels deep 64, // Shoot for maximum size of 64 nodes 10, // We put up to 10 items per collection |inner| prop_oneof![ prop::collection::vec(inner.clone(), 0..10).prop_map(Json::Array), prop::collection::hash_map("[a-zA-Z_][a-zA-Z0-9_]*", inner, 0..10).prop_map(Json::Map), ], ) } #[derive(Clone, Debug)] enum Json { Null, Bool(bool), Number(f64), String(String), Array(Vec), Map(HashMap), } // Convert our custom Json enum to serde_json::Value impl From for Value { fn from(json: Json) -> Self { match json { Json::Null => Value::Null, Json::Bool(b) => Value::Bool(b), Json::Number(n) => Value::Number(Number::from_f64(n).unwrap()), Json::String(s) => Value::String(s), Json::Array(arr) => Value::Array(arr.into_iter().map(Value::from).collect()), Json::Map(map) => Value::Object(map.into_iter().map(|(k, v)| (k, Value::from(v))).collect()), } } } // Wrap claims in an outer JSON object to ensure proper nesting fn wrap_claims(claims: HashMap) -> Value { let wrapped: HashMap = claims .into_iter() .map(|(k, v)| (k, wrap_value(v))) .collect(); Value::Object(Map::from_iter(wrapped)) } // Recursively wrap values to ensure all values are valid JSON objects fn wrap_value(value: Value) -> Value { match value { Value::Object(map) => { if map.is_empty() { Value::Object(Map::new()) // Ensure empty map is wrapped as an empty object } else { Value::Object(map.into_iter().map(|(k, v)| (k, wrap_value(v))).collect()) } } Value::Array(arr) => Value::Array(arr.into_iter().map(wrap_value).collect()), Value::Null => Value::Null, // Do not wrap null values other => Value::Object(Map::from_iter(vec![("value".to_string(), other)])), // Wrap primitive values } } // Define a strategy to generate arbitrary claims with valid JSON string keys fn claim_strategy() -> impl Strategy> { prop::collection::hash_map("[a-zA-Z_][a-zA-Z0-9_]*", arb_json().prop_map(Value::from), 1..10) } // Simulated GenericBuilder structure struct SimulatedGenericBuilder { claims: HashMap>, } impl SimulatedGenericBuilder { pub fn new() -> Self { Self { claims: HashMap::new(), } } pub fn extend_claims(&mut self, claims: HashMap>) { self.claims.extend(claims); } pub fn build_payload_from_claims(&mut self) -> Result { let claims = std::mem::take(&mut self.claims); let serialized_claims: HashMap = claims .into_iter() .map(|(k, v)| (k, serde_json::to_value(v).unwrap_or(Value::Null))) .collect(); let wrapped_claims = wrap_claims(serialized_claims); serde_json::to_string(&wrapped_claims) } } // Custom function to compare JSON values with tolerance for floating-point numbers fn compare_json_values(a: &Value, b: &Value) -> bool { match (a, b) { (Value::Number(a_num), Value::Number(b_num)) => { let a_f64 = a_num.as_f64().unwrap(); let b_f64 = b_num.as_f64().unwrap(); (a_f64 - b_f64).abs() < 1e-10 // Tolerance for floating-point comparison } (Value::Object(a_map), Value::Object(b_map)) => { if a_map.len() != b_map.len() { return false; } for (key, a_value) in a_map { if let Some(b_value) = b_map.get(key) { if !compare_json_values(a_value, b_value) { return false; } } else { return false; } } true } (Value::Array(a_arr), Value::Array(b_arr)) => { if a_arr.len() != b_arr.len() { return false; } for (a_value, b_value) in a_arr.iter().zip(b_arr.iter()) { if !compare_json_values(a_value, b_value) { return false; } } true } _ => a == b, } } proptest! { #[test] #[ignore] fn test_build_payload_from_claims(claims in claim_strategy()) { // Debug print to check the generated claims println!("Generated claims: {:?}", claims); let wrapped_claims = wrap_claims(claims.clone()); println!("Wrapped claims: {:?}", wrapped_claims); let mut builder = SimulatedGenericBuilder::new(); builder.extend_claims(claims.clone().into_iter().map(|(k, v)| (k, Box::new(v) as Box)).collect()); let payload_result = builder.build_payload_from_claims(); // Check if payload is built successfully prop_assert!(payload_result.is_ok(), "Failed to build payload: {:?}", payload_result); let payload = payload_result.unwrap(); println!("Generated payload: {}", payload); let payload_value: Value = serde_json::from_str(&payload).expect("Payload should be valid JSON"); // Check if all claims are present in the payload for (key, _) in claims { let expected_value = wrapped_claims.get(&key).unwrap(); let actual_value = payload_value.get(&key).unwrap(); prop_assert!(compare_json_values(expected_value, actual_value), "Key '{}' not found or value mismatch: expected {:?}, got {:?}", key, expected_value, actual_value); } } } rusty_paseto-0.7.1/tests/generic_claims_wrap_value_prop_test.proptest-regressions000064400000000000000000000011201046102023000271610ustar 00000000000000# Seeds for failure cases proptest has generated in the past. It is # automatically read and these particular cases re-run before any # novel cases are generated. # # It is recommended to check this file in to source control so that # everyone who runs the test benefits from these saved cases. cc d900fc433a21df07d9701323e3c92c90b64e205d311663fa6385d19609329102 # shrinks to input = Map({}) cc 96f261d316383697b99b1fb42233ad10c27f812b3fd2f3c2b0337ef3ee85e246 # shrinks to input = Array([]) cc 6903a0c80fa97eb92ccdd54c8087b1b96ce86dfc3d429e4965f20c2a892c058d # shrinks to input = Bool(false) rusty_paseto-0.7.1/tests/generic_claims_wrap_value_prop_test.rs000064400000000000000000000136041046102023000233760ustar 00000000000000/*! * Property Tests for wrap_value Function * * This file contains property tests designed to validate the correctness and robustness * of the `wrap_value` function. The `wrap_value` function ensures that all values are * recursively wrapped to maintain valid JSON objects, handling various edge cases * including empty objects, arrays, and null values. * * The primary goals of these tests are: * 1. **Validation**: Ensure the `wrap_value` function correctly handles different types of JSON values. * 2. **Robustness**: Identify and address potential edge cases that may not be covered by unit tests. * 3. **Consistency**: Verify that the function maintains the expected structure and behavior for all inputs. * * ## Test Strategy * * The property tests leverage the `proptest` crate to generate a wide range of JSON values, * including nested structures. The generated values are then passed to the `wrap_value` * function, and the resulting wrapped values are compared against the expected outcomes. * * ## Key Test Scenarios * * - **Null Values**: Ensure null values remain null and are not wrapped unnecessarily. * - **Empty Objects**: Verify that empty maps are wrapped as empty JSON objects. * - **Primitive Values**: Confirm that primitive values (e.g., strings, numbers) remain unchanged. * - **Arrays**: Ensure arrays, including empty arrays, are wrapped correctly and consistently. * - **Nested Structures**: Validate the recursive wrapping of nested JSON objects and arrays. * * ## Findings * * - The `wrap_value` function correctly handles a wide range of input values, passing all property tests. * - No significant corner cases were identified during the testing process, indicating that the function * is robust and reliable for most practical use cases. * * ## Conclusion * * The property tests demonstrate that the `wrap_value` function is robust and reliable for most practical * use cases. The function's behavior is consistent with the expected outcomes for a wide range of input values. * * This approach ensures comprehensive validation of the `wrap_value` function, contributing to the overall * stability and reliability of the system. */ use std::collections::HashMap; use proptest::prelude::*; use serde_json::{Map, Value}; // Define a strategy to generate arbitrary JSON values fn arb_json() -> impl Strategy { let leaf = prop_oneof![ Just(Json::Null), any::().prop_map(Json::Bool), any::().prop_map(Json::Number), "[a-zA-Z0-9_]+".prop_map(Json::String), ]; leaf.prop_recursive( 3, // 3 levels deep 64, // Shoot for maximum size of 64 nodes 10, // We put up to 10 items per collection |inner| prop_oneof![ prop::collection::vec(inner.clone(), 0..10).prop_map(Json::Array), prop::collection::hash_map("[a-zA-Z_][a-zA-Z0-9_]*", inner, 0..10).prop_map(Json::Map), ], ) } #[derive(Clone, Debug)] enum Json { Null, Bool(bool), Number(f64), String(String), Array(Vec), Map(HashMap), } // Convert our custom Json enum to serde_json::Value impl From for Value { fn from(json: Json) -> Self { match json { Json::Null => Value::Null, Json::Bool(b) => Value::Bool(b), Json::Number(n) => Value::Number(serde_json::Number::from_f64(n).unwrap()), Json::String(s) => Value::String(s), Json::Array(arr) => Value::Array(arr.into_iter().map(Value::from).collect()), Json::Map(map) => Value::Object(map.into_iter().map(|(k, v)| (k, Value::from(v))).collect()), } } } fn wrap_value(value: Value) -> Value { match value { Value::Object(map) => { if map.is_empty() { Value::Object(Map::new()) // Ensure empty map is wrapped as an empty object } else { Value::Object(map.into_iter().map(|(k, v)| (k, wrap_value(v))).collect()) } } Value::Array(arr) => Value::Array(arr.into_iter().map(wrap_value).collect()), Value::Null => Value::Null, // Do not wrap null values other => other, // Do not wrap primitive values } } proptest! { #[test] fn test_wrap_value(input in arb_json()) { let value: Value = input.into(); let wrapped_value = wrap_value(value.clone()); // Ensure null values remain null if let Value::Null = value { prop_assert_eq!(wrapped_value, Value::Null); } else if let Value::Object(map) = &value { if map.is_empty() { prop_assert_eq!(wrapped_value, Value::Object(Map::new())); } else { // For non-empty maps, ensure they are wrapped correctly for (k, v) in map { let wrapped_sub_value = wrapped_value.get(k).expect("Key should exist in wrapped map"); let expected_sub_value = wrap_value(v.clone()); prop_assert_eq!(wrapped_sub_value, &expected_sub_value, "Key '{}' not wrapped correctly", k); } } } else if let Value::Array(arr) = &value { // Ensure arrays are wrapped correctly, including empty arrays for (original, wrapped) in arr.iter().zip(wrapped_value.as_array().expect("Wrapped value should be an array")) { let expected_sub_value = wrap_value(original.clone()); prop_assert_eq!(wrapped, &expected_sub_value, "Array element not wrapped correctly"); } if arr.is_empty() { prop_assert_eq!(wrapped_value, Value::Array(vec![])); } } else { // For other values, ensure they remain unchanged prop_assert_eq!(wrapped_value, value); } } }rusty_paseto-0.7.1/tests/v1_public_test_vectors_private_key.der000064400000000000000000000023011046102023000233160ustar 000000000000000‚˝0  *†H†÷ ‚§0‚Ł‚ɤŕNŢwŚéäaŕ–Ă>aEő—I4đЎÁí Z‹=(Í'{ŰyÂm7Zďƒ‡űóÂO!ź1*ƒ§‰1ن 7›k=Ąt{Ńş=MÓaçj|E-o ˜śŕ`ďŇe‡Öó<Ȱ\ť–5:ÝÄ0Ă]/p!íM'{vĆI”Ôĺz˛CPńw62 ™Eë ZG•´&pĽTnP’w˘őkĺő´şfy"“q‘ŔŔƒ#y2A˙@¸t˜NoÁŘĄ8&ÝWŚĽS(J _ľĂńVčuœ§ňFÖB‚đ3ȉÖ{đężÖÎ@6xšy NąuA(nüfÇ<Ł‚]śŒ°ÚߌŠv{7Š÷{ś‚Ü>aG02~€Î÷ýŠĎ•ÉąéăL|Cebš¨Ň?ěC^^ň+ԓBhYŇyÝ÷‹úĐż oo ‚ †źLÚA° W€ď’UŹ*&ߎń>Cď˙ÓVJ+C˛G&5÷/Ôh=CšƒńpҤHfďa’T!Őć>pĺć/xœĆ{'•@˙ý$˝Ť}ŃÄ]˛ćˆ–ŐçjqĄ[ţË×& oĹQĚV‡Ĺ”Â9őhĺ3ĆAűE˙<ř~Öv(ŤŹp¨˝(<+Că…oë42Ţ;Ř"sšĺěG­E[űă@ą}°SéVIă`#şç?Â^ Ĺ}Ÿ \#Ŕ9ë˘ó.G4;nßF‘řs—°çD!š{4=8ů—Gšä‚ôńńUa8=┓Ít`–ţ hó ÎgNźÚ~˛Ăšň‰Ş*)ţˆ…ŹőËHş9(kŤ ‰âř3˘\ĎĂsy#ÁItş&oŃd­_8â/ˆň2—!ß9đm˝A~€LGŤeNrˆ(Źp§ľŢ'‰Ľ‚Ť™ŢćÁKřL yW5Ě?rVąp7Ľ ŮÄ&rNäzĐpYѸÇţź&Mďítý—U ľoşćý~p†ŽcZÖľY„˝hţŇąćŰĚÚ̉֯yżł]ĘH…žŠ˛ mΖĎC€lQ—Pęą˙!ľ÷ë‚6z”ëČsŽĎ­(Vşˆ•ůuŔŕ3TÔ&GPW¨Ë°¨Ďł…mččDĘ縳,“M‘ÜĎ  š˘Nâ|˛œFiŐş9ůäŹÔ‰…ĄiĄ‚|P)(aŽ}ýlłč(q_aŘýCďÍR8…ă=A€.×A}U‰š Šow@ ×7KHŁű 6¨ĽAÁäoٌü F8‹ťd–ŸblaŔ̕ÔTĚŘ• œ č)lŽ›œ˙ϖÉÇňqÖ ł_u!ęňăŚ79ąţ ßÔňÉínŘŐЙ“đœ}×,*'JŻěŕ­K&ŐÜĄqŃœCÉÎn-ćăNČŞä gÄQLžœ1żJVďnǟÜ.hë8QˇŹ |&ĽĂ1”ěŘ\+@ŤjI—ŽŹ,vEڌÉ™şo؛8w¨UvÍ Ë"Ľ1lHšTŁö¨ëXEíAý\‘ŕt]–N¸†ŕfxéŮ#÷ńĚö‹Ý?B2rusty_paseto-0.7.1/tests/v1_public_test_vectors_private_key.pk8000064400000000000000000000023011046102023000232460ustar 000000000000000‚˝0  *†H†÷ ‚§0‚Ł‚ɤŕNŢwŚéäaŕ–Ă>aEő—I4đЎÁí Z‹=(Í'{ŰyÂm7Zďƒ‡űóÂO!ź1*ƒ§‰1ن 7›k=Ąt{Ńş=MÓaçj|E-o ˜śŕ`ďŇe‡Öó<Ȱ\ť–5:ÝÄ0Ă]/p!íM'{vĆI”Ôĺz˛CPńw62 ™Eë ZG•´&pĽTnP’w˘őkĺő´şfy"“q‘ŔŔƒ#y2A˙@¸t˜NoÁŘĄ8&ÝWŚĽS(J _ľĂńVčuœ§ňFÖB‚đ3ȉÖ{đężÖÎ@6xšy NąuA(nüfÇ<Ł‚]śŒ°ÚߌŠv{7Š÷{ś‚Ü>aG02~€Î÷ýŠĎ•ÉąéăL|Cebš¨Ň?ěC^^ň+ԓBhYŇyÝ÷‹úĐż oo ‚ †źLÚA° W€ď’UŹ*&ߎń>Cď˙ÓVJ+C˛G&5÷/Ôh=CšƒńpҤHfďa’T!Őć>pĺć/xœĆ{'•@˙ý$˝Ť}ŃÄ]˛ćˆ–ŐçjqĄ[ţË×& oĹQĚV‡Ĺ”Â9őhĺ3ĆAűE˙<ř~Öv(ŤŹp¨˝(<+Că…oë42Ţ;Ř"sšĺěG­E[űă@ą}°SéVIă`#şç?Â^ Ĺ}Ÿ \#Ŕ9ë˘ó.G4;nßF‘řs—°çD!š{4=8ů—Gšä‚ôńńUa8=┓Ít`–ţ hó ÎgNźÚ~˛Ăšň‰Ş*)ţˆ…ŹőËHş9(kŤ ‰âř3˘\ĎĂsy#ÁItş&oŃd­_8â/ˆň2—!ß9đm˝A~€LGŤeNrˆ(Źp§ľŢ'‰Ľ‚Ť™ŢćÁKřL yW5Ě?rVąp7Ľ ŮÄ&rNäzĐpYѸÇţź&Mďítý—U ľoşćý~p†ŽcZÖľY„˝hţŇąćŰĚÚ̉֯yżł]ĘH…žŠ˛ mΖĎC€lQ—Pęą˙!ľ÷ë‚6z”ëČsŽĎ­(Vşˆ•ůuŔŕ3TÔ&GPW¨Ë°¨Ďł…mččDĘ縳,“M‘ÜĎ  š˘Nâ|˛œFiŐş9ůäŹÔ‰…ĄiĄ‚|P)(aŽ}ýlłč(q_aŘýCďÍR8…ă=A€.×A}U‰š Šow@ ×7KHŁű 6¨ĽAÁäoٌü F8‹ťd–ŸblaŔ̕ÔTĚŘ• œ č)lŽ›œ˙ϖÉÇňqÖ ł_u!ęňăŚ79ąţ ßÔňÉínŘŐЙ“đœ}×,*'JŻěŕ­K&ŐÜĄqŃœCÉÎn-ćăNČŞä gÄQLžœ1żJVďnǟÜ.hë8QˇŹ |&ĽĂ1”ěŘ\+@ŤjI—ŽŹ,vEڌÉ™şo؛8w¨UvÍ Ë"Ľ1lHšTŁö¨ëXEíAý\‘ŕt]–N¸†ŕfxéŮ#÷ńĚö‹Ý?B2rusty_paseto-0.7.1/tests/v1_public_test_vectors_public_key.der000064400000000000000000000004161046102023000231270ustar 000000000000000‚ ‚ɤŕNŢwŚéäaŕ–Ă>aEő—I4đЎÁí Z‹=(Í'{ŰyÂm7Zďƒ‡űóÂO!ź1*ƒ§‰1ن 7›k=Ąt{Ńş=MÓaçj|E-o ˜śŕ`ďŇe‡Öó<Ȱ\ť–5:ÝÄ0Ă]/p!íM'{vĆI”Ôĺz˛CPńw62 ™Eë ZG•´&pĽTnP’w˘őkĺő´şfy"“q‘ŔŔƒ#y2A˙@¸t˜NoÁŘĄ8&ÝWŚĽS(J _ľĂńVčuœ§ňFÖB‚đ3ȉÖ{đężÖÎ@6xšy NąuA(nüfÇ<Łrusty_paseto-0.7.1/tests/version1_test_vectors.rs000064400000000000000000000373171046102023000204670ustar 00000000000000#[cfg(all(test, feature = "v1"))] mod v1_test_vectors { use anyhow::Result; use rusty_paseto::core::*; use serde_json::json; #[cfg(feature = "v1_local")] #[test] fn test_1_e_1() -> Result<()> { //setup //let key = Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?; let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a signed message", "exp":"2019-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); // // //create a public V1 token let token = Paseto::::builder() .set_payload(payload) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v1.local.WzhIh1MpbqVNXNt7-HbWvL-JwAym3Tomad9Pc2nl7wK87vGraUVvn2bs8BBNo7jbukCNrkVID0jCK2vr5bP18G78j1bOTbBcP9HZzqnraEdspcjd_PvrxDEhj9cS2MG5fmxtvuoHRp3M24HvxTtql9z26KTfPWxJN5bAJaAM6gos8fnfjJO8oKiqQMaiBP_Cqncmqw8"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, None)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "v1_local")] #[test] fn test_1_e_2() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a secret message", "exp":"2019-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); // // //create a public V1 token let token = Paseto::::builder() .set_payload(payload) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v1.local.w_NOpjgte4bX-2i1JAiTQzHoGUVOgc2yqKqsnYGmaPaCu_KWUkRGlCRnOvZZxeH4HTykY7AE_jkzSXAYBkQ1QnwvKS16uTXNfnmp8IRknY76I2m3S5qsM8klxWQQKFDuQHl8xXV0MwAoeFh9X6vbwIqrLlof3s4PMjRDwKsxYzkMr1RvfDI8emoPoW83q4Q60_xpHaw"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, None)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "v1_local")] #[test] fn test_1_e_3() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("26f7553354482a1d91d4784627854b8da6b8042a7966523c2b404e8dbbe7f7f2")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a signed message", "exp":"2019-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); // // //create a public V1 token let token = Paseto::::builder() .set_payload(payload) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v1.local.4VyfcVcFAOAbB8yEM1j1Ob7Iez5VZJy5kHNsQxmlrAwKUbOtq9cv39T2fC0MDWafX0nQJ4grFZzTdroMvU772RW-X1oTtoFBjsl_3YYHWnwgqzs0aFc3ejjORmKP4KUM339W3syBYyjKIOeWnsFQB6Yef-1ov9rvqt7TmwONUHeJUYk4IK_JEdUeo_uFRqAIgHsiGCg"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, None)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "v1_local")] #[test] fn test_1_e_4() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("26f7553354482a1d91d4784627854b8da6b8042a7966523c2b404e8dbbe7f7f2")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a secret message", "exp":"2019-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); //let footer = Footer::from("{\"kid\":\"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN\"}"); // // //create a public V1 token let token = Paseto::::builder() .set_payload(payload) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v1.local.IddlRQmpk6ojcD10z1EYdLexXvYiadtY0MrYQaRnq3dnqKIWcbbpOcgXdMIkm3_3gksirTj81bvWrWkQwcUHilt-tQo7LZK8I6HCK1V78B9YeEqGNeeWXOyWWHoJQIe0d5nTdvejdt2Srz_5Q0QG4oiz1gB_wmv4U5pifedaZbHXUTWXchFEi0etJ4u6tqgxZSklcec"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, None)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "v1_local")] #[test] fn test_1_e_5() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("26f7553354482a1d91d4784627854b8da6b8042a7966523c2b404e8dbbe7f7f2")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a signed message", "exp":"2019-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); let footer = json!({"kid":"UbkK8Y6iv4GZhFp6Tx3IWLWLfNXSEvJcdT3zdR65YZxo"}).to_string(); let footer = footer.as_str(); let footer = Footer::from(footer); // // //create a public V1 token let token = Paseto::::builder() .set_payload(payload) .set_footer(footer) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v1.local.4VyfcVcFAOAbB8yEM1j1Ob7Iez5VZJy5kHNsQxmlrAwKUbOtq9cv39T2fC0MDWafX0nQJ4grFZzTdroMvU772RW-X1oTtoFBjsl_3YYHWnwgqzs0aFc3ejjORmKP4KUM339W3szA28OabR192eRqiyspQ6xPM35NMR-04-FhRJZEWiF0W5oWjPVtGPjeVjm2DI4YtJg.eyJraWQiOiJVYmtLOFk2aXY0R1poRnA2VHgzSVdMV0xmTlhTRXZKY2RUM3pkUjY1WVp4byJ9"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, footer)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "v1_local")] #[test] fn test_1_e_6() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("26f7553354482a1d91d4784627854b8da6b8042a7966523c2b404e8dbbe7f7f2")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a secret message", "exp":"2019-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); let footer = json!({"kid":"UbkK8Y6iv4GZhFp6Tx3IWLWLfNXSEvJcdT3zdR65YZxo"}).to_string(); let footer = footer.as_str(); let footer = Footer::from(footer); // // //create a public V1 token let token = Paseto::::builder() .set_payload(payload) .set_footer(footer) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v1.local.IddlRQmpk6ojcD10z1EYdLexXvYiadtY0MrYQaRnq3dnqKIWcbbpOcgXdMIkm3_3gksirTj81bvWrWkQwcUHilt-tQo7LZK8I6HCK1V78B9YeEqGNeeWXOyWWHoJQIe0d5nTdvcT2vnER6NrJ7xIowvFba6J4qMlFhBnYSxHEq9v9NlzcKsz1zscdjcAiXnEuCHyRSc.eyJraWQiOiJVYmtLOFk2aXY0R1poRnA2VHgzSVdMV0xmTlhTRXZKY2RUM3pkUjY1WVp4byJ9"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, footer)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "v1_local")] #[test] fn test_1_e_7() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("26f7553354482a1d91d4784627854b8da6b8042a7966523c2b404e8dbbe7f7f2")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a signed message", "exp":"2019-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); let footer = json!({"kid":"UbkK8Y6iv4GZhFp6Tx3IWLWLfNXSEvJcdT3zdR65YZxo"}).to_string(); let footer = footer.as_str(); let footer = Footer::from(footer); // // //create a public V1 token let token = Paseto::::builder() .set_payload(payload) .set_footer(footer) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v1.local.4VyfcVcFAOAbB8yEM1j1Ob7Iez5VZJy5kHNsQxmlrAwKUbOtq9cv39T2fC0MDWafX0nQJ4grFZzTdroMvU772RW-X1oTtoFBjsl_3YYHWnwgqzs0aFc3ejjORmKP4KUM339W3szA28OabR192eRqiyspQ6xPM35NMR-04-FhRJZEWiF0W5oWjPVtGPjeVjm2DI4YtJg.eyJraWQiOiJVYmtLOFk2aXY0R1poRnA2VHgzSVdMV0xmTlhTRXZKY2RUM3pkUjY1WVp4byJ9"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, footer)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "v1_local")] #[test] fn test_1_e_8() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("26f7553354482a1d91d4784627854b8da6b8042a7966523c2b404e8dbbe7f7f2")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a secret message", "exp":"2019-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); let footer = json!({"kid":"UbkK8Y6iv4GZhFp6Tx3IWLWLfNXSEvJcdT3zdR65YZxo"}).to_string(); let footer = footer.as_str(); let footer = Footer::from(footer); // // //create a public V1 token let token = Paseto::::builder() .set_payload(payload) .set_footer(footer) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v1.local.IddlRQmpk6ojcD10z1EYdLexXvYiadtY0MrYQaRnq3dnqKIWcbbpOcgXdMIkm3_3gksirTj81bvWrWkQwcUHilt-tQo7LZK8I6HCK1V78B9YeEqGNeeWXOyWWHoJQIe0d5nTdvcT2vnER6NrJ7xIowvFba6J4qMlFhBnYSxHEq9v9NlzcKsz1zscdjcAiXnEuCHyRSc.eyJraWQiOiJVYmtLOFk2aXY0R1poRnA2VHgzSVdMV0xmTlhTRXZKY2RUM3pkUjY1WVp4byJ9"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, footer)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "v1_local")] #[test] fn test_1_e_9() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("26f7553354482a1d91d4784627854b8da6b8042a7966523c2b404e8dbbe7f7f2")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a secret message", "exp":"2019-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); let footer = Footer::from("arbitrary-string-that-isn't-json"); // // //create a public V1 token let token = Paseto::::builder() .set_payload(payload) .set_footer(footer) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v1.local.IddlRQmpk6ojcD10z1EYdLexXvYiadtY0MrYQaRnq3dnqKIWcbbpOcgXdMIkm3_3gksirTj81bvWrWkQwcUHilt-tQo7LZK8I6HCK1V78B9YeEqGNeeWXOyWWHoJQIe0d5nTdvdgNpe3vI21jV2YL7WVG5p63_JxxzLckBu9azQ0GlDMdPxNAxoyvmU1wbpSbRB9Iw4.YXJiaXRyYXJ5LXN0cmluZy10aGF0LWlzbid0LWpzb24"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, footer)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "v1_public")] #[test] fn test_1_s_1() -> Result<()> { let private_key = include_bytes!("v1_public_test_vectors_private_key.pk8"); let pk: &[u8] = private_key; let private_key = PasetoAsymmetricPrivateKey::::from(pk); let public_key = include_bytes!("v1_public_test_vectors_public_key.der"); let pubk: &[u8] = public_key; let public_key = PasetoAsymmetricPublicKey::::from(pubk); let payload = json!({"data": "this is a signed message", "exp":"2019-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); //create a public V1 token let token = Paseto::::builder() .set_payload(payload) .try_sign(&private_key)?; //now let's try to decrypt it let json = Paseto::::try_verify(&token, &public_key, None)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "v1_public")] #[test] fn test_1_s_2() -> Result<()> { //setup let private_key = include_bytes!("v1_public_test_vectors_private_key.pk8"); let pk: &[u8] = private_key; let private_key = PasetoAsymmetricPrivateKey::::from(pk); let public_key = include_bytes!("v1_public_test_vectors_public_key.der"); let pubk: &[u8] = public_key; let public_key = PasetoAsymmetricPublicKey::::from(pubk); let payload = json!({"data": "this is a signed message","exp": "2019-01-01T00:00:00+00:00"}).to_string(); let footer = json!({"kid":"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN"}).to_string(); //create message for test vector // eprintln!("\nJSON INFO: {}\n", json); let message = Payload::from(payload.as_str()); let footer = Footer::from(footer.as_str()); // // //create a local v2 token //let token = Paseto::::build_token(header, message, &key, None); let token = Paseto::::default() .set_payload(message.clone()) .set_footer(footer.clone()) .try_sign(&private_key)?; // //validate the test vector //now let's try to decrypt it let json = Paseto::::try_verify(&token, &public_key, footer)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "v1_public")] #[test] fn test_1_s_3() -> Result<()> { //setup let private_key = include_bytes!("v1_public_test_vectors_private_key.pk8"); let pk: &[u8] = private_key; let private_key = PasetoAsymmetricPrivateKey::::from(pk); let public_key = include_bytes!("v1_public_test_vectors_public_key.der"); let pubk: &[u8] = public_key; let public_key = PasetoAsymmetricPublicKey::::from(pubk); let payload = json!({"data": "this is a signed message","exp": "2019-01-01T00:00:00+00:00"}).to_string(); let footer = json!({"kid":"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN"}).to_string(); //create message for test vector // eprintln!("\nJSON INFO: {}\n", json); let message = Payload::from(payload.as_str()); let footer = Footer::from(footer.as_str()); // // //create a local v2 token //let token = Paseto::::build_token(header, message, &key, None); let token = Paseto::::default() .set_payload(message.clone()) .set_footer(footer.clone()) .try_sign(&private_key)?; //now let's try to decrypt it let json = Paseto::::try_verify(&token, &public_key, footer)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "v1_public")] #[test] #[should_panic] fn test_1_f_1() { //this test is prevented at compile time and passes by defacto panic!("non-compileable test") } #[cfg(feature = "v1_public")] #[test] #[should_panic] fn test_1_f_2() { //this test is prevented at compile time and passes by defacto panic!("non-compileable test") } } rusty_paseto-0.7.1/tests/version2_test_vectors.rs000064400000000000000000000404271046102023000204640ustar 00000000000000#[cfg(all(test, feature = "v2"))] mod v2_test_vectors { use anyhow::Result; use rusty_paseto::core::*; use serde_json::json; #[cfg(feature = "v2_local")] #[test] fn test_2_e_1() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<24>::try_from("000000000000000000000000000000000000000000000000")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a signed message", "exp":"2019-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); // // //create a public V2 token let token = Paseto::::builder() .set_payload(payload) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v2.local.97TTOvgwIxNGvV80XKiGZg_kD3tsXM_-qB4dZGHOeN1cTkgQ4PnW8888l802W8d9AvEGnoNBY3BnqHORy8a5cC8aKpbA0En8XELw2yDk2f1sVODyfnDbi6rEGMY3pSfCbLWMM2oHJxvlEl2XbQ"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, None)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "v2_local")] #[test] fn test_2_e_2() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<24>::try_from("000000000000000000000000000000000000000000000000")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a secret message", "exp":"2019-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); // // //create a public V2 token let token = Paseto::::builder() .set_payload(payload) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v2.local.CH50H-HM5tzdK4kOmQ8KbIvrzJfjYUGuu5Vy9ARSFHy9owVDMYg3-8rwtJZQjN9ABHb2njzFkvpr5cOYuRyt7CRXnHt42L5yZ7siD-4l-FoNsC7J2OlvLlIwlG06mzQVunrFNb7Z3_CHM0PK5w"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, None)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "v2_local")] #[test] fn test_2_e_3() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<24>::try_from("45742c976d684ff84ebdc0de59809a97cda2f64c84fda19b")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a signed message", "exp":"2019-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); // // //create a public V2 token let token = Paseto::::builder() .set_payload(payload) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v2.local.5K4SCXNhItIhyNuVIZcwrdtaDKiyF81-eWHScuE0idiVqCo72bbjo07W05mqQkhLZdVbxEa5I_u5sgVk1QLkcWEcOSlLHwNpCkvmGGlbCdNExn6Qclw3qTKIIl5-O5xRBN076fSDPo5xUCPpBA"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, None)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "v2_local")] #[test] fn test_2_e_4() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<24>::try_from("45742c976d684ff84ebdc0de59809a97cda2f64c84fda19b")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a secret message", "exp":"2019-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); //let footer = Footer::from("{\"kid\":\"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN\"}"); // // //create a public V2 token let token = Paseto::::builder() .set_payload(payload) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v2.local.pvFdDeNtXxknVPsbBCZF6MGedVhPm40SneExdClOxa9HNR8wFv7cu1cB0B4WxDdT6oUc2toyLR6jA6sc-EUM5ll1EkeY47yYk6q8m1RCpqTIzUrIu3B6h232h62DPbIxtjGvNRAwsLK7LcV8oQ"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, None)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "v2_local")] #[test] fn test_2_e_5() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<24>::try_from("45742c976d684ff84ebdc0de59809a97cda2f64c84fda19b")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a signed message", "exp":"2019-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); let footer = json!({"kid":"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN"}).to_string(); let footer = footer.as_str(); let footer = Footer::from(footer); // // //create a public V2 token let token = Paseto::::builder() .set_payload(payload) .set_footer(footer) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v2.local.5K4SCXNhItIhyNuVIZcwrdtaDKiyF81-eWHScuE0idiVqCo72bbjo07W05mqQkhLZdVbxEa5I_u5sgVk1QLkcWEcOSlLHwNpCkvmGGlbCdNExn6Qclw3qTKIIl5-zSLIrxZqOLwcFLYbVK1SrQ.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, footer)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "v2_local")] #[test] fn test_2_e_6() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<24>::try_from("45742c976d684ff84ebdc0de59809a97cda2f64c84fda19b")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a secret message", "exp":"2019-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); let footer = json!({"kid":"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN"}).to_string(); let footer = footer.as_str(); let footer = Footer::from(footer); // // //create a public V2 token let token = Paseto::::builder() .set_payload(payload) .set_footer(footer) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v2.local.pvFdDeNtXxknVPsbBCZF6MGedVhPm40SneExdClOxa9HNR8wFv7cu1cB0B4WxDdT6oUc2toyLR6jA6sc-EUM5ll1EkeY47yYk6q8m1RCpqTIzUrIu3B6h232h62DnMXKdHn_Smp6L_NfaEnZ-A.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, footer)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "v2_local")] #[test] fn test_2_e_7() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<24>::try_from("45742c976d684ff84ebdc0de59809a97cda2f64c84fda19b")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a signed message", "exp":"2019-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); let footer = json!({"kid":"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN"}).to_string(); let footer = footer.as_str(); let footer = Footer::from(footer); // // //create a public V2 token let token = Paseto::::builder() .set_payload(payload) .set_footer(footer) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v2.local.5K4SCXNhItIhyNuVIZcwrdtaDKiyF81-eWHScuE0idiVqCo72bbjo07W05mqQkhLZdVbxEa5I_u5sgVk1QLkcWEcOSlLHwNpCkvmGGlbCdNExn6Qclw3qTKIIl5-zSLIrxZqOLwcFLYbVK1SrQ.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, footer)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "v2_local")] #[test] fn test_2_e_8() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<24>::try_from("45742c976d684ff84ebdc0de59809a97cda2f64c84fda19b")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a secret message", "exp":"2019-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); let footer = json!({"kid":"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN"}).to_string(); let footer = footer.as_str(); let footer = Footer::from(footer); // // //create a public V2 token let token = Paseto::::builder() .set_payload(payload) .set_footer(footer) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v2.local.pvFdDeNtXxknVPsbBCZF6MGedVhPm40SneExdClOxa9HNR8wFv7cu1cB0B4WxDdT6oUc2toyLR6jA6sc-EUM5ll1EkeY47yYk6q8m1RCpqTIzUrIu3B6h232h62DnMXKdHn_Smp6L_NfaEnZ-A.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, footer)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "v2_local")] #[test] fn test_2_e_9() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<24>::try_from("45742c976d684ff84ebdc0de59809a97cda2f64c84fda19b")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a secret message", "exp":"2019-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); let footer = Footer::from("arbitrary-string-that-isn't-json"); // // //create a public V2 token let token = Paseto::::builder() .set_payload(payload) .set_footer(footer) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v2.local.pvFdDeNtXxknVPsbBCZF6MGedVhPm40SneExdClOxa9HNR8wFv7cu1cB0B4WxDdT6oUc2toyLR6jA6sc-EUM5ll1EkeY47yYk6q8m1RCpqTIzUrIu3B6h232h62DoOJbyKBGPZG50XDZ6mbPtw.YXJiaXRyYXJ5LXN0cmluZy10aGF0LWlzbid0LWpzb24"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, footer)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "v2_public")] #[test] fn test_2_s_1() -> Result<()> { let private_key = Key::<64>::try_from("b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; let private_key = PasetoAsymmetricPrivateKey::::from(&private_key); let public_key = Key::<32>::try_from("1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; let public_key = PasetoAsymmetricPublicKey::::from(&public_key); let payload = json!({"data": "this is a signed message", "exp":"2019-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); // // //create a public V2 token let token = Paseto::::builder() .set_payload(payload) .try_sign(&private_key)?; // //validate the test vector assert_eq!(token.to_string(), "v2.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAxOS0wMS0wMVQwMDowMDowMCswMDowMCJ9HQr8URrGntTu7Dz9J2IF23d1M7-9lH9xiqdGyJNvzp4angPW5Esc7C5huy_M8I8_DjJK2ZXC2SUYuOFM-Q_5Cw"); ////now let's try to decrypt it let json = Paseto::::try_verify(&token, &public_key, None)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "v2_public")] #[test] fn test_2_s_2() -> Result<()> { //setup let private_key = Key::<64>::try_from("b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; let private_key = PasetoAsymmetricPrivateKey::::from(&private_key); let public_key = Key::<32>::try_from("1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; let public_key = PasetoAsymmetricPublicKey::::from(&public_key); let payload = json!({"data": "this is a signed message","exp": "2019-01-01T00:00:00+00:00"}).to_string(); let footer = json!({"kid":"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN"}).to_string(); //create message for test vector // eprintln!("\nJSON INFO: {}\n", json); let message = Payload::from(payload.as_str()); let footer = Footer::from(footer.as_str()); // // //create a local v2 token //let token = Paseto::::build_token(header, message, &key, None); let token = Paseto::::default() .set_payload(message.clone()) .set_footer(footer.clone()) .try_sign(&private_key)?; // //validate the test vector assert_eq!(token, "v2.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAxOS0wMS0wMVQwMDowMDowMCswMDowMCJ9flsZsx_gYCR0N_Ec2QxJFFpvQAs7h9HtKwbVK2n1MJ3Rz-hwe8KUqjnd8FAnIJZ601tp7lGkguU63oGbomhoBw.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9"); //now let's try to decrypt it let json = Paseto::::try_verify(&token, &public_key, footer)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "v2_public")] #[test] fn test_2_s_3() -> Result<()> { //setup let private_key = Key::<64>::try_from("b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; let private_key = PasetoAsymmetricPrivateKey::::from(&private_key); let public_key = Key::<32>::try_from("1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; let public_key = PasetoAsymmetricPublicKey::::from(&public_key); let payload = json!({"data": "this is a signed message","exp": "2019-01-01T00:00:00+00:00"}).to_string(); let footer = json!({"kid":"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN"}).to_string(); //create message for test vector // eprintln!("\nJSON INFO: {}\n", json); let message = Payload::from(payload.as_str()); let footer = Footer::from(footer.as_str()); // // //create a local v2 token //let token = Paseto::::build_token(header, message, &key, None); let token = Paseto::::default() .set_payload(message.clone()) .set_footer(footer.clone()) .try_sign(&private_key)?; // //validate the test vector assert_eq!(token.to_string(), "v2.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAxOS0wMS0wMVQwMDowMDowMCswMDowMCJ9flsZsx_gYCR0N_Ec2QxJFFpvQAs7h9HtKwbVK2n1MJ3Rz-hwe8KUqjnd8FAnIJZ601tp7lGkguU63oGbomhoBw.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9"); //now let's try to decrypt it let json = Paseto::::try_verify(&token, &public_key, footer)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "public")] #[test] #[should_panic] fn test_2_f_1() { //this test is prevented at compile time and passes by defacto panic!("non-compileable test") } #[cfg(feature = "public")] #[test] #[should_panic] fn test_2_f_2() { //this test is prevented at compile time and passes by defacto panic!("non-compileable test") } #[cfg(feature = "public")] #[test] #[should_panic] fn test_2_f_3() { //this test is prevented at compile time and passes by defacto panic!("non-compileable test") } } rusty_paseto-0.7.1/tests/version3_test_vectors.rs000064400000000000000000000606731046102023000204720ustar 00000000000000#[cfg(all(test, feature = "v3"))] mod v3_test_vectors { use anyhow::Result; use rusty_paseto::core::*; use serde_json::json; #[cfg(all(test, feature = "v3_local"))] #[test] fn test_3_e_1() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); // // //create a public V3 token let token = Paseto::::builder() .set_payload(payload) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v3.local.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADbfcIURX_0pVZVU1mAESUzrKZAsRm2EsD6yBoZYn6cpVZNzSJOhSDN-sRaWjfLU-yn9OJH1J_B8GKtOQ9gSQlb8yk9Iza7teRdkiR89ZFyvPPsVjjFiepFUVcMa-LP18zV77f_crJrVXWa5PDNRkCSeHfBBeg"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, None, None)?; assert_eq!(payload, json); Ok(()) } #[cfg(all(test, feature = "v3_local"))] #[test] fn test_3_e_2() -> Result<()> { let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a hidden message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); // // //create a public V3 token let token = Paseto::::builder() .set_payload(payload) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v3.local.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADbfcIURX_0pVZVU1mAESUzrKZAqhWxBMDgyBoZYn6cpVZNzSJOhSDN-sRaWjfLU-yn9OJH1J_B8GKtOQ9gSQlb8yk9IzZfaZpReVpHlDSwfuygx1riVXYVs-UjcrG_apl9oz3jCVmmJbRuKn5ZfD8mHz2db0A"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, None, None)?; assert_eq!(payload, json); Ok(()) } #[cfg(all(test, feature = "v3_local"))] #[test] fn test_3_e_3() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("26f7553354482a1d91d4784627854b8da6b8042a7966523c2b404e8dbbe7f7f2")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); // // //create a public V3 token let token = Paseto::::builder() .set_payload(payload) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0ROIIykcrGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJlxnt5xyhQjFJomwnt7WW_7r2VT0G704ifult011-TgLCyQ2X8imQhniG_hAQ4BydM"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, None, None)?; assert_eq!(payload, json); Ok(()) } #[cfg(all(test, feature = "v3_local"))] #[test] fn test_3_e_4() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("26f7553354482a1d91d4784627854b8da6b8042a7966523c2b404e8dbbe7f7f2")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a hidden message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); // // //create a public V3 token let token = Paseto::::builder() .set_payload(payload) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0X-4P3EcxGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJlBZa_gOpVj4gv0M9lV6Pwjp8JS_MmaZaTA1LLTULXybOBZ2S4xMbYqYmDRhh3IgEk"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, None, None)?; assert_eq!(payload, json); Ok(()) } #[cfg(all(test, feature = "v3_local"))] #[test] fn test_3_e_5() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("26f7553354482a1d91d4784627854b8da6b8042a7966523c2b404e8dbbe7f7f2")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); let footer = json!({"kid":"UbkK8Y6iv4GZhFp6Tx3IWLWLfNXSEvJcdT3zdR65YZxo"}).to_string(); let footer = footer.as_str(); let footer = Footer::from(footer); // // //create a public V3 token let token = Paseto::::builder() .set_payload(payload) .set_footer(footer) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0ROIIykcrGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJlkYSIbXOgVuIQL65UMdW9WcjOpmqvjqD40NNzed-XPqn1T3w-bJvitYpUJL_rmihc.eyJraWQiOiJVYmtLOFk2aXY0R1poRnA2VHgzSVdMV0xmTlhTRXZKY2RUM3pkUjY1WVp4byJ9"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, footer, None)?; assert_eq!(payload, json); Ok(()) } #[cfg(all(test, feature = "v3_local"))] #[test] fn test_3_e_6() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("26f7553354482a1d91d4784627854b8da6b8042a7966523c2b404e8dbbe7f7f2")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a hidden message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); let footer = json!({"kid":"UbkK8Y6iv4GZhFp6Tx3IWLWLfNXSEvJcdT3zdR65YZxo"}).to_string(); let footer = footer.as_str(); let footer = Footer::from(footer); // // //create a public V3 token let token = Paseto::::builder() .set_payload(payload) .set_footer(footer) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0X-4P3EcxGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJmSeEMphEWHiwtDKJftg41O1F8Hat-8kQ82ZIAMFqkx9q5VkWlxZke9ZzMBbb3Znfo.eyJraWQiOiJVYmtLOFk2aXY0R1poRnA2VHgzSVdMV0xmTlhTRXZKY2RUM3pkUjY1WVp4byJ9"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, footer, None)?; assert_eq!(payload, json); Ok(()) } #[cfg(all(test, feature = "v3_local"))] #[test] fn test_3_e_7() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("26f7553354482a1d91d4784627854b8da6b8042a7966523c2b404e8dbbe7f7f2")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); let footer = json!({"kid":"UbkK8Y6iv4GZhFp6Tx3IWLWLfNXSEvJcdT3zdR65YZxo"}).to_string(); let footer = footer.as_str(); let footer = Footer::from(footer); let implicit_assertion = json!({"test-vector":"3-E-7"}).to_string(); let implicit_assertion = implicit_assertion.as_str(); let implicit_assertion = ImplicitAssertion::from(implicit_assertion); // // //create a public V3 token let token = Paseto::::builder() .set_payload(payload) .set_footer(footer) .set_implicit_assertion(implicit_assertion) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0ROIIykcrGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJkzWACWAIoVa0bz7EWSBoTEnS8MvGBYHHo6t6mJunPrFR9JKXFCc0obwz5N-pxFLOc.eyJraWQiOiJVYmtLOFk2aXY0R1poRnA2VHgzSVdMV0xmTlhTRXZKY2RUM3pkUjY1WVp4byJ9"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, footer, implicit_assertion)?; assert_eq!(payload, json); Ok(()) } #[cfg(all(test, feature = "v3_local"))] #[test] fn test_3_e_8() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("26f7553354482a1d91d4784627854b8da6b8042a7966523c2b404e8dbbe7f7f2")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a hidden message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); let footer = json!({"kid":"UbkK8Y6iv4GZhFp6Tx3IWLWLfNXSEvJcdT3zdR65YZxo"}).to_string(); let footer = footer.as_str(); let footer = Footer::from(footer); let implicit_assertion = json!({"test-vector":"3-E-8"}).to_string(); let implicit_assertion = implicit_assertion.as_str(); let implicit_assertion = ImplicitAssertion::from(implicit_assertion); // // //create a public V3 token let token = Paseto::::builder() .set_payload(payload) .set_footer(footer) .set_implicit_assertion(implicit_assertion) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0X-4P3EcxGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJmZHSSKYR6AnPYJV6gpHtx6dLakIG_AOPhu8vKexNyrv5_1qoom6_NaPGecoiz6fR8.eyJraWQiOiJVYmtLOFk2aXY0R1poRnA2VHgzSVdMV0xmTlhTRXZKY2RUM3pkUjY1WVp4byJ9"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, footer, implicit_assertion)?; assert_eq!(payload, json); Ok(()) } #[cfg(all(test, feature = "v3_local"))] #[test] fn test_3_e_9() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("26f7553354482a1d91d4784627854b8da6b8042a7966523c2b404e8dbbe7f7f2")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a hidden message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); let footer = Footer::from("arbitrary-string-that-isn't-json"); let implicit_assertion = json!({"test-vector":"3-E-9"}).to_string(); let implicit_assertion = implicit_assertion.as_str(); let implicit_assertion = ImplicitAssertion::from(implicit_assertion); // // //create a public V3 token let token = Paseto::::builder() .set_payload(payload) .set_footer(footer) .set_implicit_assertion(implicit_assertion) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0X-4P3EcxGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJlk1nli0_wijTH_vCuRwckEDc82QWK8-lG2fT9wQF271sgbVRVPjm0LwMQZkvvamqU.YXJiaXRyYXJ5LXN0cmluZy10aGF0LWlzbid0LWpzb24"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, footer, implicit_assertion)?; assert_eq!(payload, json); Ok(()) } //V3 Public #[cfg(feature = "v3_public")] #[test] fn test_3_s_1() -> Result<()> { //setup let private_key = Key::<48>::try_from( "20347609607477aca8fbfbc5e6218455f3199669792ef8b466faa87bdc67798144c848dd03661eed5ac62461340cea96", )?; let private_key = PasetoAsymmetricPrivateKey::::from(&private_key); let public_key = Key::<49>::try_from( "02fbcb7c69ee1c60579be7a334134878d9c5c5bf35d552dab63c0140397ed14cef637d7720925c44699ea30e72874c72fb", )?; let public_key = PasetoAsymmetricPublicKey::::try_from(&public_key)?; let payload = json!({"data":"this is a signed message","exp":"2022-01-01T00:00:00+00:00"}).to_string(); //create message for test vector let message = Payload::from(payload.as_str()); // let footer = Footer::default(); // let assertion = ImplicitAssertion::default(); // // //create a local v2 token let token = Paseto::::default() .set_payload(message.clone()) .try_sign(&private_key)?; // //validate the test vector assert_eq!(token.to_string(), "v3.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAyMi0wMS0wMVQwMDowMDowMCswMDowMCJ9qqEwwrKHKi5lJ7b9MBKc0G4MGZy0ptUiMv3lAUAaz-JY_zjoqBSIxMxhfAoeNYiSyvfUErj76KOPWm1OeNnBPkTSespeSXDGaDfxeIrl3bRrPEIy7tLwLAIsRzsXkfph"); //now let's try to decrypt it let json = Paseto::::try_verify(&token, &public_key, None, None)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "v3_public")] #[test] fn test_3_s_2() -> Result<()> { //setup let private_key = Key::<48>::try_from( "20347609607477aca8fbfbc5e6218455f3199669792ef8b466faa87bdc67798144c848dd03661eed5ac62461340cea96", )?; let private_key = PasetoAsymmetricPrivateKey::::from(&private_key); let public_key = Key::<49>::try_from( "02fbcb7c69ee1c60579be7a334134878d9c5c5bf35d552dab63c0140397ed14cef637d7720925c44699ea30e72874c72fb", )?; let public_key = PasetoAsymmetricPublicKey::::try_from(&public_key)?; let payload = json!({"data":"this is a signed message","exp":"2022-01-01T00:00:00+00:00"}).to_string(); //create message for test vector let message = Payload::from(payload.as_str()); let footer = Footer::from("{\"kid\":\"dYkISylxQeecEcHELfzF88UZrwbLolNiCdpzUHGw9Uqn\"}"); // let assertion = ImplicitAssertion::default(); // // //create a local v2 token let token = Paseto::::default() .set_payload(message.clone()) .set_footer(footer.clone()) .try_sign(&private_key)?; // //validate the test vector assert_eq!(token.to_string(), "v3.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAyMi0wMS0wMVQwMDowMDowMCswMDowMCJ9ZWrbGZ6L0MDK72skosUaS0Dz7wJ_2bMcM6tOxFuCasO9GhwHrvvchqgXQNLQQyWzGC2wkr-VKII71AvkLpC8tJOrzJV1cap9NRwoFzbcXjzMZyxQ0wkshxZxx8ImmNWP.eyJraWQiOiJkWWtJU3lseFFlZWNFY0hFTGZ6Rjg4VVpyd2JMb2xOaUNkcHpVSEd3OVVxbiJ9"); //now let's try to decrypt it let json = Paseto::::try_verify(&token, &public_key, footer, None)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "v3_public")] #[test] fn test_3_s_3() -> Result<()> { //setup let private_key = Key::<48>::try_from( "20347609607477aca8fbfbc5e6218455f3199669792ef8b466faa87bdc67798144c848dd03661eed5ac62461340cea96", )?; let private_key = PasetoAsymmetricPrivateKey::::from(&private_key); let public_key = Key::<49>::try_from( "02fbcb7c69ee1c60579be7a334134878d9c5c5bf35d552dab63c0140397ed14cef637d7720925c44699ea30e72874c72fb", )?; let public_key = PasetoAsymmetricPublicKey::::try_from(&public_key)?; let payload = json!({"data":"this is a signed message","exp":"2022-01-01T00:00:00+00:00"}).to_string(); //create message for test vector let message = Payload::from(payload.as_str()); let footer = Footer::from("{\"kid\":\"dYkISylxQeecEcHELfzF88UZrwbLolNiCdpzUHGw9Uqn\"}"); let assertion = ImplicitAssertion::from("{\"test-vector\":\"3-S-3\"}"); // // //create a local v2 token let token = Paseto::::default() .set_payload(message.clone()) .set_footer(footer.clone()) .set_implicit_assertion(assertion.clone()) .try_sign(&private_key)?; // //validate the test vector assert_eq!(token.to_string(), "v3.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAyMi0wMS0wMVQwMDowMDowMCswMDowMCJ94SjWIbjmS7715GjLSnHnpJrC9Z-cnwK45dmvnVvCRQDCCKAXaKEopTajX0DKYx1Xqr6gcTdfqscLCAbiB4eOW9jlt-oNqdG8TjsYEi6aloBfTzF1DXff_45tFlnBukEX.eyJraWQiOiJkWWtJU3lseFFlZWNFY0hFTGZ6Rjg4VVpyd2JMb2xOaUNkcHpVSEd3OVVxbiJ9"); //now let's try to decrypt it let json = Paseto::::try_verify(&token, &public_key, footer, assertion)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "v3_public")] #[test] fn test_3_f_1() -> Result<()> { //setup let private_key = Key::<48>::try_from( "20347609607477aca8fbfbc5e6218455f3199669792ef8b466faa87bdc67798144c848dd03661eed5ac62461340cea96", )?; let private_key = PasetoAsymmetricPrivateKey::::from(&private_key); //create message for test vector let footer = Footer::from("arbitrary-string-that-isn't-json"); let assertion = ImplicitAssertion::from("\"test-vector\":\"3-F-1\"}"); // // //create a v3 public signed token let token = Paseto::::default() //.set_payload(message.clone()) .set_footer(footer.clone()) .set_implicit_assertion(assertion.clone()) .try_sign(&private_key)?; // //validate the test vector assert_ne!(token.to_string(), "v3.local.tthw-G1Da_BzYeMu_GEDp-IyQ7jzUCQHxCHRdDY6hQjKg6CuxECXfjOzlmNgNJ-WELjN61gMDnldG9OLkr3wpxuqdZksCzH9Ul16t3pXCLGPoHQ9_l51NOqVmMLbFVZOPhsmdhef9RxJwmqvzQ_Mo_JkYRlrNA.YXJiaXRyYXJ5LXN0cmluZy10aGF0LWlzbid0LWpzb24"); Ok(()) } #[cfg(feature = "v3_local")] #[test] fn test_3_f_2() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("df654812bac492663825520ba2f6e67cf5ca5bdc13d4e7507a98cc4c2fcc3ad8")?; let nonce = PasetoNonce::::from(&nonce); // let payload = json!({"data": "this is a hidden message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); // let payload = payload.as_str(); // let payload = Payload::from(payload); let footer = Footer::from("{\"kid\":\"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN\"}"); let implicit_assertion = ImplicitAssertion::from("{\"test-vector\":\"3-F-2\"}"); // // //create a public V3 token let token = Paseto::::builder() // .set_payload(payload) .set_footer(footer) .set_implicit_assertion(implicit_assertion) .try_encrypt(&key, &nonce)?; let test_token = "v3.public.eyJpbnZhbGlkIjoidGhpcyBzaG91bGQgbmV2ZXIgZGVjb2RlIn1hbzIBD_EU54TYDTvsN9bbCU1QPo7FDeIhijkkcB9BrVH73XyM3Wwvu1pJaGCOEc0R5DVe9hb1ka1cYBd0goqVHt0NQ2NhPtILz4W36eCCqyU4uV6xDMeLI8ni6r3GnaY.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9"; // //validate the test vector assert_ne!(token.to_string(), test_token); ////now let's try to decrypt it let verify_attempt = Paseto::::try_decrypt(&test_token, &key, footer, implicit_assertion); assert!(verify_attempt.is_err()); Ok(()) } #[cfg(feature = "v3_local")] #[test] fn test_3_f_3() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("26f7553354482a1d91d4784627854b8da6b8042a7966523c2b404e8dbbe7f7f2")?; let nonce = PasetoNonce::::from(&nonce); // let payload = json!({"data": "this is a hidden message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); // let payload = payload.as_str(); // let payload = Payload::from(payload); let footer = Footer::from("arbitrary-string-that-isn't-json"); let implicit_assertion = ImplicitAssertion::from("{\"test-vector\":\"3-F-3\"}"); // // //create a public V3 token let token = Paseto::::builder() // .set_payload(payload) .set_footer(footer) .set_implicit_assertion(implicit_assertion) .try_encrypt(&key, &nonce)?; let test_token = "v4.local.1JgN1UG8TFAYS49qsx8rxlwh-9E4ONUm3slJXYi5EibmzxpF0Q-du6gakjuyKCBX8TvnSLOKqCPu8Yh3WSa5yJWigPy33z9XZTJF2HQ9wlLDPtVn_Mu1pPxkTU50ZaBKblJBufRA.YXJiaXRyYXJ5LXN0cmluZy10aGF0LWlzbid0LWpzb24"; // //validate the test vector assert_ne!(token.to_string(), test_token); ////now let's try to decrypt it let verify_attempt = Paseto::::try_decrypt(&test_token, &key, footer, implicit_assertion); assert!(verify_attempt.is_err()); Ok(()) } #[cfg(feature = "v3_local")] #[test] fn test_3_f_4() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; let nonce = PasetoNonce::::from(&nonce); // let payload = json!({"data": "this is a hidden message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); // let payload = payload.as_str(); // let payload = Payload::from(payload); let footer = Footer::default(); let implicit_assertion = ImplicitAssertion::default(); // // //create a public V3 token let token = Paseto::::builder() // .set_payload(payload) .set_footer(footer) .set_implicit_assertion(implicit_assertion) .try_encrypt(&key, &nonce)?; let test_token = "v3.local.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADbfcIURX_0pVZVU1mAESUzrKZAsRm2EsD6yBoZYn6cpVZNzSJOhSDN-sRaWjfLU-yn9OJH1J_B8GKtOQ9gSQlb8yk9Iza7teRdkiR89ZFyvPPsVjjFiepFUVcMa-LP18zV77f_crJrVXWa5PDNRkCSeHfBBeh"; // //validate the test vector assert_ne!(token.to_string(), test_token); ////now let's try to decrypt it let verify_attempt = Paseto::::try_decrypt(&test_token, &key, footer, implicit_assertion); assert!(verify_attempt.is_err()); Ok(()) } #[cfg(feature = "v3_local")] #[test] fn test_3_f_5() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("26f7553354482a1d91d4784627854b8da6b8042a7966523c2b404e8dbbe7f7f2")?; let nonce = PasetoNonce::::from(&nonce); // let payload = json!({"data": "this is a hidden message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); // let payload = payload.as_str(); // let payload = Payload::from(payload); let footer = Footer::from("{\"kid\":\"UbkK8Y6iv4GZhFp6Tx3IWLWLfNXSEvJcdT3zdR65YZxo\"}"); let implicit_assertion = ImplicitAssertion::default(); // // //create a public V3 token let token = Paseto::::builder() // .set_payload(payload) .set_footer(footer) .set_implicit_assertion(implicit_assertion) .try_encrypt(&key, &nonce)?; let test_token = "v3.local.JvdVM1RIKh2R1HhGJ4VLjaa4BCp5ZlI8K0BOjbvn9_LwY78vQnDait-Q-sjhF88dG2B0ROIIykcrGHn8wzPbTrqObHhyoKpjy3cwZQzLdiwRsdEK5SDvl02_HjWKJW2oqGMOQJlkYSIbXOgVuIQL65UMdW9WcjOpmqvjqD40NNzed-XPqn1T3w-bJvitYpUJL_rmihc=.eyJraWQiOiJVYmtLOFk2aXY0R1poRnA2VHgzSVdMV0xmTlhTRXZKY2RUM3pkUjY1WVp4byJ9"; // //validate the test vector assert_ne!(token.to_string(), test_token); ////now let's try to decrypt it let verify_attempt = Paseto::::try_decrypt(&test_token, &key, footer, implicit_assertion); assert!(verify_attempt.is_err()); // assert_ne!(verify_attempt, ""); Ok(()) } } rusty_paseto-0.7.1/tests/version4_test_vectors.rs000064400000000000000000000472431046102023000204710ustar 00000000000000#[cfg(all(test, feature = "v4"))] mod v4_test_vectors { use anyhow::{Result}; use serde_json::json; use rusty_paseto::core::*; #[cfg(feature = "local")] #[test] fn test_4_e_1() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); // // //create a public v4 token let token = Paseto::::builder() .set_payload(payload) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v4.local.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAr68PS4AXe7If_ZgesdkUMvSwscFlAl1pk5HC0e8kApeaqMfGo_7OpBnwJOAbY9V7WU6abu74MmcUE8YWAiaArVI8XJ5hOb_4v9RmDkneN0S92dx0OW4pgy7omxgf3S8c3LlQg"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, None, None)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "local")] #[test] fn test_4_e_2() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a hidden message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); // // //create a public v4 token let token = Paseto::::builder() .set_payload(payload) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v4.local.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQAr68PS4AXe7If_ZgesdkUMvS2csCgglvpk5HC0e8kApeaqMfGo_7OpBnwJOAbY9V7WU6abu74MmcUE8YWAiaArVI8XIemu9chy3WVKvRBfg6t8wwYHK0ArLxxfZP73W_vfwt5A"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, None, None)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "local")] #[test] fn test_4_e_3() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("df654812bac492663825520ba2f6e67cf5ca5bdc13d4e7507a98cc4c2fcc3ad8")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); // // //create a public v4 token let token = Paseto::::builder() .set_payload(payload) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v4.local.32VIErrEkmY4JVILovbmfPXKW9wT1OdQepjMTC_MOtjA4kiqw7_tcaOM5GNEcnTxl60WkwMsYXw6FSNb_UdJPXjpzm0KW9ojM5f4O2mRvE2IcweP-PRdoHjd5-RHCiExR1IK6t6-tyebyWG6Ov7kKvBdkrrAJ837lKP3iDag2hzUPHuMKA"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, None, None)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "local")] #[test] fn test_4_e_4() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("df654812bac492663825520ba2f6e67cf5ca5bdc13d4e7507a98cc4c2fcc3ad8")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a hidden message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); // // //create a public v4 token let token = Paseto::::builder() .set_payload(payload) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v4.local.32VIErrEkmY4JVILovbmfPXKW9wT1OdQepjMTC_MOtjA4kiqw7_tcaOM5GNEcnTxl60WiA8rd3wgFSNb_UdJPXjpzm0KW9ojM5f4O2mRvE2IcweP-PRdoHjd5-RHCiExR1IK6t4gt6TiLm55vIH8c_lGxxZpE3AWlH4WTR0v45nsWoU3gQ"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, None, None)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "local")] #[test] fn test_4_e_5() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("df654812bac492663825520ba2f6e67cf5ca5bdc13d4e7507a98cc4c2fcc3ad8")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); let footer = json!({"kid":"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN"}).to_string(); let footer = footer.as_str(); let footer = Footer::from(footer); // // //create a public v4 token let token = Paseto::::builder() .set_payload(payload) .set_footer(footer) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v4.local.32VIErrEkmY4JVILovbmfPXKW9wT1OdQepjMTC_MOtjA4kiqw7_tcaOM5GNEcnTxl60WkwMsYXw6FSNb_UdJPXjpzm0KW9ojM5f4O2mRvE2IcweP-PRdoHjd5-RHCiExR1IK6t4x-RMNXtQNbz7FvFZ_G-lFpk5RG3EOrwDL6CgDqcerSQ.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, footer, None)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "local")] #[test] fn test_4_e_6() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("df654812bac492663825520ba2f6e67cf5ca5bdc13d4e7507a98cc4c2fcc3ad8")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a hidden message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); let footer = json!({"kid":"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN"}).to_string(); let footer = footer.as_str(); let footer = Footer::from(footer); // // //create a public v4 token let token = Paseto::::builder() .set_payload(payload) .set_footer(footer) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v4.local.32VIErrEkmY4JVILovbmfPXKW9wT1OdQepjMTC_MOtjA4kiqw7_tcaOM5GNEcnTxl60WiA8rd3wgFSNb_UdJPXjpzm0KW9ojM5f4O2mRvE2IcweP-PRdoHjd5-RHCiExR1IK6t6pWSA5HX2wjb3P-xLQg5K5feUCX4P2fpVK3ZLWFbMSxQ.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, footer, None)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "local")] #[test] fn test_4_e_7() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("df654812bac492663825520ba2f6e67cf5ca5bdc13d4e7507a98cc4c2fcc3ad8")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a secret message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); let footer = json!({"kid":"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN"}).to_string(); let footer = footer.as_str(); let footer = Footer::from(footer); let implicit_assertion = json!({"test-vector":"4-E-7"}).to_string(); let implicit_assertion = implicit_assertion.as_str(); let implicit_assertion = ImplicitAssertion::from(implicit_assertion); // // //create a public v4 token let token = Paseto::::builder() .set_payload(payload) .set_footer(footer) .set_implicit_assertion(implicit_assertion) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v4.local.32VIErrEkmY4JVILovbmfPXKW9wT1OdQepjMTC_MOtjA4kiqw7_tcaOM5GNEcnTxl60WkwMsYXw6FSNb_UdJPXjpzm0KW9ojM5f4O2mRvE2IcweP-PRdoHjd5-RHCiExR1IK6t40KCCWLA7GYL9KFHzKlwY9_RnIfRrMQpueydLEAZGGcA.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, footer, implicit_assertion)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "local")] #[test] fn test_4_e_8() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("df654812bac492663825520ba2f6e67cf5ca5bdc13d4e7507a98cc4c2fcc3ad8")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a hidden message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); let footer = json!({"kid":"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN"}).to_string(); let footer = footer.as_str(); let footer = Footer::from(footer); let implicit_assertion = json!({"test-vector":"4-E-8"}).to_string(); let implicit_assertion = implicit_assertion.as_str(); let implicit_assertion = ImplicitAssertion::from(implicit_assertion); // // //create a public v4 token let token = Paseto::::builder() .set_payload(payload) .set_footer(footer) .set_implicit_assertion(implicit_assertion) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v4.local.32VIErrEkmY4JVILovbmfPXKW9wT1OdQepjMTC_MOtjA4kiqw7_tcaOM5GNEcnTxl60WiA8rd3wgFSNb_UdJPXjpzm0KW9ojM5f4O2mRvE2IcweP-PRdoHjd5-RHCiExR1IK6t5uvqQbMGlLLNYBc7A6_x7oqnpUK5WLvj24eE4DVPDZjw.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, footer, implicit_assertion)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "local")] #[test] fn test_4_e_9() -> Result<()> { //setup let key = PasetoSymmetricKey::::from(Key::<32>::try_from( "707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f", )?); let nonce = Key::<32>::try_from("df654812bac492663825520ba2f6e67cf5ca5bdc13d4e7507a98cc4c2fcc3ad8")?; let nonce = PasetoNonce::::from(&nonce); let payload = json!({"data": "this is a hidden message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); let footer = Footer::from("arbitrary-string-that-isn't-json"); let implicit_assertion = json!({"test-vector":"4-E-9"}).to_string(); let implicit_assertion = implicit_assertion.as_str(); let implicit_assertion = ImplicitAssertion::from(implicit_assertion); // // //create a public v4 token let token = Paseto::::builder() .set_payload(payload) .set_footer(footer) .set_implicit_assertion(implicit_assertion) .try_encrypt(&key, &nonce)?; // //validate the test vector assert_eq!(token.to_string(), "v4.local.32VIErrEkmY4JVILovbmfPXKW9wT1OdQepjMTC_MOtjA4kiqw7_tcaOM5GNEcnTxl60WiA8rd3wgFSNb_UdJPXjpzm0KW9ojM5f4O2mRvE2IcweP-PRdoHjd5-RHCiExR1IK6t6tybdlmnMwcDMw0YxA_gFSE_IUWl78aMtOepFYSWYfQA.YXJiaXRyYXJ5LXN0cmluZy10aGF0LWlzbid0LWpzb24"); ////now let's try to decrypt it let json = Paseto::::try_decrypt(&token, &key, footer, implicit_assertion)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "v4_public")] #[test] fn test_4_s_1() -> Result<()> { //then generate the V2 local key for it //setup //let key = Key::<32>::try_from("707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f")?; //let key = PasetoKey::::from(&key); //let nonce = Key::<32>::try_from("0000000000000000000000000000000000000000000000000000000000000000")?; //let nonce = PasetoNonce::::from(&nonce); let private_key = Key::<64>::try_from("b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; let pk: &[u8] = private_key.as_slice(); let private_key = PasetoAsymmetricPrivateKey::::from(pk); let public_key = Key::<32>::try_from("1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; let public_key = PasetoAsymmetricPublicKey::::from(&public_key); let payload = json!({"data": "this is a signed message", "exp":"2022-01-01T00:00:00+00:00"}).to_string(); let payload = payload.as_str(); let payload = Payload::from(payload); // // //create a public v4 token let token = Paseto::::builder() .set_payload(payload) .try_sign(&private_key)?; // //validate the test vector assert_eq!(token.to_string(), "v4.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAyMi0wMS0wMVQwMDowMDowMCswMDowMCJ9bg_XBBzds8lTZShVlwwKSgeKpLT3yukTw6JUz3W4h_ExsQV-P0V54zemZDcAxFaSeef1QlXEFtkqxT1ciiQEDA"); ////now let's try to decrypt it let json = Paseto::::try_verify(&token, &public_key, None, None)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "public")] #[test] fn test_4_s_2() -> Result<()> { //setup let private_key = Key::<64>::try_from("b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; let pk: &[u8] = private_key.as_slice(); let private_key = PasetoAsymmetricPrivateKey::::from(pk); let public_key = Key::<32>::try_from("1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; let public_key = PasetoAsymmetricPublicKey::::from(&public_key); let payload = json!({"data": "this is a signed message","exp": "2022-01-01T00:00:00+00:00"}).to_string(); let footer = json!({"kid":"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN"}).to_string(); //create message for test vector // eprintln!("\nJSON INFO: {}\n", json); let message = Payload::from(payload.as_str()); let footer = Footer::from(footer.as_str()); // // //create a local v2 token //let token = Paseto::::build_token(header, message, &key, None); let token = Paseto::::default() .set_payload(message.clone()) .set_footer(footer.clone()) .try_sign(&private_key); match token { Ok(token) => { // //validate the test vector assert_eq!(token, "v4.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAyMi0wMS0wMVQwMDowMDowMCswMDowMCJ9v3Jt8mx_TdM2ceTGoqwrh4yDFn0XsHvvV_D0DtwQxVrJEBMl0F2caAdgnpKlt4p7xBnx1HcO-SPo8FPp214HDw.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9"); //now let's try to decrypt it let json = Paseto::::try_verify(&token, &public_key, footer, None)?; assert_eq!(payload, json); Ok(()) } Err(thiserror) => { eprintln!("here's the error: {}", thiserror); Err(anyhow::Error::from(thiserror)) } } } #[cfg(feature = "public")] #[test] fn test_4_s_3() -> Result<()> { //setup let private_key = Key::<64>::try_from("b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; let pk: &[u8] = private_key.as_slice(); let private_key = PasetoAsymmetricPrivateKey::::from(pk); let public_key = Key::<32>::try_from("1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")?; let public_key = PasetoAsymmetricPublicKey::::from(&public_key); let payload = json!({"data": "this is a signed message","exp": "2022-01-01T00:00:00+00:00"}).to_string(); let footer = json!({"kid":"zVhMiPBP9fRf2snEcT7gFTioeA9COcNy9DfgL1W60haN"}).to_string(); let assertion = json!({"test-vector":"4-S-3"}).to_string(); //create message for test vector // eprintln!("\nJSON INFO: {}\n", json); let message = Payload::from(payload.as_str()); let footer = Footer::from(footer.as_str()); let assertion = ImplicitAssertion::from(assertion.as_str()); // // //create a local v2 token //let token = Paseto::::build_token(header, message, &key, None); let token = Paseto::::default() .set_payload(message.clone()) .set_footer(footer.clone()) .set_implicit_assertion(assertion) .try_sign(&private_key)?; // //validate the test vector assert_eq!(token.to_string(), "v4.public.eyJkYXRhIjoidGhpcyBpcyBhIHNpZ25lZCBtZXNzYWdlIiwiZXhwIjoiMjAyMi0wMS0wMVQwMDowMDowMCswMDowMCJ9NPWciuD3d0o5eXJXG5pJy-DiVEoyPYWs1YSTwWHNJq6DZD3je5gf-0M4JR9ipdUSJbIovzmBECeaWmaqcaP0DQ.eyJraWQiOiJ6VmhNaVBCUDlmUmYyc25FY1Q3Z0ZUaW9lQTlDT2NOeTlEZmdMMVc2MGhhTiJ9"); //now let's try to decrypt it let json = Paseto::::try_verify(&token, &public_key, footer, assertion)?; assert_eq!(payload, json); Ok(()) } #[cfg(feature = "public")] #[test] #[should_panic] fn test_4_f_1() { //this test is prevented at compile time and passes by defacto panic!("non-compileable test") } #[cfg(feature = "public")] #[test] #[should_panic] fn test_4_f_2() { //this test is prevented at compile time and passes by defacto panic!("non-compileable test") } #[cfg(feature = "public")] #[test] #[should_panic] fn test_4_f_3() { //this test is prevented at compile time and passes by defacto panic!("non-compileable test") } }