rpm-sequoia-1.10.2/.cargo/config.toml000064400000000000000000000003421046102023000154610ustar 00000000000000[resolver] incompatible-rust-versions = "fallback" [target.'cfg(all())'] # Note: if the RUSTFLAGS environment variable is set, this will be # ignored. rustflags = [ "-Aunused-parens", "-Aunused-macros", ] rpm-sequoia-1.10.2/.cargo_vcs_info.json0000644000000001360000000000100133570ustar { "git": { "sha1": "401dbd9ae2eca087a82b7c15c0379f878aea02da" }, "path_in_vcs": "" }rpm-sequoia-1.10.2/.ci/all_commits.sh000075500000000000000000000022711046102023000154640ustar 00000000000000#!/usr/bin/env bash # Test all commits on this branch but the last one. # # Used in the all_commits ci job to ensure all commits build # and tests pass at least for the sequoia-openpgp crate. # NOTE: under gitlab's Settings, "CI/CD", General Pipelines ensure # that the "git shallow clone" setting is set to 0. Otherwise other # branch are not fetched. set -e set -x # Use dummy identity to make git rebase happy. git config user.name "C.I. McTestface" git config user.email "ci.mctestface@example.com" # Make sure the gitlab project is configured. if ! git describe --all origin/main then echo "origin/main is not present. Configure the gitlab project (see .ci/all_commits.sh)." exit 1 fi # If the previous commit already is on main we're done. git merge-base --is-ancestor HEAD~ origin/main && echo "All commits tested already" && exit 0 # Leave out the last commit - it has already been checked. git checkout HEAD~ git status git rebase origin/main \ --exec 'echo ===; echo ===; echo ===; git log -n 1;' \ --exec 'cargo test --all' && echo "All commits passed tests" && exit 0 # The rebase failed - probably because a test failed. git rebase --abort; exit 1 rpm-sequoia-1.10.2/.codespellrc000064400000000000000000000004051046102023000144460ustar 00000000000000[codespell] skip = *.bin,*.gpg,*.pgp,./.git,data,highlight.js,*/target,Makefile,*.html,*/cargo,*.xml,*.xmlv2,Cargo.lock, ignore-words-list = crate,ede,iff,mut,nd,te,uint,KeyServer,keyserver,Keyserver,keyservers,Keyservers,keypair,keypairs,KeyPair,fpr,dedup,ba, rpm-sequoia-1.10.2/.github/workflows/authenticate-commits.yml000064400000000000000000000005141046102023000224160ustar 00000000000000name: authenticate-commits on: pull_request: types: [opened, reopened, synchronize] jobs: authenticate-commits: runs-on: ubuntu-latest permissions: contents: read pull-requests: write issues: write steps: - name: Authenticating commits uses: sequoia-pgp/authenticate-commits@v1 rpm-sequoia-1.10.2/.github/workflows/ci.yml000064400000000000000000000113231046102023000166620ustar 00000000000000name: ci on: push: env: CARGO_TERM_COLOR: always jobs: codespell: name: Codespell runs-on: ubuntu-22.04 steps: - name: Setup | Checkout rpm-sequoia uses: actions/checkout@v4 - name: Setup | Dependencies run: sudo apt update && sudo apt install codespell - name: Codespell run: codespell --version && codespell --config .codespellrc --summary compile: name: Compile runs-on: ubuntu-latest steps: - name: Setup | Checkout rpm-sequoia uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup | Build Cache rpm-sequoia uses: actions/cache@v4 with: path: | ~/.cargo/bin/ ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Setup | Dependencies rpm-sequoia run: sudo apt update && sudo apt install cargo clang git nettle-dev pkg-config libssl-dev - name: Build | Compile rpm-sequoia run: cargo build - name: Build | Test rpm-sequoia run: cargo test - name: Build | Doc rpm-sequoia run: cargo doc --no-deps all_commits: name: All Commits runs-on: ubuntu-latest needs: ["Compile"] steps: - name: Setup | Checkout rpm-sequoia uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup | Build Cache rpm-sequoia uses: actions/cache@v4 with: path: | ~/.cargo/bin/ ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Setup | Dependencies rpm-sequoia run: sudo apt update && sudo apt install cargo clang git nettle-dev pkg-config libssl-dev - name: Build | Compile rpm-sequoia run: cargo build - name: Build | Test other commits run: .ci/all_commits.sh rpm: name: RPM runs-on: ubuntu-latest needs: ["Compile"] steps: - name: Setup | Checkout rpm-sequoia uses: actions/checkout@v4 with: fetch-depth: 0 - name: Setup | Build Cache Rust dependencies uses: actions/cache@v4 with: path: | ~/.cargo/bin/ ~/.cargo/registry/index/ ~/.cargo/registry/cache/ ~/.cargo/git/db/ target/ key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Setup | Dependencies rpm-sequoia run: sudo apt update && sudo apt install cargo clang git nettle-dev pkg-config libssl-dev - name: Build | Compile rpm-sequoia run: cargo build - name: Setup | rpm Dependencies run: | sudo apt install podman - name: Setup | Checkout rpm uses: actions/checkout@v4 with: repository: rpm-software-management/rpm.git ref: master fetch-depth: 1 path: rpm - name: Test | rpm run: | export PKG_CONFIG_PATH=$(pwd)/target/debug if ! test -e $PKG_CONFIG_PATH/rpm-sequoia-uninstalled.pc then echo "$PKG_CONFIG_PATH/rpm-sequoia-uninstalled.pc is missing. Did you build librpm-sequoia?" exit 1 fi export LD_LIBRARY_PATH=$PKG_CONFIG_PATH if ! test -e $LD_LIBRARY_PATH/librpm_sequoia.so then echo "$LD_LIBRARY_PATH/librpm_sequoia.so is missing. Did you build librpm-sequoia?" exit 1 fi echo "::group::make check" cd rpm RPM_ROOT=$(pwd) cd tests # based on ./mktree.oci build podman build --target full -t rpm-tests -f Dockerfile .. # install rpm-sequoia in the test image podman build -t rpm-tests-sequoia -f ../../tests/Dockerfile ../../ # run the tests by overriding librpm-sequoia in the container. if ! podman run --privileged -it --rm --read-only --tmpfs /tmp -v $RPM_ROOT:/srv:z \ --workdir /srv -e ROOTLESS=1 rpm-tests-sequoia \ rpmtests -k OpenPGP -k signature -k rpmkeys -k digest; then echo "::endgroup::" cd .. for log in rpmtests.dir/*/rpmtests.log do echo "::group::$log" cat $log || true echo "::endgroup::" done exit 1 else echo "::endgroup::" fi rpm-sequoia-1.10.2/.github/workflows/fast-forward.yml000064400000000000000000000007761046102023000207000ustar 00000000000000name: fast-forward on: issue_comment: types: [created, edited] jobs: fast-forward: # Only run if the comment contains the /fast-forward command. if: ${{ contains(github.event.comment.body, '/fast-forward') && github.event.issue.pull_request }} runs-on: ubuntu-latest permissions: contents: write pull-requests: write issues: write steps: - name: Fast forwarding uses: sequoia-pgp/fast-forward@main with: merge: true rpm-sequoia-1.10.2/.github/workflows/pull-request.yml000064400000000000000000000007201046102023000207300ustar 00000000000000name: pull-request on: pull_request: types: [opened, reopened, synchronize] jobs: check-fast-forward: runs-on: ubuntu-latest permissions: contents: read # We appear to need write permission for both pull-requests and # issues in order to post a comment to a pull request. pull-requests: write issues: write steps: - name: Checking if fast forwarding is possible uses: sequoia-pgp/fast-forward@main rpm-sequoia-1.10.2/Cargo.lock0000644000001751100000000000100113370ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "aead" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ "crypto-common", "generic-array 0.14.7", ] [[package]] name = "aes" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", "cpufeatures", "zeroize", ] [[package]] name = "aes-gcm" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" dependencies = [ "aead", "aes", "cipher", "ctr", "ghash", "subtle", ] [[package]] name = "aho-corasick" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "anstyle" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anyhow" version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" [[package]] name = "argon2" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", "cpufeatures", "password-hash", ] [[package]] name = "ascii-canvas" version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" dependencies = [ "term", ] [[package]] name = "assert_cmd" version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39bae1d3fa576f7c6519514180a72559268dd7d1fe104070956cb687bc6673bd" dependencies = [ "anstyle", "bstr", "libc", "predicates", "predicates-core", "predicates-tree", "wait-timeout", ] [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "base16ct" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bindgen" version = "0.72.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" dependencies = [ "bitflags", "cexpr", "clang-sys", "itertools 0.13.0", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", "syn", ] [[package]] name = "bit-set" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" [[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 0.14.7", ] [[package]] name = "block-padding" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" dependencies = [ "generic-array 0.14.7", ] [[package]] name = "blowfish" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" dependencies = [ "byteorder", "cipher", ] [[package]] name = "botan" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d4c7647d67c53194fa0740404c6c508880aef2bfe99a9868dbb4b86f090377" dependencies = [ "botan-sys", ] [[package]] name = "botan-sys" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04285fa0c094cc9961fe435b1b279183db9394844ad82ce483aa6196c0e6da38" [[package]] name = "bstr" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" dependencies = [ "memchr", "regex-automata", "serde", ] [[package]] name = "buffered-reader" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db26bf1f092fd5e05b5ab3be2f290915aeb6f3f20c4e9f86ce0f07f336c2412f" dependencies = [ "libc", ] [[package]] name = "bumpalo" version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "camellia" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3264e2574e9ef2b53ce6f536dea83a69ac0bc600b762d1523ff83fe07230ce30" dependencies = [ "byteorder", "cipher", ] [[package]] name = "cast5" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b07d673db1ccf000e90f54b819db9e75a8348d6eb056e9b8ab53231b7a9911" dependencies = [ "cipher", ] [[package]] name = "cbc" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b52a9543ae338f279b96b0b9fed9c8093744685043739079ce85cd58f289a6" dependencies = [ "cipher", ] [[package]] name = "cc" version = "1.2.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" dependencies = [ "find-msvc-tools", "shlex", ] [[package]] name = "cdylib-link-lines" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98eabef08bbdf5afd0b9c0cabb1ac335f7c70447ef095eed85dffd9628b20bc" [[package]] name = "cexpr" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ "nom", ] [[package]] name = "cfb-mode" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "738b8d467867f80a71351933f70461f5b56f24d5c93e0cf216e59229c968d330" dependencies = [ "cipher", ] [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", "windows-link", ] [[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 = "clang-sys" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" dependencies = [ "glob", "libc", "libloading", ] [[package]] name = "cmac" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8543454e3c3f5126effff9cd44d562af4e31fb8ce1cc0d3dcd8f084515dbc1aa" dependencies = [ "cipher", "dbl", "digest", ] [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crypto-bigint" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array 0.14.7", "rand_core 0.6.4", "subtle", "zeroize", ] [[package]] name = "crypto-common" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array 0.14.7", "rand_core 0.6.4", "typenum", ] [[package]] name = "ctr" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ "cipher", ] [[package]] name = "curve25519-dalek" version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", "digest", "fiat-crypto", "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", ] [[package]] name = "dbl" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd2735a791158376708f9347fe8faba9667589d82427ef3aed6794a8981de3d9" dependencies = [ "generic-array 0.14.7", ] [[package]] name = "der" version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", "pem-rfc7468", "zeroize", ] [[package]] name = "des" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" dependencies = [ "cipher", ] [[package]] name = "difflib" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" [[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 = "displaydoc" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "doc-comment" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "780955b8b195a21ab8e4ac6b60dd1dbdcec1dc6c51c0617964b08c81785e12c9" [[package]] name = "dsa" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48bc224a9084ad760195584ce5abb3c2c34a225fa312a128ad245a6b412b7689" dependencies = [ "digest", "num-bigint-dig", "num-traits", "pkcs8", "rfc6979", "sha2", "signature", "zeroize", ] [[package]] name = "dyn-clone" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "eax" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9954fabd903b82b9d7a68f65f97dc96dd9ad368e40ccc907a7c19d53e6bfac28" dependencies = [ "aead", "cipher", "cmac", "ctr", "subtle", ] [[package]] name = "ecb" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a8bfa975b1aec2145850fcaa1c6fe269a16578c44705a532ae3edc92b8881c7" dependencies = [ "cipher", ] [[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.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" dependencies = [ "curve25519-dalek", "ed25519", "rand_core 0.6.4", "serde", "sha2", "subtle", "zeroize", ] [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[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 0.14.7", "group", "hkdf", "pem-rfc7468", "pkcs8", "rand_core 0.6.4", "sec1", "subtle", "zeroize", ] [[package]] name = "ena" version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabffdaee24bd1bf95c5ef7cec31260444317e72ea56c4c91750e8b7ee58d5f1" dependencies = [ "log", ] [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", "windows-sys", ] [[package]] name = "fastrand" version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "ff" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ "rand_core 0.6.4", "subtle", ] [[package]] name = "fiat-crypto" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "find-msvc-tools" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fixedbitset" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "foldhash" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] name = "foreign-types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ "foreign-types-shared", ] [[package]] name = "foreign-types-shared" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[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 = "generic-array" version = "1.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eaf57c49a95fd1fe24b90b3033bee6dc7e8f1288d51494cb44e627c295e38542" dependencies = [ "rustversion", "typenum", ] [[package]] name = "getrandom" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", "js-sys", "libc", "wasi", "wasm-bindgen", ] [[package]] name = "getrandom" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", "r-efi 5.3.0", "wasip2", ] [[package]] name = "getrandom" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" dependencies = [ "cfg-if", "libc", "r-efi 6.0.0", "wasip2", "wasip3", ] [[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 = "glob" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "group" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", "rand_core 0.6.4", "subtle", ] [[package]] name = "hashbrown" version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "foldhash", ] [[package]] name = "hashbrown" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[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 = "iana-time-zone" version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "log", "wasm-bindgen", "windows-core", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "icu_collections" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" dependencies = [ "displaydoc", "potential_utf", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locale_core" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_normalizer" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" dependencies = [ "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", "zerovec", ] [[package]] name = "icu_normalizer_data" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" [[package]] name = "icu_properties" version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "020bfc02fe870ec3a66d93e677ccca0562506e5872c650f893269e08615d74ec" dependencies = [ "icu_collections", "icu_locale_core", "icu_properties_data", "icu_provider", "zerotrie", "zerovec", ] [[package]] name = "icu_properties_data" version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "616c294cf8d725c6afcd8f55abc17c56464ef6211f9ed59cccffe534129c77af" [[package]] name = "icu_provider" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" dependencies = [ "displaydoc", "icu_locale_core", "writeable", "yoke", "zerofrom", "zerotrie", "zerovec", ] [[package]] name = "id-arena" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" [[package]] name = "idea" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "075557004419d7f2031b8bb7f44bb43e55a83ca7b63076a8fb8fe75753836477" dependencies = [ "cipher", ] [[package]] name = "idna" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" dependencies = [ "idna_adapter", "smallvec", "utf8_iter", ] [[package]] name = "idna_adapter" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" dependencies = [ "icu_normalizer", "icu_properties", ] [[package]] name = "indexmap" version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", "hashbrown 0.17.0", "serde", "serde_core", ] [[package]] name = "inout" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ "block-padding", "generic-array 0.14.7", ] [[package]] name = "itertools" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] [[package]] name = "itertools" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" version = "0.3.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "keccak" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" dependencies = [ "cpufeatures", ] [[package]] name = "lalrpop" version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba4ebbd48ce411c1d10fb35185f5a51a7bfa3d8b24b4e330d30c9e3a34129501" dependencies = [ "ascii-canvas", "bit-set", "ena", "itertools 0.14.0", "lalrpop-util", "petgraph", "regex", "regex-syntax", "sha3", "string_cache", "term", "unicode-xid", "walkdir", ] [[package]] name = "lalrpop-util" version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5baa5e9ff84f1aefd264e6869907646538a52147a755d494517a8007fb48733" dependencies = [ "regex-automata", "rustversion", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ "spin", ] [[package]] name = "leb128fmt" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" [[package]] name = "libc" version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libloading" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", "windows-link", ] [[package]] name = "libm" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "linux-raw-sys" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] name = "litemap" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "lock_api" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ "scopeguard", ] [[package]] name = "log" version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "md-5" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ "cfg-if", "digest", ] [[package]] name = "memchr" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memsec" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c797b9d6bb23aab2fc369c65f871be49214f5c759af65bde26ffaaa2b646b492" [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "nettle" version = "7.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2578a3627c28fefb60f1680e20e85f38bd8c8bf98c288b370489a573b0640907" dependencies = [ "getrandom 0.4.2", "libc", "nettle-sys", "thiserror 2.0.18", "typenum", ] [[package]] name = "nettle-sys" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f35502358aa77e598570bbf9a79ad19c4985a1bcd157b5d70197688dafca9b48" dependencies = [ "bindgen", "cc", "libc", "pkg-config", "tempfile", "vcpkg", ] [[package]] name = "new_debug_unreachable" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[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-bigint-dig" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" dependencies = [ "lazy_static", "libm", "num-integer", "num-iter", "num-traits", "rand 0.8.6", "smallvec", "zeroize", ] [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-iter" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", "num-traits", ] [[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 = "ocb3" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c196e0276c471c843dd5777e7543a36a298a4be942a2a688d8111cd43390dedb" dependencies = [ "aead", "cipher", "ctr", "subtle", ] [[package]] name = "once_cell" version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "opaque-debug" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openssl" version = "0.10.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f38c4372413cdaaf3cc79dd92d29d7d9f5ab09b51b10dded508fb90bb70b9222" dependencies = [ "bitflags", "cfg-if", "foreign-types", "libc", "once_cell", "openssl-macros", "openssl-sys", ] [[package]] name = "openssl-macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "openssl-sys" version = "0.9.114" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13ce1245cd07fcc4cfdb438f7507b0c7e4f3849a69fd84d52374c66d83741bb6" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "p256" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" dependencies = [ "ecdsa", "elliptic-curve", "primeorder", "sha2", ] [[package]] name = "p384" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" dependencies = [ "ecdsa", "elliptic-curve", "primeorder", "sha2", ] [[package]] name = "p521" version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" dependencies = [ "base16ct", "ecdsa", "elliptic-curve", "primeorder", "rand_core 0.6.4", "sha2", ] [[package]] name = "parking_lot" version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-link", ] [[package]] name = "password-hash" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", "rand_core 0.6.4", "subtle", ] [[package]] name = "pem-rfc7468" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" dependencies = [ "base64ct", ] [[package]] name = "petgraph" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset", "indexmap", ] [[package]] name = "phf_shared" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" dependencies = [ "siphasher", ] [[package]] name = "pkcs1" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ "der", "pkcs8", "spki", ] [[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.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" [[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 = "potential_utf" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" dependencies = [ "zerovec", ] [[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy", ] [[package]] name = "precomputed-hash" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "predicates" version = "3.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ada8f2932f28a27ee7b70dd6c1c39ea0675c55a36879ab92f3a715eaa1e63cfe" dependencies = [ "anstyle", "difflib", "predicates-core", ] [[package]] name = "predicates-core" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cad38746f3166b4031b1a0d39ad9f954dd291e7854fcc0eed52ee41a0b50d144" [[package]] name = "predicates-tree" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0de1b847b39c8131db0467e9df1ff60e6d0562ab8e9a16e568ad0fdb372e2f2" dependencies = [ "predicates-core", "termtree", ] [[package]] name = "prettyplease" version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", "syn", ] [[package]] name = "primeorder" version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" dependencies = [ "elliptic-curve", ] [[package]] name = "proc-macro2" version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] [[package]] name = "r-efi" version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" [[package]] name = "r-efi" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" [[package]] name = "rand" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "rand_chacha", "rand_core 0.6.4", ] [[package]] name = "rand" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" dependencies = [ "rand_core 0.9.5", ] [[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 0.6.4", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom 0.2.17", ] [[package]] name = "rand_core" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] [[package]] name = "redox_syscall" version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ "bitflags", ] [[package]] name = "regex" version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "rfc6979" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ "hmac", "subtle", ] [[package]] name = "ripemd" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" dependencies = [ "digest", ] [[package]] name = "rpm-sequoia" version = "1.10.2" dependencies = [ "anyhow", "assert_cmd", "cdylib-link-lines", "chrono", "lazy_static", "libc", "sequoia-openpgp", "sequoia-policy-config", "thiserror 2.0.18", ] [[package]] name = "rsa" version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ "const-oid", "digest", "num-bigint-dig", "num-integer", "num-traits", "pkcs1", "pkcs8", "rand_core 0.6.4", "signature", "spki", "subtle", "zeroize", ] [[package]] name = "rustc-hash" version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustix" version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys", "windows-sys", ] [[package]] name = "rustversion" version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "sec1" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", "der", "generic-array 0.14.7", "pkcs8", "subtle", "zeroize", ] [[package]] name = "semver" version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "sequoia-openpgp" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0620e44a7d514adf7df87b44db235f13b81fed7ddc265adb26f014d42626ac47" dependencies = [ "aes", "aes-gcm", "anyhow", "argon2", "base64", "block-padding", "blowfish", "botan", "buffered-reader", "camellia", "cast5", "cbc", "cfb-mode", "chrono", "cipher", "des", "digest", "dsa", "dyn-clone", "eax", "ecb", "ecdsa", "ed25519", "ed25519-dalek", "getrandom 0.2.17", "hkdf", "idea", "idna", "lalrpop", "lalrpop-util", "libc", "md-5", "memsec", "nettle", "num-bigint-dig", "ocb3", "openssl", "openssl-sys", "p256", "p384", "p521", "rand 0.9.4", "rand_core 0.6.4", "regex", "regex-syntax", "ripemd", "rsa", "sha1collisiondetection", "sha2", "sha3", "thiserror 2.0.18", "twofish", "typenum", "win-crypto-ng", "winapi", "x25519-dalek", "xxhash-rust", ] [[package]] name = "sequoia-policy-config" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8c2be5e64986e3192d5714c2334fbfb9e22c8cb41d7ad4583847eb590a8cea1" dependencies = [ "anyhow", "chrono", "sequoia-openpgp", "serde", "thiserror 1.0.69", "toml", ] [[package]] name = "serde" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" dependencies = [ "serde_core", ] [[package]] name = "serde_core" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", "serde", "serde_core", "zmij", ] [[package]] name = "sha1collisiondetection" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f606421e4a6012877e893c399822a4ed4b089164c5969424e1b9d1e66e6964b" dependencies = [ "const-oid", "digest", "generic-array 1.3.5", ] [[package]] name = "sha2" version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "sha3" version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" dependencies = [ "digest", "keccak", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signature" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", "rand_core 0.6.4", ] [[package]] name = "siphasher" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[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 = "stable_deref_trait" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "string_cache" version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" dependencies = [ "new_debug_unreachable", "parking_lot", "phf_shared", "precomputed-hash", ] [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "synstructure" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tempfile" version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", "getrandom 0.4.2", "once_cell", "rustix", "windows-sys", ] [[package]] name = "term" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8c27177b12a6399ffc08b98f76f7c9a1f4fe9fc967c784c5a071fa8d93cf7e1" dependencies = [ "windows-sys", ] [[package]] name = "termtree" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f50febec83f5ee1df3015341d8bd429f2d1cc62bcba7ea2076759d315084683" [[package]] name = "thiserror" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ "thiserror-impl 2.0.18", ] [[package]] name = "thiserror-impl" version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "thiserror-impl" version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tinystr" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "toml" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", ] [[package]] name = "twofish" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a78e83a30223c757c3947cd144a31014ff04298d8719ae10d03c31c0448c8013" dependencies = [ "cipher", ] [[package]] name = "typenum" version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" [[package]] name = "unicode-ident" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[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 = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "wait-timeout" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" dependencies = [ "libc", ] [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "wasi" version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" version = "1.0.1+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" dependencies = [ "wit-bindgen 0.46.0", ] [[package]] name = "wasip3" version = "0.4.0+wasi-0.3.0-rc-2026-01-06" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" dependencies = [ "wit-bindgen 0.51.0", ] [[package]] name = "wasm-bindgen" version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" dependencies = [ "bumpalo", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.118" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" dependencies = [ "unicode-ident", ] [[package]] name = "wasm-encoder" version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" dependencies = [ "leb128fmt", "wasmparser", ] [[package]] name = "wasm-metadata" version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" dependencies = [ "anyhow", "indexmap", "wasm-encoder", "wasmparser", ] [[package]] name = "wasmparser" version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" dependencies = [ "bitflags", "hashbrown 0.15.5", "indexmap", "semver", ] [[package]] name = "win-crypto-ng" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "99abfb435a71e54ab2971d8d8c32f1a7e006cdbf527f71743b1d45b93517bb92" dependencies = [ "cipher", "doc-comment", "rand_core 0.6.4", "winapi", "zeroize", ] [[package]] name = "winapi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ "windows-sys", ] [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", "windows-link", "windows-result", "windows-strings", ] [[package]] name = "windows-implement" version = "0.60.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "windows-interface" version = "0.59.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" [[package]] name = "windows-result" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ "windows-link", ] [[package]] name = "windows-sys" version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] [[package]] name = "wit-bindgen" version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" [[package]] name = "wit-bindgen" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" dependencies = [ "wit-bindgen-rust-macro", ] [[package]] name = "wit-bindgen-core" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" dependencies = [ "anyhow", "heck", "wit-parser", ] [[package]] name = "wit-bindgen-rust" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" dependencies = [ "anyhow", "heck", "indexmap", "prettyplease", "syn", "wasm-metadata", "wit-bindgen-core", "wit-component", ] [[package]] name = "wit-bindgen-rust-macro" version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" dependencies = [ "anyhow", "prettyplease", "proc-macro2", "quote", "syn", "wit-bindgen-core", "wit-bindgen-rust", ] [[package]] name = "wit-component" version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" dependencies = [ "anyhow", "bitflags", "indexmap", "log", "serde", "serde_derive", "serde_json", "wasm-encoder", "wasm-metadata", "wasmparser", "wit-parser", ] [[package]] name = "wit-parser" version = "0.244.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" dependencies = [ "anyhow", "id-arena", "indexmap", "log", "semver", "serde", "serde_derive", "serde_json", "unicode-xid", "wasmparser", ] [[package]] name = "writeable" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "x25519-dalek" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ "curve25519-dalek", "rand_core 0.6.4", "zeroize", ] [[package]] name = "xxhash-rust" version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" [[package]] name = "yoke" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" dependencies = [ "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zerocopy" version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "zerofrom" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zeroize" version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "zerotrie" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" dependencies = [ "displaydoc", "yoke", "zerofrom", ] [[package]] name = "zerovec" version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "zmij" version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" rpm-sequoia-1.10.2/Cargo.toml0000644000000041020000000000100113520ustar # 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" rust-version = "1.85" name = "rpm-sequoia" version = "1.10.2" authors = ["Neal H. Walfield "] build = "build.rs" autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "An implementation of the RPM PGP interface using Sequoia." homepage = "https://sequoia-pgp.org/" readme = "README.md" keywords = [ "cryptography", "openpgp", "pgp", "signing", ] categories = [ "cryptography", "authentication", ] license = "LGPL-2.0-or-later" repository = "https://github.com/rpm-software-management/rpm-sequoia" [badges.maintenance] status = "actively-developed" [features] crypto-botan = ["sequoia-openpgp/crypto-botan"] crypto-botan2 = ["sequoia-openpgp/crypto-botan2"] crypto-cng = ["sequoia-openpgp/crypto-cng"] crypto-nettle = ["sequoia-openpgp/crypto-nettle"] crypto-openssl = ["sequoia-openpgp/crypto-openssl"] crypto-rust = ["sequoia-openpgp/crypto-rust"] default = ["crypto-nettle"] [lib] name = "rpm_sequoia" crate-type = ["cdylib"] path = "src/lib.rs" [[test]] name = "symbols" path = "tests/symbols.rs" [dependencies.anyhow] version = "1" [dependencies.chrono] version = "0.4" features = ["std"] default-features = false [dependencies.lazy_static] version = "1" [dependencies.libc] version = "0.2" [dependencies.sequoia-openpgp] version = "2" default-features = false [dependencies.sequoia-policy-config] version = "0.8" [dependencies.thiserror] version = ">=1, <3" [dev-dependencies.assert_cmd] version = "2.0" [build-dependencies.anyhow] version = "1" [build-dependencies.cdylib-link-lines] version = "0.1.4" rpm-sequoia-1.10.2/Cargo.toml.orig000064400000000000000000000030031046102023000150320ustar 00000000000000[package] name = "rpm-sequoia" description = "An implementation of the RPM PGP interface using Sequoia." version = "1.10.2" authors = ["Neal H. Walfield "] homepage = "https://sequoia-pgp.org/" repository = "https://github.com/rpm-software-management/rpm-sequoia" readme = "README.md" keywords = ["cryptography", "openpgp", "pgp", "signing"] categories = ["cryptography", "authentication"] license = "LGPL-2.0-or-later" edition = "2021" rust-version = "1.85" build = "build.rs" [badges] maintenance = { status = "actively-developed" } [dependencies] anyhow = "1" chrono = { version = "0.4", default-features = false, features = [ "std" ] } lazy_static = "1" libc = "0.2" sequoia-openpgp = { version = "2", default-features = false } sequoia-policy-config = "0.8" thiserror = { version = ">=1, <3" } [build-dependencies] anyhow = "1" cdylib-link-lines = "0.1.4" [dev-dependencies] assert_cmd = "2.0" [lib] crate-type = ["cdylib"] [features] # To use a different cryptographic backend, e.g., OpenSSL, do: # # cargo build --release --no-default-features --features crypto-openssl # We explicitly do not want to enable Sequoia's decompression support. # Hence we only select a crypto backend. default = ["crypto-nettle"] crypto-nettle = ["sequoia-openpgp/crypto-nettle"] crypto-rust = ["sequoia-openpgp/crypto-rust"] crypto-cng = ["sequoia-openpgp/crypto-cng"] crypto-openssl = ["sequoia-openpgp/crypto-openssl"] crypto-botan = ["sequoia-openpgp/crypto-botan"] crypto-botan2 = ["sequoia-openpgp/crypto-botan2"] rpm-sequoia-1.10.2/LICENSE.txt000064400000000000000000000627341046102023000140060ustar 00000000000000rpm-sequoia is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. rpm-sequoia is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. --- GNU LIBRARY GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1991 Free Software Foundation, Inc. 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. [This is the first released version of the library GPL. It is numbered 2 because it goes with version 2 of the ordinary GPL.] Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Library General Public License, applies to some specially designated Free Software Foundation software, and to any other libraries whose authors decide to use it. You can use it for your libraries, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library, or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link a program with the library, you must provide complete object files to the recipients so that they can relink them with the library, after making changes to the library and recompiling it. And you must show them these terms so they know their rights. Our method of protecting your rights has two steps: (1) copyright the library, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the library. Also, for each distributor's protection, we want to make certain that everyone understands that there is no warranty for this free library. If the library is modified by someone else and passed on, we want its recipients to know that what they have is not the original version, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that companies distributing free software will individually obtain patent licenses, thus in effect transforming the program into proprietary software. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License, which was designed for utility programs. This license, the GNU Library General Public License, applies to certain designated libraries. This license is quite different from the ordinary one; be sure to read it in full, and don't assume that anything in it is the same as in the ordinary license. The reason we have a separate public license for some libraries is that they blur the distinction we usually make between modifying or adding to a program and simply using it. Linking a program with a library, without changing the library, is in some sense simply using the library, and is analogous to running a utility program or application program. However, in a textual and legal sense, the linked executable is a combined work, a derivative of the original library, and the ordinary General Public License treats it as such. Because of this blurred distinction, using the ordinary General Public License for libraries did not effectively promote software sharing, because most developers did not use the libraries. We concluded that weaker conditions might promote sharing better. However, unrestricted linking of non-free programs would deprive the users of those programs of all benefit from the free status of the libraries themselves. This Library General Public License is intended to permit developers of non-free programs to use free libraries, while preserving your freedom as a user of such programs to change the free libraries that are incorporated in them. (We have not seen how to achieve this as regards changes in header files, but we have achieved it as regards changes in the actual functions of the Library.) The hope is that this will lead to faster development of free libraries. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, while the latter only works together with the library. Note that it is possible for a library to be covered by the ordinary General Public License rather than by this special one. GNU LIBRARY GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License Agreement applies to any software library which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Library General Public License (also called "this License"). Each licensee is addressed as "you". A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) "Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. 1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) The modified work must itself be a software library. b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. 4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. 5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. 6. As an exception to the Sections above, you may also compile or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) b) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. c) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. d) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. 7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. 10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 13. The Free Software Foundation may publish revised and/or new versions of the Library General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. 14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! rpm-sequoia-1.10.2/Makefile000064400000000000000000000020651046102023000136120ustar 00000000000000# To download this Makefile, run: # # $ wget 'https://gitlab.com/sequoia-pgp/common-ci/-/raw/main/Makefile?ref_type=heads&inline=false' # # To update this Makefile, run: # # $ make Makefile all: @echo "Try:" @echo "$ make deny" update: Makefile .PHONY: deny deny: deny.toml cargo deny check # The configuration files that we can update. .PHONY: deny.toml deny.toml: $(call update-file "$@") # We can also update the Makefile. Makefile: $(call update-file "$@") # Download the latest version of the configuration file. If it # changed, save the old version to $@.bak, and show a diff. define update-file = T=$$(mktemp); \ wget 'https://gitlab.com/sequoia-pgp/common-ci/-/raw/main/$@?ref_type=heads&inline=false' -O "$$T" \ && echo "***************" \ && if test -e "$@"; then \ if ! diff -u "$$T" "$@"; then \ echo "*** $@ was out of date."; \ cp "$@" "$@.bak"; \ echo "(old version saved to $@.bak)"; \ mv "$$T" "$@"; \ else \ echo "$@ was up to date."; \ rm "$$T"; \ fi; \ else \ echo "Downloaded $@"; \ mv "$$T" "$@"; \ fi endef rpm-sequoia-1.10.2/README.md000064400000000000000000000125411046102023000134310ustar 00000000000000This library provides an implementation of the [rpm]'s [pgp interface] using [Sequoia]. [rpm]: https://github.com/rpm-software-management/rpm [pgp interface]: https://github.com/rpm-software-management/rpm/blob/master/include/rpm/rpmpgp.h [Sequoia]: https://sequoia-pgp.org # Configuration This library's [crypto policy] can be customized. It finds the configuration file by checking the following in turn: - the `RPM_SEQUOIA_CRYPTO_POLICY` environment variable, - `/etc/crypto-policies/back-ends/rpm-sequoia.config`, - the `SEQUOIA_CRYPTO_POLICY` environment variable, and finally, - `/etc/crypto-policies/back-ends/sequoia.config`. Only the first configuration file that is present is used. If an environment is set to the empty string, then an empty configuration file is used. That is, the default policy is used. Thus, if `RPM_SEQUOIA_CRYPTO_POLICY` is not set, and `/etc/crypto-policies/back-ends/rpm-sequoia.config`, the latter will be used. In this case, `SEQUOIA_CRYPTO_POLICY` and `/etc/crypto-policies/back-ends/sequoia.config` will be completely ignored. Refer to the [Fedora Crypto Policy] project for information about the crypto policy. [crypto policy]: https://docs.rs/sequoia-policy-config/latest/sequoia_policy_config/ [Sequoia's default policy]: https://docs.sequoia-pgp.org/sequoia_openpgp/policy/struct.StandardPolicy.html [Fedora Crypto Policy]: https://gitlab.com/redhat-crypto/fedora-crypto-policies/ # Building To build, you need [rustc] (version 1.85 or later), cargo, and [nettle-devel], which is the cryptographic library that Sequoia uses by default. [rustc]: https://packages.fedoraproject.org/pkgs/rust/rust/ [nettle-devel]: https://packages.fedoraproject.org/pkgs/nettle/nettle-devel ``` $ sudo dnf install cargo rustc clang pkg-config nettle-devel $ mkdir /tmp/rpm $ cd /tmp/rpm $ git clone https://github.com/rpm-software-management/rpm-sequoia.git Cloning into 'rpm-sequoia'... done. $ cd rpm-sequoia $ PREFIX=/usr LIBDIR="\${prefix}/lib64" \ cargo build --release && cargo test --release Updating crates.io index ... test result: ok. ... ``` To use a different cryptographic backend, you need to disable the default backend, and select your preferred backend. For instance, to use Sequoia's OpenSSL backend, you would compile `rpm-sequoia` as follows: ``` $ cargo build --release --no-default-features --features crypto-openssl ``` See [`sequoia-openpgp`'s README] for the list of currently supported cryptographic backends. [`sequoia-openpgp`'s README]: https://gitlab.com/sequoia-pgp/sequoia#features The rpm-sequoia artifacts (the .a, .so, and the .pc files) are placed in the build directory, which, in this case, is `/tmp/rpm/rpm-sequoia/target/release`. We also set two environment variables when calling `cargo build`: * `PREFIX` is the prefix that will be used in the generated `rpm-sequoia.pc` file. It defaults to `/usr/local`. * `LIBDIR` is the installed library path listed in the generated metadata. It can be an absolute path or one based on `${prefix}`, and defaults to `${prefix}/lib`. # Testing `rpm-sequoia` has a minimal test suite. Testing is instead done via `rpm`'s test suite. # rpm 4.20 As of version 4.20, `rpm` uses containers to run its test suite. The simplest solution is to build a container with the `rpm` test suite, copy `rpm-sequoia` on top of that (for example in another container layer), run `ldconfig`, and then run the tests, like so: ``` $ cd /tmp/rpm $ git clone https://github.com/rpm-software-management/rpm.git Cloning into 'rpm'... done. $ cd rpm/tests $ podman build --target full -t rpm-tests -f Dockerfile .. $ cd /tmp/rpm/rpm-sequoia $ podman build -t rpm-tests-sequoia -f tests/Dockerfile . $ podman run --privileged -it --rm --read-only --tmpfs /tmp -v /tmp/rpm/rpm/:/srv:z --workdir /srv -e ROOTLESS=1 rpm-tests-sequoia rpmtests -k OpenPGP -k signature -k rpmkeys -k digest ``` To get tracing output, set the `RPM_TRACE` environment variable to 1. This can be passed by adding `-e RPM_TRACE=1` to the last command, like so: ``` $ podman run --privileged -it --rm --read-only --tmpfs /tmp -v /tmp/rpm/rpm/:/srv:z --workdir /srv -e ROOTLESS=1 -e RPM_TRACE=1 rpm-tests-sequoia rpmtests -k OpenPGP -k signature -k rpmkeys -k digest ``` If a tests fails, its log will be saved to `/tmp/rpm/rpm/rpmtests.dir/xxx/rpmtests.log` where `xxx` is the test's number. The entire run's log is saved to `/tmp/rpm/rpm/rpmtests.log`. Note: these are exposed to the file system due to how we run `podman`. # For rpm 4.18 To build and test rpm-sequoia for rpm version 4.18, do: ``` $ cd /tmp/rpm $ git clone git@github.com:rpm-software-management/rpm.git Cloning into 'rpm'... done. $ cd rpm $ git checkout rpm-4.18.1-release Switched to a new branch 'rpm-4.18.1-release' $ sudo dnf install automake autoconf gettext-devel libtool tar zlib-devel file-devel libarchive-devel popt-devel sqlite-devel lua-devel fakechroot $ autoreconf -fis ... $ mkdir b $ cd b $ export PKG_CONFIG_PATH=/tmp/rpm/rpm-sequoia/target/release $ export LD_LIBRARY_PATH=/tmp/rpm/rpm-sequoia/target/release $ ../configure --prefix=/ --with-crypto=sequoia $ make $ make check ``` # Symbols test without Rust toolchain To run the symbols test binary without having a Rust toolchain installed, set an environment variable called `TEST_DONT_BUILD_LIB` with any value. This of course requires you to build the library before the test would be executed. rpm-sequoia-1.10.2/build.rs000064400000000000000000000151611046102023000136200ustar 00000000000000use std::env; use std::fs::File; use std::io::Write; use std::path::{Path, PathBuf}; use std::collections::HashMap; use anyhow::Result; struct PkgConfigTemplate { cargo_toml: HashMap, pc_in: String, } impl PkgConfigTemplate { /// Read the pkg-config template file. fn new(src: P, pc_in: S) -> Result where P: AsRef, S: AsRef { let src = src.as_ref(); let mut pc_in_ = PathBuf::from(src); pc_in_.push(pc_in.as_ref()); let pc_in = pc_in_; let pc_in = std::fs::read_to_string(pc_in)?; let cargo_toml = HashMap::from([ ("NAME".to_string(), env!("CARGO_PKG_NAME").to_string()), ("DESCRIPTION".to_string(), env!("CARGO_PKG_DESCRIPTION").to_string()), ("VERSION".to_string(), env!("CARGO_PKG_VERSION").to_string()), ("HOMEPAGE".to_string(), env!("CARGO_PKG_HOMEPAGE").to_string()), ("REQUIRES".to_string(), if cfg!(feature = "crypto-botan") { "botan-3" } else if cfg!(feature = "crypto-botan2") { "botan-2" } else if cfg!(feature = "crypto-nettle") { "nettle" } else if cfg!(feature = "crypto-openssl") { "libssl" } else if cfg!(feature = "crypto-cng") { "" } else if cfg!(feature = "crypto-rust") { "" } else { panic!("No cryptographic backend selected. Try: \ \"cargo build --no-default-features \ --features crypto-openssl\"") }.to_string()), ]); Ok(PkgConfigTemplate { cargo_toml, pc_in, }) } /// Perform substitutions on the pkg-config file based on what was /// read from the Cargo.toml file and the provided substitution /// map. /// /// The mappings in the substitution map are preferred to those in /// the Cargo.toml file. /// /// Substitutions take the form of keys and values where the /// string @KEY@ is substituted with the value of KEY. So, /// @VERSION@ is substituted with the value of VERSION. fn substitute(&self, map: HashMap) -> Result { let mut pc: String = self.pc_in.clone(); for (key, value) in map.iter().chain(self.cargo_toml.iter()) { pc = pc.replace(&format!("@{}@", key), value); } Ok(pc) } } fn main() -> Result<(), anyhow::Error> { // Generate // ${CARGO_TARGET_DIR}/${PROFILE}/rpm-sequoia{-uninstalled}.pc // from ${SRC}/rpm-sequoia.pc.in. let src = env::current_dir()?; // Location of the build directory (e.g., // `/tmp/rpm-sequoia/debug`). let mut build_dir = PathBuf::from(&src); if let Some(target_dir) = env::var_os("CARGO_TARGET_DIR") { // Note: if CARGO_TARGET_DIR is absolute, this will first // clear build_dir, which is what we want. build_dir.push(target_dir); } else { build_dir.push("target"); } let profile = env::var_os("PROFILE").expect("PROFILE not set"); build_dir.push(&profile); let pc_in = PkgConfigTemplate::new(&src, "rpm-sequoia.pc.in")?; // Generate rpm-sequoia.pc. let mut pc = build_dir.clone(); pc.push("rpm-sequoia.pc"); let prefix = env::var_os("PREFIX"); let prefix: &str = match prefix.as_ref().map(|s| s.to_str()) { Some(Some(s)) => s, Some(None) => Err(anyhow::anyhow!("PREFIX contains invalid UTF-8"))?, None => "/usr/local", }; let libdir = env::var_os("LIBDIR"); let libdir: &str = match libdir.as_ref().map(|s| s.to_str()) { Some(Some(s)) => s, Some(None) => Err(anyhow::anyhow!("LIBDIR contains invalid UTF-8"))?, None => "${prefix}/lib", }; let content = pc_in.substitute(HashMap::from([ ("PREFIX".to_string(), prefix.into()), ("LIBDIR".to_string(), libdir.into()), ]))?; let mut pc = File::create(&pc).unwrap_or_else( |_| panic!("Creating {:?} (CARGO_TARGET_DIR: {:?})", pc, env::var_os("CARGO_TARGET_DIR"))); pc.write_all(content.as_bytes())?; // Generate rpm-sequoia-uninstalled.pc. let mut pc = build_dir.clone(); pc.push("rpm-sequoia-uninstalled.pc"); let content = pc_in.substitute(HashMap::from([ ("PREFIX".to_string(), build_dir.to_str() .expect("build directory is not valid UTF-8").to_string()), ("LIBDIR".to_string(), "${prefix}".into()), ]))?; let mut pc = File::create(&pc).unwrap_or_else( |_| panic!("Creating {:?} (CARGO_TARGET_DIR: {:?})", pc, env::var_os("CARGO_TARGET_DIR"))); pc.write_all(content.as_bytes())?; // Rerun if... println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-changed=Cargo.toml"); println!("cargo:rerun-if-changed=rpm-sequoia.pc.in"); println!("cargo:rerun-if-env-changed=PREFIX"); println!("cargo:rerun-if-env-changed=LIBDIR"); println!("cargo:rerun-if-env-changed=PROFILE"); println!("cargo:rerun-if-env-changed=CARGO_TARGET_DIR"); // Set the soname. let arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap(); let os = env::var("CARGO_CFG_TARGET_OS").unwrap(); let env = env::var("CARGO_CFG_TARGET_ENV").unwrap(); // We do not care about `_pre` and such. let major = env::var("CARGO_PKG_VERSION_MAJOR").unwrap(); let minor = env::var("CARGO_PKG_VERSION_MINOR").unwrap(); let patch = env::var("CARGO_PKG_VERSION_PATCH").unwrap(); // libdir might contain "${prefix}". Replace it with // the actual prefix value if found. let libdir_resolved = libdir.replace("${prefix}", prefix); let linker_lines = cdylib_link_lines::shared_object_link_args( "rpm_sequoia", &major, &minor, &patch, &arch, &os, &env, PathBuf::from(libdir_resolved), build_dir.clone(), ); for line in linker_lines { println!("cargo:rustc-cdylib-link-arg={}", line); } #[cfg(unix)] { // Create a symlink. let mut create = true; let mut link = build_dir.clone(); link.push(format!("librpm_sequoia.so.{}", major)); if let Ok(current) = std::fs::read_link(&link) { if current.to_str() == Some("librpm_sequoia.so") { // Do nothing. create = false; } else { // Invalid. std::fs::remove_file(&link)?; } } if create { std::os::unix::fs::symlink("librpm_sequoia.so", link)?; } } Ok(()) } rpm-sequoia-1.10.2/deny.toml000064400000000000000000000043501046102023000140050ustar 00000000000000# This file is maintained in https://gitlab.com/sequoia-pgp/common-ci. # You can fetch it as follows: # # $ wget 'https://gitlab.com/sequoia-pgp/common-ci/-/raw/main/deny.toml?ref_type=heads&inline=false' -O deny.toml # # You should add that file as is to your project. # # You should also consider adding the Makefile # # $ wget 'https://gitlab.com/sequoia-pgp/common-ci/-/raw/main/Makefile?ref_type=heads&inline=false' -O Makefile # # Which makes it easy to keep that file up to date by doing: # # $ make [advisories] ignore = [ # These are due to sequoia-tpm's dependency on structopt. # sequoia-keystore crate actually use those. So we're fine. Remove # these once sequoia-tpm no longer users structopt. "RUSTSEC-2021-0139", "RUSTSEC-2021-0145", # Unfixable (as of rsa 0.9.6) marvin attack. "RUSTSEC-2023-0071", "RUSTSEC-2020-0159", "RUSTSEC-2020-0071", # chrono not affected by time 0.1 issue # fehler is unmaintained. # # fehler is used by subplot and thus an indirect dependency. Remove # when a new version subplot is released without fehler. See # https://gitlab.com/subplot/subplot/-/issues/340. "RUSTSEC-2023-0067", # yaml-rust is unmaintained. # # yaml-rust is used by subplot/roadmap/serde_yaml thus an indirect # dependency. Remove when a new version of roadmap is released that # uses a newer version of serde_yaml. See # https://gitlab.com/larswirzenius/roadmap/-/issues/13 "RUSTSEC-2024-0320", # instant is unmaintained. # # instant is used by indicatif and thus an indirect dependency. # Remove when a new version of indicatif is released that drops the # dependency. "RUSTSEC-2024-0384", # paste is unmaintained as of 2025-03-07 "RUSTSEC-2024-0436", # humantime is unmaintained. "RUSTSEC-2025-0014", ] yanked = "deny" [bans] multiple-versions = "allow" deny = [ # does not have responsible disclosure policy: # https://github.com/briansmith/ring#bug-reporting {name = "ring"}, ] [licenses] allow = [ "Apache-2.0", "Apache-2.0 WITH LLVM-exception", "BSD-3-Clause", "BSD-2-Clause", "BSL-1.0", "CC0-1.0", "CC-BY-4.0", "GPL-2.0", "GPL-3.0", "ISC", "LGPL-2.0", "LGPL-3.0", "MIT", "MIT-0", "MPL-2.0", "Unicode-DFS-2016", "Unicode-3.0", "Zlib", ] rpm-sequoia-1.10.2/doc/release-checklist.md000064400000000000000000000031061046102023000166250ustar 00000000000000This is a checklist for doing releases. 1. Start from `origin/main`, create a branch `staging`. 1. Switch to the branch. 1. Bump the version in `Cargo.toml` to `XXX`. 1. Bump the version in `README.md` to `XXX`. 1. Run `cargo check` (this implicitly updates `Cargo.lock`). 1. Update dependencies and run tests. - Use the exact Rust toolchain version of the current Sequoia MSRV (refer to `Cargo.toml`): `rustup default 1.xx` - Run `cargo update` to update the dependencies. If some dependency is updated and breaks due to our MSRV, find a good version of that dependency and select it using e.g. `cargo update -p backtrace --precise 3.46`. - Run `cargo build && cargo check` 1. Commit changes to `Cargo.toml` and `Cargo.lock`. 1. Make a commit with the message `Release XXX.`. - Push to github, and create a merge request. Don't auto merge!!! 1. Make sure `cargo publish` works: - `mkdir -p /tmp/sequoia-staging` - `cd /tmp/sequoia-staging` - `git clone git@github.com:rpm-software-management/rpm-sequoia.git` - `cd rpm-sequoia` - `git checkout origin/staging` - `cargo publish --features crypto-nettle --dry-run` 1. Wait until CI and `cargo publish ... --dry-run` are successful. In case of errors, correct them, and restart. 1. Merge the merge request. 1. Run `cargo publish --features crypto-nettle`. 1. Make a tag `vXXX` with the message `Release XXX.` signed with an offline-key, which has been certified by our `openpgp-ca@sequoia-pgp.org` key. 1. Push the signed tag `vXXX`. rpm-sequoia-1.10.2/openpgp-policy.toml000064400000000000000000001655321046102023000160250ustar 00000000000000version = 0 commit_goodlist = [] [authorization."Neal H. Walfield "] sign_commit = true sign_tag = true sign_archive = true add_user = true retire_user = true audit = true keyring = """ -----BEGIN PGP PUBLIC KEY BLOCK----- Comment: F717 3B3C 7C68 5CD9 ECC4 191B 74E4 45BA 0E15 C957 Comment: Neal H. Walfield (Code Signing Key) Comment: Neal H. Walfield Comment: Neal H. Walfield Comment: Neal H. Walfield Comment: Neal H. Walfield xsEhBFUjmukBDqCpmVI7Ve+2xTFSTG+mXMFHml63/Yai2nqxBk9gBfQfRFIjMt74 whGG3LA1ccH2vtsUMbm+F9d+hmzfiErloOVeamfSTCXVPHl4vuVRGXoH5tL09bbm LE7cidDj49GelOxbfqHKVw3+Fd2zLlQdiaWYJ7CdRDZOT22zEx+6n59/gO5WNnym aib+nXWAbXJ+pU7fzHU4PlhDXT/FfV2mzyQg6AiToColG5/CfOBp+WP6pAU4eNIx IlKYxzLnyAPUy+nuqojTJ+Ni16Jve/hpKM7G1TGAzjzdC5zSVMELi/5kdldCD9Hg 7sqw6RPlxbH52bryenYfLyfIaInHCHKmqWRAu3fxMcZ65qo8khYrzZngYewVAafR i/GSZmKxzntmP0GYziceGsbF8dEFF1scfebGKuDqtBhQ0MMuxTbTLg1+KKN8rhqW Teikrt0JPbD1viaVX7Z7G12fZ8lBU4sjd3HGO5EK+3Cs8bjLXbzb8UIz7u28u7Dq VQB4jhgh+IXyZzaeELV9KPr5IVNjT9K9gX6JJlVSi5BnxUVY0pEhtKiiLO6PCC2N PenWkWpp3UEZ5ILnLhlmPe7ICiBCK1IQtNHEAfDalKO1t/gWKi0JlOqv2j9ER68A EQEAAc0jTmVhbCBILiBXYWxmaWVsZCA8bmVhbEBnMTBjb2RlLmNvbT7CwUoEMAEK ACAWIQSPF3dxGKM92pukjmKqyzJDYwBS2QUCWc01BwIdIAAKCRCqyzJDYwBS2R08 DqCVcQ7mbbsFgEX/0SpcrWIYznMFqrRwIYuYysJxmhUYTHqV1FJiECjVBPOLabov /DSHlCHi2GrpImI4ReKgLDdYAMlAL5zca21lDHGwtghYAXkWMqyQa2SIL5+6+cNB A1tlEPcVAknLqg7At92VHOQMBKaQLR46Dt0BowhnrKbPC/ICnquO7g5nhXMfwN0+ tA+3QDp6nbAjEXDF94zKgG1PXgHTgB3F3oMUipJo5xMfzXJZ0EgsDJiXRjRAu7Lp 44nv6eKJdUw1mVKmo+BfbChC99LuqSNQornEinXUVv/ecjIuWqK10w18BLFFZCnX S+WsPFWSQ4Bl0LIfA+g/TACBsq8gBybkxm0GE/YQw1oSP9VLPEQUaJspeIp1jIW6 wEOLIbPB3KWj/RGvZddDhXz5y1rSOUhg3ObAcC9ytWmpAHr4Q/4onOThL3e7VFNi SK7rEX19TD2dGLMfOiD+lsDrbcmYQL+1bzpQPjO1WlzA8/rBMe/EDjWTV9p7xiC2 Y/BIbph6WgaFX+9VioJ5CIbFssOfkl9VOOStdhsG55+cbv+1xkJ5kUEKm9sjpDO/ GUK9+kI6Yge2I9W3+DeT1PAzwyu0Cj2ePRYEJkp703KXggNfiIjCwWUEEwEKACQF AlUjpZACGwMFCRLMAwAICwkIBw0MCwoFFQoJCAsCHgECF4AAIQkQqssyQ2MAUtkW IQSPF3dxGKM92pukjmKqyzJDYwBS2RZGDpsEbOO6HrU2F5SK4Kc03ndtXi0jpCci Z+nDjfm6TOEBDbYx5YUOsYwnfXt7aWSSNikRTyEZHWA3BExE2J7ddNG8OGIhAnAH +USj4cTmEwlwTdAMyXSVL1Hp82Vsr9CcdJNU6jAxi0QDJk9d8EvDksbQUy8fuDbs dgKb16QjL2nsEZ2Gd7fKluK3I8pTU81cbEA7s/4d3sQzGCLomHQ+75436gypcglN q84TWtpeMAUYku7pl8Do1oj8lryQBqnjKJTRXic3gtN4f7YoRkrCIcRXbeCCdc2k bQbcp8CEjI/NPNTezyXn8Sk6RsJitf+L5Op3yPmcagay2ycjRdfMdPA6V4VC+e8H MAFzSWigdBPrCP6e/7Wo94sMy4lrQtjxHaY7uAqk025KrXMti9KvK5yL0xzww1yh WAHEB6Oso2DS3/FRBAKhn+n7gp8HwjyDAieXP1leL1RToO2a0jJ+MNfWOmWRnGbr U5op9nLaseW4PopTO9G4m+gSJxuTgxiP7Ovo/eD8dicaoEtgvLEi0mSGpZUgdZXd pB8Eo/wiD6wFD1NkMRWYRSlS0b3ataC91z0DmPpoEZ+5F36ZzPgLmvxqN/FCFwb0 bMmDyHo5pAH+niuAi1rNIU5lYWwgSC4gV2FsZmllbGQgPG5lYWxAZ251cGcub3Jn PsLBZQQTAQoAOwIbAwgLCQgHDQwLCgUVCgkICwIeAQIXgBYhBI8Xd3EYoz3am6SO YqrLMkNjAFLZBQJoPu+wBQkU/IguAAoJEKrLMkNjAFLZwg0On0fD2wlUllFPdsRf auqi2e23/JdQIQ0VQNA/r92xXEbfpxO9D03FqzQI0wLgCSJs0yxOC8rmOdCcMnLd 6iwAFb/YJLpDi9BLEfsAJ+0g2oQMMbuSrUPqm7kXEEVhiffLlEl/1PEE5jSGFa1D KTciFL7admDuUjdwXJ5Kat9thQXhO8HZ3SoBoLqodCFT2sC7pmRJj7TOGSOIDtY5 lvd7ESece7eQxp1ow3HiUaLjHGBWYLdpTNzQ8Y7Kw5kY0APeSwgDO85z8ZzceQWS 33Mxb8Um4UZ+0vlIlnPR48D4H5HYuGjUj2qOWo3uyvyBTisfT+TYSrtIb+kalKfr vpXyiRSq8lQorTWc1ikWQvX3rh+mqACbL8l8BCK1Id3CzkE0mAEKiQFPUXU8GkJu 9JjiwUyjqhhOd1yqfvNc5rOx+qe3r+wdYP8FLVny2HtoSRFnSxgs1entWp3FI196 p84tidugy5KkZ6HosH5dickEV1labKKoIwtX/IpFPdR4pUBlzVYH0OojWYCNAXBt d8pFpe0NTkaN1GMSBZhp/Iwo0vPFOaQT/QyU7sNaimZz9OwPb2dvEQ6Llo+RVY5t TUtGZUIEFW7V2yjVLHzhuns4vjERhmHzCcLBZQQTAQoAOwIbAwgLCQgHDQwLCgUV CgkICwIeAQIXgBYhBI8Xd3EYoz3am6SOYqrLMkNjAFLZBQJmzCnUBQkTDIO7AAoJ EKrLMkNjAFLZErAOn0fLiphDTNzyRbZeSRL96PjRNU/RfnpWRF4OucqUY5SfwVg1 MaEUi3N079l33YGQjo1X/ylgc55DRQcw1o+sQEEfjn9vjqV6fjm2xY8rNUMd4/Cg WfLik7tOwF/Mb4tdNubFeEAPIMz8UKhBedq0oHqW/EhaAhoST82PGRl+TDqcbMQ7 ZwhEHjok5tbY5bOZYpHOkaj/HuJg9ZzZS7YJgPksjFctm9aHN5406M+N9Cxz5MP0 Ci/uQmam2je+/nIH9Q3ASmtNKEKxsPzm3FdDUt4Ogmuvf/42kExJFtQSmPDFCUEt M4IW3ShzUOfkIeS/hamWcNm7AT7VgOnccp/HkddROX5Fz10vc5gqzbW18pU99BF1 2URLm/O7RCqMfKgp66BCuTpj8Y+fJ9bkIBp/zHdzpV5FytAtpTiygODcVUdUH8SS 3ip3fM2ozbNXf0V/KdXvtrh1Ug1Rp5fwEYMan705XZetOO8HmcST3hQL/xivWBHa 4zdS6tMy+wzxgynmBP/ievURbwHCHduEWm3DDuInuRa8sL5gg6+lHxh7FahJsd4V unl7N96w25Jv/u46Dpkiar8mTnwszuzfY5cL519ttjpPF7Yw55fFOqdauS6e18c/ EcLBZQQTAQoAOwIbAwgLCQgHDQwLCgUVCgkICwIeAQIXgBYhBI8Xd3EYoz3am6SO YqrLMkNjAFLZBQJl3aSvBQkRp1e1AAoJEKrLMkNjAFLZMocOmgPUi5hvcTR/7a/F 2vXpiJAPW20qWBMJHmEJTgSaFL2wPlAY/1LbrwyLyWsY2MjmOnOjVR00cvLHz8bV 9kncRUqLp+ERqO+GVe5pPT0jAaNI7F4zcmKyh9lEAy+kqOtEZAcVnmJDjqVYyfmw 53m8lGCcbFgEYHVwtJR+/xDq6KTZjRAuzPuKzF5Ztl/9n7I8513UV0XO/EPekMw4 CNew4IE0n08nQVAiGknag4CHQMzSosXpetrzk3LhRjZiOgsmEU3aLe6dOFY/Bips U1iq+/gF8Dv2UQliR85+SN4Y0M9G8V1qpO3yWvfDwdSYHhK0uMpO6JfKvVWi/fju ZOjuVOrrhdBfscPxSGJrWpfRwgFGNrvSANYh53AsLr9Q+KlpTqpiy1xYN+Qsy/6q JfyPnEfAJOCMXTJBZzOR90qi4CsSGhyTNRBXUqrRSlgYxT+SSRprsyCjWS8qvNd/ JzsfIYVoX67EEUZ2rEv25/pgxOvgFpIzNaxDlnxOxZkq9rSS/rhh2+awMXLo49AJ TyaOOspwKf5MZJ5IgcV6MTFpSlWY8aj9L0n72PprL5fCmRao9lEVOJiJDKXGv+H+ MMvj2Y9VEH15LiUSVZc98oEkPgm4YqxJssLBZQQTAQoAOwIbAwgLCQgHDQwLCgUV CgkICwIeAQIXgBYhBI8Xd3EYoz3am6SOYqrLMkNjAFLZBQJkJqswBQkQ5EO1AAoJ EKrLMkNjAFLZaOgOoJzDpLGAckDlQGnwBwx9532kVg+L6quv8PQx3y7Bgo6w2B17 3qxyJed3efVAJxGf8qgEqArGyMJU36aw84vYTat4u41KWNw+0eI8QYoJchd/KqqQ w0sg2AvnuRbK1Wdhe6BB2Cn76eFO4krMu4EiIV9MltgxnyCuGnEDd7s8R6382N94 safhysAVfDXs38HYdo4A+FzDBWn5FLqenEuJtWcNBVWgZHyAU8zjaOeGPUfnHun8 gNpSMNoqcGSoAIf670i3wO6n51HJfGR3ifaGeIaEkLMn4DyYjxz2pAoroe1QB98K AOoMuRbd1yJJKpUlfiTeH9BRLwQ7EqsmZgiQlyHZxfkukZHKLzd1qnng/AiScck0 LyuyKqTw6BiRs8GmsBpSNHvuvRGUqYs/ORVb/BgM4O7GzcTwjszvzxcTgJI9SaIf YtwLxDUQrqKDRgcHRmSdG6I3uLyJRQmUV3BO8iXw4o+UmtPbr7cvNuQFVlGfc+TF 8M8h1QnuErKuV7kAtl0zMFagWKLDFUZP5vJmQkIuPozv72zXIhV+K9cP3LYcEzVp mbx66PGAgbsbv5OeU9gJfbJyWB6DGZ90aHLBwCHJhrxZSBVIRdquaiQplpMkRvR+ icLBZQQTAQoAOwIbAwgLCQgHDQwLCgUVCgkICwIeAQIXgBYhBI8Xd3EYoz3am6SO YqrLMkNjAFLZBQJjN4MHBQkPDROeAAoJEKrLMkNjAFLZvz8OnjpkQjNx0gzlYtqT IBOUQWJNCZpsALYGol/Wpx33mb4i77mjtCoOJ7BNhxBFUxxJnSCzER0BLYzV7a7N yeZJ2mNnQGtr1o7W3l9UrqlRsmbabLnA2TnGROurkrVXgCvKKqIelHdGRMHO6Aoy iSE6/Cn6NGf59FbqyEoaX1A+y9e2qlz912bFjMrdIZCjLPd46d+kGZcZ4nJ3YxfR YW+AdoQ7ZfBepgs0BpxGtIhYDXWwclZxscKhODYzT/D6qVdwZlA5tyA9ZJw6FC8u VHupNZD32wpQW2l7bf8YsWatANI1N6wDOb7WvRMoX00psTGLTub87lJGF8FOjxM4 fCEO6kf4Ykj2eJf5Rnc9bpd9xsvlXhjzqxjK36FiU8JxqKR1oCb/WSe8WQQ074XQ 3H1lA0LWNLyghyWE4H9Jwv5yw/EFhFDkcBiZbXrFRohLZwf/vcIKqbxtyA46POA3 olcBUUPrDpfcBqJUaBNP/jrsJzYCTgdi/EpLNTwe/4ab7C1SZLcWm6WQ1IK2stL1 6TFpOJqGjcH/iEAqRTYbYa6bkchW+jh95TqxySuwcOLPvCRTO7Cn9BMRgiP1A9jU Tz4ICn/uFOTBniIZ0fdrryf9vyLKaQbN28LBZQQTAQoAOwIbAwgLCQgHDQwLCgUV CgkICwIeAQIXgBYhBI8Xd3EYoz3am6SOYqrLMkNjAFLZBQJiRkCQBQkOG9EnAAoJ EKrLMkNjAFLZp48On2IBKfNa8enyuLzxkxa+1cFFtxX3h0Viji2YF0piuSyTWLWK vtP1vfAlrXSDEYW35KVKZSiZaj1Rb7FfZXSwoL5Lhlxn49IQzBYoID3lpmgEXifd 4n0ExzOYJibJhAUKVtyO5oV6ffb++8ilu8VBXLQ1RMAraoEFboXXz27lXQi4zaAE vCOo1zNGrcRqkzS3wzl5f0BScNBq39wZDqm+6DkUHQB/FkIRQQCs95ai9qL3JsGP /5On2c8aJKf2HLeTT1Yo1GYcjiYwQDn8B591mh7SKQgVLRIed3F6Iyz+/Viv+8rX 9zW01KEDhhVMyIv6omefRN6XN9CN/rK5KRg9ZzXzV9wp/0Jeb2RxE6J67BY93AV1 D5PjbeT3wbWTYOaBqxn2yKofQhjS5pWwwKngGhvwrli1f8Db+R0yuloV+PsEWWAW oCmBsIykKAk4jHY5v/3OmIvtdOh08dhGm5VcbZ7s+J0d0t+iG0n2rTgOsTDVlTWv h/wr72hqOcZjhkHTc0At2KvFCRjlfSlD7ZhDhm3CQSFvyIVN/jqmQkA0x7gHlW1q EA9MyzYV9X4mqtQ5B1iKQB25IQorvMUli6FVVSh7rwUs6OlSMOnxDrFUu76XNaPC 58LBZQQTAQoAOwIbAwgLCQgHDQwLCgUVCgkICwIeAQIXgBYhBI8Xd3EYoz3am6SO YqrLMkNjAFLZBQJhVk+2BQkNK+BNAAoJEKrLMkNjAFLZc1gOn0apoz0XikdVwpsL 3+qRJRJi14x7MHctS/p7ZyUviYmX7NkeQEicRKuE5K+xu0yMmpmsICvZrnmIi1cB 7EP6pGDZgYo1iqYaIyAmv0yvunm4ghhUS6atwJN+cfAKrUXh+ogZkaV4j5vuvlDt Gifawo2HL0dnidcR5C5PParIr3A7r5m0gI+8bUc1+wlXxOP1Iyv3hYo11qPq/Qu2 okN7hLhDmBhmXuZnwqJ8ymUY/bn7uk34PhAgbHlpBcls3LB0zSvNpPXmPSPf7Kl0 088ldRSiMmTAM6ZuEc/osB6gP4Ejj/cYA1ej7i3K/0zSGIRLZ+l9LstSLnH1Nd6m w+gAzMFoObdGBkUoKGGvArzYT8O8mgSmeg+fXd4KuV0Vyw1zD66IfoEfihMvEwDe DhchrWc9ZkS/10Se1uJ8mmKT+sm7j6KK3DgWfZnr8/CwThARfGtQn6bGcglf1Y0r X2wMG4NF76hoLJknaQ1JE5aYyS/PPeBXNQAX+wTt6wJuyDyx3APUbCNQu6V4eKH0 SgX/lgIHxyqqK6xqH/F/Wbdf/gTfD879kxEWSbg5NZk8Pk/aw9CgBI/XQg35EcL0 RD4ZIfqSAGAftFvSHqrXVOmwdDYVsMfTV8LBZQQTAQoAOwIbAwgLCQgHDQwLCgUV CgkICwIeAQIXgBYhBI8Xd3EYoz3am6SOYqrLMkNjAFLZBQJgZQ1FBQkMOp2dAAoJ EKrLMkNjAFLZHLcOoIlk/Q48vLf2P1aV4eAHLSbXwbQb9YUAw16ZkmH0MtKoBNTe +Ka/xv6joxKHL8jgjsUWBsCtVk04HzucJzCdQHHVfuFSFrqQV+AZv5lUeuoGVP7q c+drwgS54pjHKl9qRXknlumODA5K9zq2a12QLedCXU3UrGq7gOBEukaQeJvJVWKa JRFl1Se02mx2goFTkUmyTdVMMukI6OP1woPA5NZgApiIwD5LvGbx6GgiwXoN2K3F VgmNKWgDDdLYQyDhKmVakzLasdwLSBCwXvH5Ynss9iShaAQHvnpy4pjobzV+hL69 ecBUDjc6jBHRrx2IOwFGiaP6aD4FDREtz47Yx+XAxxom+1kOkXhb83RSaHc9Wv5b F1TSwmZ/bX/AMBxc2LHvSDKl1cTuDdPHnKnCM389rQLsU67edDiRgITILpOia9IV 2JROLKv52fW4Ee3oLAxHMDDVFsAQLCPnM6hp0Iyz7AewZMOPyKXVcAj8tkBjumT9 HA/EWwNPFc175C5QeiSvOV7PJk6Z2b3+dGzGM8PMv0vFDnc/naXk70Hf87sXLFXk IlgIGO2tltqL8oY+EOClC8eBi6+NdawBzUVfC5VIxYSxUOQDLtolS11K7aRpkBkH DMLBZQQTAQoAOwIbAwgLCQgHDQwLCgUVCgkICwIeAQIXgBYhBI8Xd3EYoz3am6SO YqrLMkNjAFLZBQJcsIjNBQkLT1TRAAoJEKrLMkNjAFLZYqsOn1VikcHnN61UQhS/ /27thmZwxReWKHzI2upRrwitWp85/mKxV8c2B6iBoWKgPi6KQibtjEqFQr0Vw+Yt 7v/rJBm6gnOPAzWNxNAOoiTdVm2mLK+95raAGi7oGEt7tpwWnAGOzBJQzR5b+j2r CWxfDmmr8Yi7lBtkqXKwM4XGAOQJ6x/JgNozs2nZ/aTXmsZH550RnMA6KRZmHVPo lKet9VMljnVHLIGmj7ynYe5I+gY7SvAJQ0ezd7696v3PQZy2QuODjCBGxPf7Wi2a xYr0D7b0GabUatQYIa1mnbchVKx62suEk+Svc97VxXryZiLPMk2Zua/QJ4iVuBRO J50CQO82bfzgw0cdKuEl9ZaL8hsw4C1i283euoIVLqiZB1sjPZuy2PzbRDuueUts BmTIRbc4CL3/9Lnn1lbUj7m7L3bBJ6y54giRKLA+VVgEFXmBBywgpbCewn3B+DG6 oR23OSv9PHznGhzvXhvZbSRhA8WbNlf4atRlrEicryq0U3InJKNi0mVQwgUL3ra/ Lc1Pvml/gE8nkdMfbD3pRy3HVxkEqb89hFy0WS9PUoWIfEzFHFIW1fbty62wBBsI KxE/mUhWAYKmtrz5MLvT4EDTWbzqad+3LMLBZQQTAQoAOwIbAwgLCQgHDQwLCgUV CgkICwIeAQIXgBYhBI8Xd3EYoz3am6SOYqrLMkNjAFLZBQJZzTZ7BQkHhUwSAAoJ EKrLMkNjAFLZty0On3ABjKfIvxqMZLE0XKo8ybBl1AqJI6/jxtx+NWeKuLQsak/u BvssYe4twK6odXpDszxb2adRO+s+RzX6YUfh+yl4MSqKyP/4XbmfVI3He8MRU7yB Ah3LJt7j9GsENC3htnpKPfK1ci6lGPSkVeWKGFZ0Kv3eYaBvnGazLZUXwZ0QL1hH FgNPgI6DaaZHytPWhtgcuIgYwFAFfVhr0m1UgVfMlePoBvSLuDFyrpjVS3G6SKp3 d16NdfP49nnP9aef96xJSgmedMfi/5lduL+8d0/yXAb+Xyo7v0s6e+v6ggNl25ac vhckkZV6iAyVmzuKx5sG24D/g93kIPx9HkEXehu5SYWpJLtz8wXRY4q05bC9jRQb JrbKheELm6XPwHiGSwG1wQTwvn9f+N0RwogZRsbyB3J1UVbO015/T3mnJxoapk8w +zsS+OyxkMr44cJ61frShruojiWbMi/qUp4VQNVjgMS7ysBLvtMM/6I4VCsz0e7G DJuvJATopxEVg8VleY8fRZeOGGArWvM08jns6RyavY9NhrYutf43XhvtZRg+EnE8 Cqw8giVKE4yKjH84w98Z/e0mz9+V4pZrvKa7ELv8Uxqx8H36U3dNQVtdpPTJ04y+ oMLBZQQTAQoAJAUCVSOlbwIbAwUJEswDAAgLCQgHDQwLCgUVCgkICwIeAQIXgAAh CRCqyzJDYwBS2RYhBI8Xd3EYoz3am6SOYqrLMkNjAFLZgDoOoKdOLLX7qC39jMzB mQvigcmt9WQzhTMhbeMcn9wHdydt0HEOI1zCsCzsUPaW8Q6tSTb8Ce8sbEg7kM87 skn4fzShipd0FtFaopoXMfl9wigSk/y3rgs84bytMJTrkx+kBtCAP/OUnvAwEDU0 noCFdoqajNQrKfA+OntoKqiOXHLv4ydYosPItEiC1g+qxDuZwQ4cr8Zd+Qd6REjf VPRFmnXCX0szc4cQ+5iEAlbOkTCnE1ZLuF7F4WGOTEFZgkd6p6pXWONF9MlPo+Na AUWhPAXu9x+6H5UcKUWkun9wLKZDVBpl938MrAlmk1fwOzP2QSfZGuDQFND3V87K 77ALpXtlJMh+RVZ7oyeEfSlWzTmlGCDQ+VfO2pyas7xFY0SlnxaaIEKajSVBX9QV 190NK10ENGllrA6OxEjXjov92L5MjIgbqIZKQW/fTokikLz09boUdluCljjRtBAA 7UF1VJRU8xKnLVb7siizngPRVaUsc4hghJYm/VcUAVBBY9GJDHYvSHzMUbk6tnsc sZZJAQ6PL0KBjE7Luji+Rewg6iPckngfm+5kjozpY4/PV6pHKtQ94uz31iiNx81U nkgNk9dR/LP6o73l2ecGostEACq2CEwN3c0nTmVhbCBILiBXYWxmaWVsZCA8bmVh bEBwZXAtcHJvamVjdC5vcmc+wsFlBBMBCgA7AhsDCAsJCAcNDAsKBRUKCQgLAh4B AheAFiEEjxd3cRijPdqbpI5iqssyQ2MAUtkFAmg+77AFCRT8iC4ACgkQqssyQ2MA UtnOnQ6fT+xr8vULDc1RvLw+cEeBgNbKFK/8HgBn4HBmqEMrftLHEyUwAot+Fq+o GLrcJm1nL5berVUfv6c4Br3NR3eRA8TnkSWIgRSellpCR7GL2JenisT4ZMFCxubD sRn+GFBQACFmagsY/zrw5GicvvJR4vTUNUwnPyTvc6PTVvvaUrgA5fUATiZx2VF1 4V8TwGxLPUZk0Gfv1OIl9CZjLF7LcKw5bfIhcUgF17YmN+vqkDeKQc0DtGuTXGgH +u76hSNHLN2hj/zcTCdiu0C2kjt5UwC9H5Lz1kzN0+RAoOem2FYXoi3o2XWr92mI olrDaB1sDMMCGou1K92weMrJPwrZzT4gGvp2NQiN74mKMm7VQ/rR1Kt2q9MOx3En MDUVUxxWKyNLxvjcMWhDP2h0BH6XWPNlZHyGigaGJvVVMwufvQ1ZYxJtTWssHJPv NqZxa/wf1gtvejgm1qp5tAze0xwD6IWmk58iGbXwnFDWB127PErjJcQo0ajjUBmQ vEdaTkj5vh7XlmAuO3ztLa36OmdwOKGR+GFTgl77Ku0YOyoSlyLvvKNu+aAZWLd5 rY5bpza/jLQziztGtJYpSnq00+vAcmQBz/h68TUa2HRwwAM06/8Wch0mwsFkBBMB CgA7AhsDCAsJCAcNDAsKBRUKCQgLAh4BAheAFiEEjxd3cRijPdqbpI5iqssyQ2MA UtkFAmbMKdQFCRMMg7sACgkQqssyQ2MAUtmNpA6VG0q6QsCl+Vs/XLcPVfuNdrw9 j2Nc4UeORdvbnQMBuNj/geXAeDgBghqxEw/vV+Fue/K/Vg1oby1AzzXrT9ikl01k eusV0H022kNeC/DJ1opxFyekElIsrvYJ5ZoXCZRPjIpAGNU1uVKlT6Gqrj2YrNU9 IAFApjEiuXmQHW6ynIsnMv4iccazm/1wLuhmsy+bqfKUrkeVlMaOtNDA/A3k+Uor 7iM/QvzXAVlN1+2nJwGDJZmltQeltSupKxr23Vhi9CrkHhE0Jl5266u4J1pMs4gU ZK3eqIESJjkhgmmFdnu5rp7lcl6elx+N12JMmvrdYgRmonxvX9cGr8PRtRBX/vFT lgIi+S7vFn/3W48NXCt6cs60aOk16OgzsXHwb6NXHMoI9Gn9R4CoxY9hdGm1cU4Q QcqLbqmfBnS9rS6sryBJ5xMYj6KLhsqaAnfH9MQyGNFFkSXkAlnX9vLgwfY8vY2X zGalyf5wnxf2mO1T1E87NIVUJqmRTbKaQ2LVOFHseMQ3Pv6v/vqX82dw0SCrzNuU eJurcxe1p+NCasO6g4mnjUpYjz+xPV0TiJPuHr9TqEE7+DeuAd5vnigfODGMCYsV Ad31QFm61AAEUlXz10NaUrHCwWUEEwEKADsCGwMICwkIBw0MCwoFFQoJCAsCHgEC F4AWIQSPF3dxGKM92pukjmKqyzJDYwBS2QUCZd2ksAUJEadXtQAKCRCqyzJDYwBS 2RiHDp9PcAdNAk8Azn1JOrghBBbfaAzTfbHBulu6bHE0N8SxtiT2MzdseYttE+c1 iDclJlE0cBf70yEPbep0GXTixcTV4rWcRiUyoooRXgFXOXJ2kp/1fRY/AoGVMsqE ZEE7S/75yQzYfUE8DfoR+yYPbLVM6fRs/7Pq8+Iwq+gAyoFgQae15/NRkgHiZ72l iUQaRveVSQNqSv8EAkt9PBmB/IVo29kTPyPpWkWM7VeGL1nEuvu4ML6ROSp/j/19 JGr6WApBl/IYos40kO5T8H7B6HbysZm0Keb3lZDEUCrUFjdzDwNM9lyK7q1Niskr CqeL0CjMxl+YcDld99qcbQ/Iba90N+1TuN761TxA2nFHQT+AbPHiRvVkz48el6UO joQBj7dWBHC0iuyVvxMBWzWPudUiwuJXmHEtsW1W4hoiAZRL01omUExd4f3leQN/ T7PX6ikE9H6+PGOoeMew3gF/GjAr0oozdsO5f79wU1iu+n+n7Z77zjidzPBv01dq SrAEE89HyEZBaxEsy+mzg1qCM7NT/CRWGZ5mzBwdrWJ6CkTAjD1Zd8BRiVfLUtvs j5yiYpE8Opw7kr1P2GfOURUGL5YZgLW0Lj/dckFnxaGQHtq9LZasplDCwWUEEwEK ADsCGwMICwkIBw0MCwoFFQoJCAsCHgECF4AWIQSPF3dxGKM92pukjmKqyzJDYwBS 2QUCZCarMQUJEORDtQAKCRCqyzJDYwBS2YZkDqCSv2CzWtn0wsrqdrFAU3jPiVeh o85rlG9tRlD6iLCAihUxNiRAm219ynKd0aThEDEbLY+ZNSSCHRkWPcG8Ejblcq3+ BtiLrZTNoTj1rCVi7lRSmsYKpKEVUjQTk3zBWUwWxpiqo8sodOq681AH//JJRJ6c p5IKS9MWDKuE0wQpk399uYdqWBshlhyhUTrpdIbLa6Bl2uyoV3An8ER1QHfbuwGo oKpA2UE5YsssDtQt/XTuKgJzZiYlESQaf4oHBtoYr4ZCcBPJ8QUbiOk6RXrTJwrD Xi5ork7e0VVlsV4slAMaviw9tsGCgv42k9xvzEOF24td9hq5JaeoWkVJLAvwwaSd sCXWt1MZBTSV02UsesxZfJsELo9QSmKnjvUM/hGqX4LW24LBVuMc0GYZE9CkJaPQ 3NEuJO5hvAavLECE7ioyTq5FoneBUHdE264R0UNl7THIYKqqBCxs9M3cNCghU+yT lNTqPglm10ls6j0qNdQEAkm0sZlykeRCbqJ7YyH0haa8ATO4HFDQ04CvsR7pV135 xo5uKLuKzO5HYZLNg/ilLfSCStVda4LH4p+010/+MvVIOvfXZuWSU61WYLoVM/5E sY3yhnVXalcsDg9Q258IDUfCwWUEEwEKADsCGwMICwkIBw0MCwoFFQoJCAsCHgEC F4AWIQSPF3dxGKM92pukjmKqyzJDYwBS2QUCYzeDBwUJDw0TngAKCRCqyzJDYwBS 2aS2Dp9UO7I8U8+iJrNOBRcT4w80scB38uJl938EGuF68jdFdvZcgTLgKctqVG9w KsKwuCDzDhW+bVUweblfnkSKRq1V+6hX2fhrAgUOZVdelB1cwEE2qy4sdILUHqGA XefDtvKrWn2UNyqw5k6TfTFgz38ZroJXL1Q3t0rfEGgQgdqzcq6mS0xpwyAuyH+x h4PnHpjlfuOMzmjyfA9hRPisawQwf6tzcO2YFF9SCvK5c0EauhRoHH/mW/AHwSMK dlH9nLV1ELvBC4bn+77bGfou3pXiEHHIIB40bY7CMtOYsHegGwJ4BSI+oj+zerWk oKKmx3Pt2pdcbhcbl/QkhELUAs90UbbWiUekVY/K6xMI2hphU/zOETIDlSYaeZAH N52B1WNA7cCL/M+kUgyTWtGrTkFaCEiEGNJPqUjJXn57Xmus9fYxSWbP+l1jQr++ qaqYJxzUYMlxdfSIs2ElDRmU0TZ7x9GlNTudZHPt2O1f7vvNvGX10KfSzPBgMKYV XCXPrOkJHpa+fFRSTXYMg2FO54ZCVi+TvR6F+oHVh3tA2Zv7a6HZ1zNEn3RbOwxC aRvNPfdj1PInuoo4+UQXeAdXQkivqzgJvbjSBscuYa04FWNH81dl0wLCwWUEEwEK ADsCGwMICwkIBw0MCwoFFQoJCAsCHgECF4AWIQSPF3dxGKM92pukjmKqyzJDYwBS 2QUCYkZAkAUJDhvRJwAKCRCqyzJDYwBS2RhSDqCQg6j1nrEbGybYr1dvWagOcwy0 p1/oKzDBiQ0vPwYpiA4d7Wfx1JD5gnS2YOYhgBNjvoNQZQAh9WzjIEYmCA77zyT9 WnaXZvDJtMvqgIqT/Lvxqoxh5pZwbmMiGnT8ZPXi5mxlCKgFCUaoEr24DpVHtv83 q6u0YE/w3HS2w4Qmtfgnc+7i5Fjj3ZZf98zaDxRzGRaavnh+G+M1Pg4z4pEP8vPz LI7+Wwmd71FIMqEVzVB8ra5uJAOwo7+g5yzGr0E9AASpI4Nkh8vb9NopLPEVofad lyM7egj1qmbgyEx6Jowcj07a31wSrToEU3R7DD2+1cwMyzNIVEEqccmRuH7lnjgz RmnceF6U3ge2kYhRyvJwPmhASVuAhore9DRNjyljK6laGayxp4YAW7TDm9G3rpGU GmTgAhcf5Nyrztuboz7Gq5Z+OORzhL2id3zQIMkV1upIleeJ7eppEr7IPU/69T6f 2GCcUKydClgghqOhip0nVrvlMUkvmO6dsDeMqL88VTycvJJW55ugO7TOvnwI7LDE UkUvKAf83fSlmpSsNJsSdOBXZrekPReweEqz3kc2FWJyLx2w7fLQbYlkMNSx2DoS ibsqjGPlvYwW8sMsJ88CRXXCwWUEEwEKADsCGwMICwkIBw0MCwoFFQoJCAsCHgEC F4AWIQSPF3dxGKM92pukjmKqyzJDYwBS2QUCYVZPtgUJDSvgTQAKCRCqyzJDYwBS 2avkDp0RxVxnyVT16JF9qe2CYaerV/tvJM427Dg/rKhmknpDlUW5zCU0sRG22RFf x3oU9x9uNb9F0qK5zQLI84d7YFTqQMLX1SkUCYiDhp0c5mF20dXrKoyOZmqpajet DfUkNCqAB1FQnraetNBtDo8subJW6GdilegjM3LaGyluvftJiZ9T3CJ1Dn5nE7h0 7GoIg6OivmdlsV3EOXFZzymmzc9rEA5IHVKQlXSAkXTdC+G9E65Rzcoj85TB2Q0l 6n9Bfr08UMQhR8itYQrQ7E2kthmP0Vt+pqahwDZfeBWFjkAJ2eXVKXhUGEFR11RS WL7TZxma6AZdLWkyRhbHA6h1rvQK+jUSI09PCsJ6RxiF67I4x1wfvqofqyRXeRBu VV42O2xWnxrGo1szJ3iwICyAbw1SLa/9YhIDezq2OkXMwyJa8Sq5IUzJXA05r7q7 qXmebveBe7kT/tLyUVgZ0wuttMglINNrEWFGwjbcGYzrykoldaRUPYUE8a9CaNR9 n1LLP36UrXRKBvbo3s+q3Dv8gGUpqVWN4lqCBRkzLaILZUhcwF49IoZ5vb8RuP/a cJbJjRFRH0/rsp3V735coYTt2+7NgoQxRg4kX9yE796G3vxd2Pu+xEnCwWUEEwEK ADsCGwMICwkIBw0MCwoFFQoJCAsCHgECF4AWIQSPF3dxGKM92pukjmKqyzJDYwBS 2QUCYGUNRQUJDDqdnQAKCRCqyzJDYwBS2fVSDp90hsk4Fx/BOJ6qMZUaV6047viP xR5PHC7YhMYodpUlKkYZ7FeEmBRT9Jufa18ZFvvKw7Loy8/YECWT5yfWG3Sb4kq8 2gcWuMnf696vLa2gRxl051xc58drmdUevFcmPMT4OAIH2hG3Xp/ZD5eLsyn1KMS7 2OQcq29DlcFWzzGSSOEnzlEdFPPBfY6GIvRTtXZZGBgEJwTI7Hro82NIwzslQcdu VYTkJO8TKpqWD3H/8jHNXFIe7u/UwZVqpC1zb1VCQ+9B8ou1+yFSMYte+E0o4PyJ Lfx17ZabeC83l/hHPjzCe4lcMAPJijPaJI+xTWAUN4/p8Y44wRoNwrBMgPeXwys0 qCO9o1lbiR1QSynZZpmR3quNfMWDSa/Dg6V6/u9mv8VVCm5da03WtsLDs9TQZ2WU zFdeDxR4hsac3KN/jbmFPVw7XKj0y69xFMIEAc438KX9X0+490onWB7fLrBn1OtR DCF7W/PkJORCCIKc1EtSErN8ADevv7lh5icmQ3/2sYrGiyhrTNjQpPdDgan6daS6 yumuMdEw/r2tW71VPaMhdyQOgEGEMh/olByB3pmODUvKP8kvNkcvQG7du0wZFTEC 4CqY5fT9EJq+vF9kP90A1NjCwWUEEwEKADsCGwMICwkIBw0MCwoFFQoJCAsCHgEC F4AWIQSPF3dxGKM92pukjmKqyzJDYwBS2QUCXLCIzQUJC09U0QAKCRCqyzJDYwBS 2Us/Dp4rVKtUORqUbcD6+yW6iwvi7al6GPr52rUohCZILVuqcTCpY8g92u7+HTMw U0VWJBV2mxPtfkkYMsaKkZTnznhCSqN9Y/1fTZdBNtmZkvx5DyGxq158KNciwM6l cAw99FrIxSHRB1SHcaz2sR2LOWQNZKGhA4wdcVZsIBL0Kjr3lr6CgAg98bF2Yg5f yI7BsHvLuFil7EGAa9+3ngqWV5gny3EllJeMOH9fcJ5Kh2uSJHU9kfkyTuYQY8Hy 7lToY+z4jmGQNsae9iD2OfeQa3qERNTxT6mxFxdlDD+1BksrQiz1FiQHmrUl882z oJe7ug3liFVvhiUjik/2cm9nHUdxB0e5ynzGCqYHgvi2G2WHSR2MDqHn5LQcEcTd 9hokqh280ejYa4LWsBQqeYpSgE9FLO5rJRX/+ETOfk68BNKlGltMakAL4L4vJhKd Fkzyniq9RwwgD0R4aUfjD8UX9sqAnZ35ibyskMmIu1aAPX+nMQXhr+kHDuGv7ga4 RPZIMaVhvibWaiL/W3CsEakJeMVxsdJrWsCBdpYm2k7ASwiKzHkn7Zl9W89tOoYW A+UluDJOhCWNfvafLodQRojqqx3boZutV+uz27JmVP5r4k3pReKLLDHCwWUEEwEK ADsCGwMICwkIBw0MCwoFFQoJCAsCHgECF4AWIQSPF3dxGKM92pukjmKqyzJDYwBS 2QUCWc02fAUJB4VMEgAKCRCqyzJDYwBS2X+GDp9Iyh19ICgmKigudHjrhhmBYzoV 1O/W4L6qIdG0TF3G8Ty//rjvHbYuIEGdYQC9lmq0BdN8xLN3RXmn6pOl+wempC98 +CyZhohtY4MTELl3vfcqYX7X3nY9hQh43Bz9d6KfzTqnZ+kKhF2OpQZqEnUSZBHz SG6tddPwjehYu5OwuwDr9ZKTs60DMZj/lHS4gL2zD2we38epEk2zMPGyNpuElHoo nWwpEKsDknREZ8+xZKI82CCQz+QK3EGZzoGSQifMF9R3hXGcV5Z56K591dpc/cWF Z1k1+0U7Kj1/UheiSbtG2tfNcD0RLvEpjEyjAHl/evYX9zQlnQXL5SbcAmbp3PpP MW54s0Z1tAYUvzt7czA668HcjFt6x6pITwLTLvHr3x8qJTguKv6PK4pKdOMa4K9x QQ7IE3W4XYlldH+LI74F67yKOQ2fkxfSCDTdApL6i7AsC7PBv7oFFhMrDoWiFDWQ wQ8W6egSW4PUcvvF4wJ0y5nATxY5fEz4Ei5q/YnwxzWju9chQoBDpw6ns4QP1zw6 4GaVZe2eDpliDhExlNysFPWFq9R+L9mdn8ePrHqr1WnuyGGhLc27QhgnhHwgCHDZ 5SusBTnHPRUsbSydTIJSHWTNJk5lYWwgSC4gV2FsZmllbGQgPG5lYWxAcGVwLmZv dW5kYXRpb24+wsFKBDABCgAgFiEEjxd3cRijPdqbpI5iqssyQ2MAUtkFAmbMKm4C HSAACgkQqssyQ2MAUtnVpA6ePYwta9SIyXHT7YqcU19NrbcNzcGiJBG+XF0OHNfP G8f0v0GrBIhPkyn4jwMeqyR5f8JxNX0goVQC3soIKTb3v4bRTw+4dQkfP+3zqE38 wYwR1dclyPaUEfOKTD9uEvD15c9seWyyvsMu0EdBytlXvoUNeOLOSIT24WiSq4mX BfwTHGUojbGK0KpWB+VDO/JWBbz7kBKuzV9SZvCWQJoQDYNdCK/mbiavF/UjzIrw UeZKECljZAzrxRfhSdqtmxS+yw3c3gdYCi//ROlGwK+F3BehLt/hQbK+ypLKBdUx L7y3VJMbt71n9HkkvbkS+9UW4JhmgFbrVne9DH229/rVtoWzkQxwsDQA17+jW2u/ Q3By5QqMb9QwRcKqdDNRqmLwFiTq1K0lQMl8O1dvDPYQcPQ7ZbJVsbY/lymdL1wu t/sorROFDtmvF1r17eXikzeykeItSKYyoaJOBd5+LGJWxixBWvo/bkZDi5F9naio +4V5sFcDHxPYwwb8u9b2EIcBEuwd5ijeuPKsFKtyJSErBMqGls7c3C2tHUSdheKR NpztplRvQrMKClz7Rovm96YVyuWy8SbFNl3vPD6u7zfGWArd2AATdCMSlZVIsXtP 2zqIp0tkwsFlBBMBCgA7AhsDCAsJCAcNDAsKBRUKCQgLAh4BAheAFiEEjxd3cRij PdqbpI5iqssyQ2MAUtkFAmg+77AFCRT8iC4ACgkQqssyQ2MAUtkcng6glbMumXd9 iyq6URE7MZGiYT/7gCcWyOefzzYyWv00Ae6prz8ePaR3I6HYZUeg0WomFYxVU8HX 8oldWg6gakF7oYm8m+AknseK8/tA8qGxUimPLvBKwLKoRHVnUXab4PR/nMBQ3lw/ DZMf7qQwYU93ZNVsNRJTHhGnFilYQ0VsaQj2s32j0s3oRNrJOC/JIpZJKlwxYvN1 0dsM1J1SKxgqrHwX6o81q+60xn9PupGE6+3rFMXkUz2ChluRji45xe4kuvD2JnKo cxdWw/Y1XMcks2KLVR1ErST5fsAnuaRJEsHjqmfA5Dc2iDyzR7bHOcr6vKcv4hFJ KXJi3DRuNdm9Y2WWRS+04v+Zk4Yj6VLIP5A5e+fIg5K3d029Rcml2BhpS1+GPbbD B05E3mIxzDhCKwxpf+Nnw9dbY1jiNOfEiOLm2UMZMYxdGg450RyMm4+RbBWBBO+K Uvm9dVWwtseHMnjYoDe8tbbJC/D6o5Sgaw4ZgTIhdvZaop0yFYTwtOKXlP3lSM3T bVdMK5m4EhQIhkxTredeX20N3nWW29pR/BeFR9IyU9r9k+ZcS30YI8SqUv1VlSBw vLUcqJ4KTNc+wjneK+Z9RuKNZ2PRCG2bqYG/n0jcwsFlBBMBCgA7AhsDCAsJCAcN DAsKBRUKCQgLAh4BAheAFiEEjxd3cRijPdqbpI5iqssyQ2MAUtkFAmbMKdQFCRMM g7sACgkQqssyQ2MAUtl6dg6fW1qd9tl2tbyVq3fndwx8ndcBgynyV4CLjH1KHqsB WMcnduKFg+2DxrPb2HldMnRroBi/0pQYGH99aHrfZOxtKoHglBcfdKO8MJm1g1X/ RHz1hn1C12h/9SEnhvkUzs4Eazi2hEvlAHwXnZn1iaZ5dc41AHJlNf3ufXXPXjay sB9765AphjpCIagJkqahliHCtf+kFe+hhQ0etjKrQ+isJZ8tZ6MVTrTsDsY1lRwk G9le+IeUvZle1lwUnc90+YQ4ib02y191ep9i8hvvnRROC9bTDYfNHc49aO8zuAxc 7eVAaL0/hrS+cCtoUHaQMAZZ0kAQOvvdSeWxI2APfaM0rkfJxTW1gqZbnTtEEvTm yzo4c9KfJCIU1RRAZlHmpVSYUkGbiLFYznrd3dCIxW9+7MHrytJKOnWp2LSYEbeJ yZTcc5mOD61spIb6Sf25MZNUZtngW4j5Nz5FQ4mriyipPjetllSXxfptgkvqtF3a qhfi761591ygtyDWOYLfTgfkVRhfChThUXI/Ai0non20SDkRk3MhcbyKKrS6PDBe /g0dm2IWAIIO8QKT7dHo+Y+yvNpGq7JgCc08wYttg7X4hF2gHTzgOBcRjbQx4+6/ 8wJ7OOrDwsFlBBMBCgA7AhsDCAsJCAcNDAsKBRUKCQgLAh4BAheAFiEEjxd3cRij PdqbpI5iqssyQ2MAUtkFAmXdpLAFCRGnV7UACgkQqssyQ2MAUtktqA6eORgm8s2d /zw02a/myc2hvBtRZVnNe6ElYYUTYMGW4DWj9dZWRHtuloA/ayMwNM+vtD4kBTuM K7iyhk1FbFt5j9rmSpNcxaGNJr2nBrQmcxsdIjUqmyJYPJ5kTmF5TlyY8ZR4KtR2 9LByNkSa8m05ArbbVcQjvYxVvj3ZN3/DCdeKVSqhqO76dEtkBev09ik6tIUGiy0d mXzN1Jpm1MF4gW+YHfcFfawAx5Cn9kKs52fpum5hgDMrzt8JZuEBRlK6dN21QN6y YrX3omxsIo72xEbD8rxUbbiCtj3GQZ4DAE56crVLuiSvM2q3IePCq+pNuH9zH+lF +8mP6RwC/sNhPv+wM4kI5rx1c8m/tT9PWLrjQ+LMWx6NoMUWLyhxlF0KIE9W1qLz 68VUGFlC28qznUeYR1ddUWHTTSibyRZM2Xt5c5e8hRW674KebQ/iZP+OQixSfRLE JeeYKYSR2PqV9srZ9JXUy5X8Sl+K35sH8KyXxuzEgJOxkZzdtWSTdlzI/wenHtoT Pap0vAzDlL5CbMAptjquv9hI8dMTL/vDhKhcWc8mwLXz0FAxFf5x788Qw7+0S7dU 6dGQfDbAJe1PLKrbe46r7jcjLvIVpjG7AgnTVou8wsFlBBMBCgA7AhsDCAsJCAcN DAsKBRUKCQgLAh4BAheAFiEEjxd3cRijPdqbpI5iqssyQ2MAUtkFAmQmqzAFCRDk Q7UACgkQqssyQ2MAUtkoqA6fdTSXxiMZ7Iv8u8zEvkrziH3vZXpEv2L8BY/vlMvQ KaVZKgvNg0kVdx8aY532FH4bo8VfwqsEamXr9YPN4AUiyQR1qko1mjavMx0HOTcf uPtDQkIBX9hDyecVcHfqZCkrKRDzOsYjPJ5c2gZbBNROBRq4rDaePAZ6M8i4inCX DvgN8iirf+MPVH1VSTDWVAClAbxYl3fCAvZzkdpkIjL/6xtExQmXe7Gs9jWS3mSJ bo+MccS/c8Rsu5ZDpRjYquQbeemnbGdoBoLtd5VZvxPWdLJJtzl4AOL+79aDWmJa 9tJRD+L0sGv8/7dkqzU2fZfIcYmgSdSnxuXUlPdOue5apJeTziqNSYUUOyB4aoJm HBCWyRbmmZ4nwcvYFrBOimIz9MOvzAxjTlDbrwo89wnR/BzYFtDVGJD20nzXt1zK yR81OnZUAdAr6hm4Gxdehz4Y9biTMYrXgh5WzasCB2PYnBodPazoTM6VNFIEpnAY Y83v64/C9EhhpdfVeopfCtdcZQiAiSohfAePsb1tfo7FAPGRjqhvXJUlpzmi027J w970oW4z2KzsNQ+JP6aylmR8+yhvbNWwgjq1eZRzNOnoNgD3+dPqcNupMr1jmHj0 mlVju6O2wsFlBBMBCgA7AhsDCAsJCAcNDAsKBRUKCQgLAh4BAheAFiEEjxd3cRij PdqbpI5iqssyQ2MAUtkFAmM3gwcFCQ8NE54ACgkQqssyQ2MAUtkJug6fRRPS65tf 2Q6ZPEhm+Qmsk5YSquPKm6xrB17hycoNeekD0K6szV5rnTrTjOPe7J8I5jmsdfql UPvkclM4yY7odWxW7js7sVZH4GlE3hPqySMpvr6DLMwmTS9vAL0kUip4HCDofYLG q2Fp0uqlfeHPTnZytINA9Y2aqbR7G7AMrLaYpPsZZ1ivQu/Ud4PEWXEaDoSiyicH UXq/DsYTkk6J7oKG2oK8wtnYcSyQlCglZbAlxHg7H+1HtDZCuSYzateZDV8iq0q+ LM5xN4Ro2fHuz5+RBRu2VXqpPlW1DyHzoGDDp9e2trpSCAN2SzkIxwfPkH9tPw+Y bJHDB7RUb+/JXkew9Y3abrIrowKvaSfO40rNBnNiEhhh+spVyrRzGjKeNfcidI7k o+JDq+F0xVHG/rM2WjoMfnDzzPoviBYZDpvtM1T8rXHliAuTiAA+KX/LAvqMHYyO GwpEH5+CoAgVjCy+J4gvHRPdtBTLaKz18IkrqxNFEM7BOrJZHlU4n/twZz1sR+Fp yeHsYv++eqebLnn89Gj0zAJL9WtfHutUTkj+lDjETFnSrQ6bsk/wvTU0ST0MY9CY R81oSa6DcQp4DocDolBxSJB5djjsues7FYVZ40O0wsFlBBMBCgA7AhsDCAsJCAcN DAsKBRUKCQgLAh4BAheAFiEEjxd3cRijPdqbpI5iqssyQ2MAUtkFAmJGQJAFCQ4b 0ScACgkQqssyQ2MAUtk62g6eInZSjfcHlR2SHaStzxU/wYi4qzgNIz+FcSmPp2c1 G3+iE8MQgDVMnsN3E0x6MKq07tzWpEZumoP2DSRRE0Lxt/RX5NHwjhRqljM7x5ub ebYvHh/3tYYuzwtU3hRR+3Hmtu5UL/MYwOg+k2DXs45i31s115PvXfxjDjW/Cv6i fILUDwdTt1/iiXmxMbRXnkLax5nQL2ZChns0YcoU8vHLNonU5Pu0MBoBgCM22Urg DQ5uIyGjFV89Na6KdXvZK/DVj21JTpcF1gOqnA4hnoWxB3CPFOSjsq5yZCLl6btg 7U6fwii/UAYv50ZQghD3lqLDY2NgQ1D0viD2X5zLuLp0qFW+IdeC/a8cSXpNW1Ce H+gptoY2WcuQLtoSL0/+x6tHttzJxRLEbnEWiqFqrCe7KTLdn4XgVUz1ZxAmxzjJ bXhQXDR6nTON+LBoW+UVwTxR1ppPtG3cNvge15wYZAOdyaAyAlbHC8F6cctTQIPw lflmM8+AJFrl2sEsd3ZwBR/dP7ZAhNW3y6OqI2iL914LKEJu1cC6+LXsWqheh8G0 e8H6MtFDwBBhl5qFtdzbW4KmflwK99hFhYEA4HSxSz0BEYm3O9S8mknKx9Nh8R4y UV+FLbldwsFlBBMBCgA7AhsDCAsJCAcNDAsKBRUKCQgLAh4BAheAFiEEjxd3cRij PdqbpI5iqssyQ2MAUtkFAmFWT7YFCQ0r4E0ACgkQqssyQ2MAUtnn+g6gpLWZ9IIJ QN+tbHAo6IlVGaRtpqtRY/4ED0+w5AdbFyyA/9eMUjGl5J7v734rW+1TKBBl/oRT 4VM4K0XcIKKypk0IaEVBfX10c/66KEnt/lt6IBGc51+pvs29oOqFMm2rtfYyt36E kBIkaSAEVVOEbLlWkpqi1eQTpq8ZT19ydkyAzn2kJy4cLQXitiIo/wzW6k22oAtZ /qwuG9WCgp+GIOZI+ZOOFydLbj/79o4i7hZGxINjLCR3+GWOl/ao6n1AAy3xlxWf w7lwZfC9NtyDsPbBH5nJ/Gy8UA9i/lWRhF0Z4GFplqeg8q0L0ktIXTGjq7EKku0j T3257tj3f/yzB/NvoDRoWD11fCVZ1Q+Isg85rU70s8UJdOXd+NfIpJwKNCqLI6wH JWmTlWLV9ijsMq1C5IZ7BljpHkO6mAvZccQJHYEA6Jq36G0muymXf0+HcAg4ktPB 7D48B5fJ+eFnCy3HHIfEGI0DafgozXJN8TuYWgEvM4tS5FPjLiFmDUzn0qn7roZF U1kRsYqX8V9TJ0MVr+yvnjTLOUjoUetxSgqkayRYN5KJ/Qz1vcGOTttO+RC/10Vs ixTCAJevNnFT0OwkWEwNuciyAl/NVxRA5VMwRLllwsFlBBMBCgA7AhsDCAsJCAcN DAsKBRUKCQgLAh4BAheAFiEEjxd3cRijPdqbpI5iqssyQ2MAUtkFAmBlDUUFCQw6 nZ0ACgkQqssyQ2MAUtm1fA6gnRkLxzwLQY+crxelY4ch23Tmg7M0k5qA9PduGXVI PfGCmWShO5+KzPWby81M7yscomgKQ2UeK3XFEC/xwpcvXLi6M/3yiZBn7sk6lcXG RlJqgbzTkqy9L25gyA2jE7AT/dZDdsYB3opLiu+blf1vHJZmjBGU0wCBT7Uja2b6 coDDN/bJvwn3lartt61p/EVx4N8DWnQBtPy6rN2J1mQgFJubUp92Ngs7vt4dbKxn C05pkdrVGNcWqXnbLDoR8/33CGz4VO+Dmkrq4mWT5GA8Z5LMa/S++NL1hkyWWCkb SDYw2sqc9Oi3s17iXR843w1+qoczD9hVjCGi/RT3so8EgKZIiGhPSqSn4cUSM9Jq GmU/wnX/wh2e+1Iiz/hJHy/DmG/Rva8+CM9+xidIdCPKtb6OxElZ1wFGj2YR0fa1 8IzkuvBB/H1sh04+XKPQ9E3JNmZJ01s61fqirvw8cx8q9iMglYmUOQXYZJUR9d44 xixIl54j3G75nFVN52wte5R1sAIVl7kZ8aHGbC7lB+UOcL2IDjuKS31T6xeSa03C HALeUhxKkVghyvCuK4L3jFZxuolYTe/Grb7/k6nkjFlg3+1PaYjdCkHfIFwloS+x onnR8aI4wsFlBBMBCgA7AhsDCAsJCAcNDAsKBRUKCQgLAh4BAheAFiEEjxd3cRij PdqbpI5iqssyQ2MAUtkFAlywiM0FCQtPVNEACgkQqssyQ2MAUtlazA6fXmah+zPr Uo6egmrrn5XIch6V2GXlavrJazgoiTbxiWWDdrgvoSdWVZ9iMBR1MxK6aBRaaPD4 3r4Kk2L3q/P5ZFyZXdmRZ3Ko6e+DyBz382ff5HplhgtJfgpdAKRXx/wJLyQwKMYz cxyQVqNOwMzRPZNojk/aCAWvngbMmsAi47St008NtAiXkp26oa7ZGbtF96ln3NDj SUamSIiBqNA8dfCV7OWU9rkgZoRnw/5mCDyr0uwVNDtWcx/3qvaODuEoZhnyqI+a PWPPYANeQyeLi6YFL/KircJlRXzvdekuQSuEP1pOpkOT+0av6078GmVN2LMQvGHM pjfe27Ru/q/GQZFXzbJTtWr1LLEcTF6tr82ebufDq3YcbZQwRJ0H7+7ZI/KqQ+GX NcbcZBAhWTHkqu9IVjESublbBSm+So6xKNIRETdRY73m+vSG88P8ylGEXH3RqXk6 LsPEbbTybGygxIzweqwIcoE5UAJ38uyUh5B3T+WHnQxzPj5IqkSJE58LEl9KHS6V J6WMU7wuX555VN6rvGjK4XjD/M6cD20q2eH6nSZ2c1xYORCMBul+labW0xfLOsXz j79k24GniOBdZou3D34vfq/SdXYPOnyMvzGho/HhwsFlBBMBCgA7AhsDCAsJCAcN DAsKBRUKCQgLAh4BAheAFiEEjxd3cRijPdqbpI5iqssyQ2MAUtkFAlnNNnsFCQeF TBIACgkQqssyQ2MAUtn46g6eJDQNUOuAgVcJGdVNO+A4HUx0kejBrFcc3eyuLbX7 AiqultvSuRYsq1bSv7xzqYGgzTC5447szBiqK1GDGjPUl99MG5aRn3Ni5bzRj5HP 37LVwKsMHcZu3qlbhHctfnBKslpcKQubz/kSIygTeYSwN+50PfJ3S0v1a+sJzVrz +X0cGDlBQqKCI3Sb1FJCEUQh4qj1OBXEe+89dydVNQ8RizjceqMhmlvEqwBYkp2B nm//FnD7jn48hf0fQD+aOYkvF8oB+QI2/Uu0+qyYPE+v7uDuBXSjhHRscZMLPHe1 SFF3Tzh0oWLVejZybnqVA4MFwEFs3zfmbOxy7EQSApRmWaiBIDRwTo2ASx4TdCqW UIQvFo85RcYCDMgwlrVYW0YlRfa4RJey4y85OBRtd/UJ/eOjBqGJY9PViUfMGghD 75qHob4+UkocqU8koOcH0z/WqqXS7GMWKUk0bVF74/lUYSWjLGeiFLvgVkxf4eue ZlrZSxDOPA3k6fttjZbtWGNOWWoBZZlnVhhL2tLc34p/7+BNdeWl6DcEPNTDBXYX T+nc7hTVhWzqZbsIfB+lOzueofZM8yZNDIZq0Lf1RE9Gf1Lw7Hdo0TWttXhun0w1 mx19OsFOzSdOZWFsIEguIFdhbGZpZWxkIDxuZWFsQHNlcXVvaWEtcGdwLm9yZz7C wWUEEwEKADsCGwMICwkIBw0MCwoFFQoJCAsCHgECF4AWIQSPF3dxGKM92pukjmKq yzJDYwBS2QUCaD7vsAUJFPyILgAKCRCqyzJDYwBS2X4oDp9xT9O+VhkAZ+5YJiLz loi33XZnfMItOrMyVnn+Fntf8DmurNbq5J/U2lGY8bjEotNN25z3inpx1SMHs+A+ CrvwiIAQUv+1FpWnJDJ6vMdT7QPcsObVL4KQv18gYlu7c8vKwiXTdm+tsEsFGUPL Wm9NlTvbJ1llk3sp/3pGQp7wPWVogPxOZsL6cCt3NI8IV8iIQrmAfwuKzLPizVPq 3cWwsrnyWAPtRsif8CuEyWN+2Z00VDthqGLJz/f4Hvblg1bgnMHWlAnQ1r/f7CzT Y73Goy4EMmZmISQA/SNCMsc3j3Qp37nDT/igz3iY+5R5O0a2uRHMk140p68LfUvG kzYAqF0uGsQIXmk8ogEXXsuF8TDR+0T5cfnF67aETQ9hUPr+I3s/VKE2DWkDs94W lO6U+qYV7GL/4RpsP9uyuOdsepRCPNLV+REBkgefr4W0BktsbXszWKKWk/aU+jCx nLkIURGyw/6otAHeItDQoqivTm84P0/TR4LlTjW2gqFuXw75rj9ppWX5P/jqUjER PAYFX4KgS9R6y2gq5pJwem50GONmToY6QXq4uthO17kNy4UqEjFGurdTFQNGi4p/ 1P+XSdzNh5vYRqcf+mCNNeYHu8EqcurCwWUEEwEKADsCGwMICwkIBw0MCwoFFQoJ CAsCHgECF4AWIQSPF3dxGKM92pukjmKqyzJDYwBS2QUCZswp1AUJEwyDuwAKCRCq yzJDYwBS2QfMDp9KBBMNNagHPSDfpfAB/JKcH0SbxFifAzK2ag7Jk6rWCKjAlpCH prR6Ah4sXvUZO8aQ9sdPHkvyjkK59CUIy1TQzP8hnI4Wg37sZRcJbW510K+FA7uk ljMuiXi/LTlDHpw0D/8Bjqz+oU+r2E+986fWf3rbb7ojy3GS4aWJDs5kbiaRzn1e CYeKaoQHGUmQbZ7sRyDzxjeNryZKaR2Ux3d1NnhPWdYZKvY1KGeDwXwZS9cMQVBC 1n27sCOfuUgEu9vnbNsBJs5W9ZdWQ1lk50uwjQu5BzI6Jic7YjbPSV9c8yDnYuIy 1mw8H0RCY9QNhsUryJBT3K3XIUlgtzCSm0JLdDw6mPJSdQRl+ilVpF2gj5ehiugh Q958XT6Kizgtwq0N6gMey+ILEGuSser08Mxw1MbrGQSXG2voLlLcam+NIMVMvuKB Va1OebL8LorCw+GyujwhD71lpGCZT3VO1Npvom7dqtp8NktoEyqTqzOsZCtkyQyj LJYiHbIN8nxHOvSBSlNIu8sbRaxkSf3v+GrB4EUD93y1pHq6S0Ji9GGRFQP92MQ1 bpHqojXtmv4mZmVqCKpbjWLTarCfRxe1rrWiRzEfXLolH55l//l3mwbLzGDrXIzC wWUEEwEKADsCGwMICwkIBw0MCwoFFQoJCAsCHgECF4AWIQSPF3dxGKM92pukjmKq yzJDYwBS2QUCZd2ksAUJEadXtQAKCRCqyzJDYwBS2amyDp9nyo9hWHCA+MELnlAe G1X+tiY3CHZ49wddrlY03pR7C6E0DW9Mmld7aTvs/Uctt0TrmCniHjLWMwC6vI0Y c4V1HUjIDBq0cirg+ymX2NYI2dIhbzCGnZFiEevKlh0SbZVtCvsycNUiX2jOujJF 7h7X9mFW1HCqKp76Q/WgXMVUORLeceGE6RXK09Jew531fJrXwK169GNZQsYqqMVf T0KSyG3V/rE8Zlcx1U2DrjJb30/PRp1hATD5KgQtyhwLpYKj12Suvqu/TCVEoCWq Hd+6El7sA9xAKwlwe+QiUeH/B4ZMd08o7LhRQKbBRVR65dHBG6QAVkbo7PLcxtBy v71iEqk6NixsWoUtbu9KIfP5WyyqEBlY6rZ5qIuQZHZmVpcO131bXYq0CjKeLt17 I6SATqm4W3HRxJwHPhIwGJ/4S+taA6agGd4qMBSevGsBhGAYTznWAhtRp7MasMOy ZlXS/lLkrSswJVpY+by4mLUWQOdmdqRtdT1EFly20DIPHyOioPFcit93XAPAk5QS T9qrdXm8VUiugTvd5ZAW9L1vdrZI2rvuouXng8HQFstpwiydmtFFU8RUpNRnj22S p5cAhSdMCxZrbkanprczP1iBnyrBfevCwWUEEwEKADsCGwMICwkIBw0MCwoFFQoJ CAsCHgECF4AWIQSPF3dxGKM92pukjmKqyzJDYwBS2QUCZCarMQUJEORDtQAKCRCq yzJDYwBS2dWaDp4wru9j9e55BR8R+XLl7NS/2OxTehXhnZdVHws498buEX1/Naee yAgcPAQZs/BlI+59w9VXSyoxmTG9ZwEZZ9jQZTKWuVEa+MAXasKmrZyvmPxNS7Bx CIBhxEbJMU4ZtcpuF5b5vH0Npsxgi8axHOcKlESa5zCcO+6uoSCLbsC6QNjIbrvJ tMBQ/fFPnHoBrZuuK656GbKdbkpLrztgBqOONCfjgtSgMg9CTJ4gdsty3c0gr5lz x1gfr0qLyg8U1vMvhwdz0JtoHZnUmNO5Xgfrui2cxGVhOsrwb5fPuql0GJqQuf3Y fIXATBYaAtcMH8PHc4ZzEc7Qs3wGOA0RfMWGPuvy0vGOUql+dkVLwutOvsEgYxae YZ0H8DtiWiIoEpW7bldPy186b68D9ivvnbv8fUXUtmQ+BTFj+nqxzuadTaNm6xki x7ISLfYbUeVZM7CpMTH3GNc9Pm7V1DXZ4p69lW6Cdn08e5HRkVhN2l3doz0qV05L MqJf7EExEgGfrkU+Gp6SG29jQcFkumZoJskafCfvfyi3Abp7kF6yiB3LxzmB1Uvo voghmDYbf037vWqIKwmWZbH382OBpBCAg1zmn3LKhPIIqkqboQ5mvEBE8xQjUPXC wWUEEwEKADsCGwMICwkIBw0MCwoFFQoJCAsCHgECF4AWIQSPF3dxGKM92pukjmKq yzJDYwBS2QUCYzeDBwUJDw0TngAKCRCqyzJDYwBS2e+1DqCYo9mV9ZkbTyDVr2h3 W5DNmwq9Xre88ItDXJ1DQbxnJ5ikwB0M6O2e6EV60xUmsagePZbrQ6t5eFQHOTvE IMTvibHAG2bzndIQ/fzHwjigq/gHacxN7XKeTkZUcx07mPGM/6CkQFV770p8qaQq nPjHNy8YajHMUns/wQT5uDCORlLI96DFiWCbnxP4jPsoOg5pupuG04i9pMxtRdin fgzo4UiiCx9neptD/XbFzuGnC+6Ffb+YKurkJR2LBrJArb33okskd0LgftJva62I D0Kvy49xAcnq5/HiMP6SfIZU1IbNsXcmoS7eRGGREl0Sj7qSDsAtPDXkpNiOGt1c wBGlgEdayuD+C6+7m77u17hR/cAj/2bp2k5YTQae7BfIIyTvOVGSZluINhOljXmh BA9qz3x7eWgRY+UnWOxaj55TQANqZUUlfvju3GRe5dgewKYd8Bk7rOuJYBLibw/j phbX5TA4RKyfUPyEUNxyZl07ajlgcFGhM93hLKO+TlJAuvgoH/MFSiUK5OzI3wy6 R9pH6NYKm805dvj5tT2LZaINriVpaiWPCfRdq43Hxe6ceIUbbLxfH4tAQ/rAuwX/ elAW5peXC556zEyhBxNcJ3gOW/bo55jCwWUEEwEKADsCGwMICwkIBw0MCwoFFQoJ CAsCHgECF4AWIQSPF3dxGKM92pukjmKqyzJDYwBS2QUCYkZAkAUJDhvRJwAKCRCq yzJDYwBS2UKJDp4u+gHgK6Ygso4vMHWVwmAAhLZz9dRrec4w9bD1t8b7DPfhCnlD Ia02mqI6Gbk5VLe5a5NIJp0DuXMB2OdhZg4OMR5iqA+sWdo4E+m27OhQv4P6myni gNbujkot9RIFGjb3lBfmHaEYtd+GYVyDrpxv4C5Z+LXfTvsuUZ8+vKUbNYtX9oIg X3jGKLgAyHEfjm5oCn7PyAnGi8IVYS4pbJZ11qyQoiv2mlKMs6kHZNE/ECOleI4U n+OWwwOHLNL0vcW0otSlt9awTeLn6PBnz0e428FihdXspVGXpSH/ulnU7CcINd/i WrGljw7FRV4WbkBRt7TMONqLOYMDYdMF5K68LGbjw9tR9Fw4f2coAHhJNCPtOFDX mO04IkEbLh5nbPDi/B/g/C9C2w86ubID3nuHEX1NixjGrgrtAZOwDl6Wy7MzMJ3y 7Z4GD9SXce+qS8XIXYIwWK95qoBKEiBI2b8dw2qWjZyI6wIQBIqi01w10f9d21An TzXpns4Zjux53X0FH4+V2uQJayadKJ29eKvcI5DkNYp02Zg0F5gKSdAtLw1p3TJK jGkKY9wuwKU2pAt0whdkWKKuLuJd+kuFql7uAyyveOqVNyhRCDhdxHA3BBeq2hnC wWUEEwEKADsCGwMICwkIBw0MCwoFFQoJCAsCHgECF4AWIQSPF3dxGKM92pukjmKq yzJDYwBS2QUCYVZPtwUJDSvgTQAKCRCqyzJDYwBS2X/ADp97MQxWRug+QzY94Yuu XI9q8RTzep0hZBuljoFAFiToL4SzOA95epUxsXsxRCp+KN5yAgL5RN1Om2x8RULZ iIEYlFXaUi8Zof958+fyLBHYAiBIYvqMPL7pKmRLgrL9wpI91gNU9BrrtWuBabp+ T+/1PdEgFP4wQ+tkl0nN9RS49KEraHExE1lDNiKeayaYHVL/Q9MQ98ds6X0mN/jC 2p+NEX/JD9VZzwhukzIG+pXbf/XXJSF35jWnaE79nTH3pzY1cRxTx8bjuQNr4xEn Xl6t2qKyTxsgre45JjQ6pAwzyDFi1jON3wceGGIVUU2+/bD29jEJnQQ9He018m+U 99J1h/HHemuj8dgD4sDFQm0t/TRM/ll5BwOKfX4vsptoUSFxaMFaKqGr+nilS4Wf LvZevX8o7lKn2YKN5v6i14aBMgMG/giycTqXyOpc5r0SShRhl/nv7wQuZRw6Eq2+ WJjAHTv1z6k6Xws1H5M4ZzDzUvaD+KD5AppuhSN4b6xyGa/Uh/lKCe7UFRb4GWRO fpWwdl7aSEK4/9JmHn3wxh3wHba4rM3Lm19cpfmfufD+bTjrj9hkmcoKLg7DRRJQ /uJ5L1aCYPlPDXSZLB3OG/Hl3GiaOFXCwWUEEwEKADsCGwMICwkIBw0MCwoFFQoJ CAsCHgECF4AWIQSPF3dxGKM92pukjmKqyzJDYwBS2QUCYGUNRQUJDDqdnQAKCRCq yzJDYwBS2RPgDpkBfLeL6EOQuR54r4KokmwVkppUPhTnSvD2vYHD9JVWlhtMWCQl pHxLSe8bIY9DLJl6rihvevf6tK0NeLQtog9ZVhKOrAlmQjx/Hzzw25GeTdJQhoIE xKzZ+AtxpAjn7I2V3F4Gk39RALswvBTlJryjWjS9oVOSiwiYTCyVrT3br9ftVOog j21/D+KRmRI9r3x+V4A2fLIV651iL5bXrwmXHNFscCvG9MCiftTZiK3AGk81U5yV o27MylQk6ujAhcMmGvWYToKVi55+SYWG1+EjCjSTPFckqaw0U7/Cl6sI419e9ssO 8GaxEGkQam6PpS6C+KK7UfeNLWYMCe1kqnDLZ0Wcd+wJD2NNN9WCs11+EZXIykDn Jrg4SLnHrTbLAZ+k8Iomym8jgV3e7ylM+HFXugk39/rSzguhIE00gBUb+b8Zo+i2 5RSIgeiTwT7rqRy6rlpYVm874AogUXqjrWueJcnzqoJDqHsxrD/OSFMzmp4UAW3P BWcPI3DGekxTu3iICcuUO8AuH/GbjgjUu/YV0A5S373L2O05NWwqoSP2QDwkGe+U b8hzp+ENsJe/x1mpN6N+GaCmg5lBsIypAmwdgW4refw5NFNdAIWbv984H8JEJbvC wWUEEwEKADsCGwMICwkIBw0MCwoFFQoJCAsCHgECF4AWIQSPF3dxGKM92pukjmKq yzJDYwBS2QUCXLCIzQUJC09U0QAKCRCqyzJDYwBS2W5NDp0Uzc7YDw0nP1HWoa+x Z/cJXKJmsDmiTf6G9WwF93tL5rFYiPJqPly0prBpWL5CwcRh1aRFhbweAe22SSPw NFh5EpjmuW9lonQUbCPnKSNPNMFjiOtB6VHkXYbuIGfWfWy9XQfEku1uX8DxvY37 T8L9ebvNo9sRPK56XlabeHzRdBfxbNVGomY7kPpcPX9pknsI/K6OyHavKvZV7382 gmBSgJQE7B272/qfOXEmi9xDaZahA9uHGrorv+h31WJ4dTZI2sdRW/W0hjks8bwg AoyG8DbDTDRcLkffPE0euc99M5zc13DhyLYmjFgxJwqdF3DZ+DDtv338FyLgV1Ml Y+DkLY+3TBYyI7TsnH+Gbz4Asc76KvLlzgAiMjxWjWImiQ8wiLDdz6MQ44hCMRco LkNXX+aqKMIfxjDx6IxiGK9h94pjfLwS9m883CsIvcmQzZ+Z2357dV3sc6gUn158 OmM5bbTI1oMjHEkkvyp3Z/hYPoGygaNf27IamBrG9tcITDM9i1AgfACHlPr7Ot5q Iu/WDPcD/9znKEqWBvXsh6r5Qij4b62mVLPfaHelmM25QizNMzQPTXNA0tTA2eU8 AvQurwBN6/J1TVuJO5n3xuBhpmBn91HCwWUEEwEKADsWIQSPF3dxGKM92pukjmKq yzJDYwBS2QUCWjzjIAIbAwUJB4VMEggLCQgHDQwLCgUVCgkICwIeAQIXgAAKCRCq yzJDYwBS2XkUDp9QLXbWzqYIvZYm0Mq+HEgHr0VpRmRuNp9tWqhFEw8jSAccIv4A UzDti1LCpPZPFstg9z/ttT6lYB7VhPqmTQ4tmG5HPIbdS5loxUUdynbqws+sZE6B 2jhHv0xRQMwtiS5gpFxOt2Lhtx5mWYikl5S35JjzNwA/ebh6l4Q67e+skRHXGCUW Z40MRyMPfaJMkeWQZzIfD41aGXqXwu8Wi/hlXJbileJiOeNs4ery6Rs8gM95p8yS KyEbXx6QQsaga4ukOqc9gb0BheLLTcPpnB24sjvvRsyQbnOErMOSwgZDzHQlQ++2 uhKWA1DB7U8WUNLtzZsizcmrAKm4kKUQSoqS+DuxqYpdY0I7be+X25yY7CTEybbD i/T1aigWUbbyFKwf4Uv5E4RTY/HUwltRqFF5NuyGjzbw2y2pnjpRaZxZ72r1gPCc cGPawoTLjt/aZndmhHXByFEjHN7t1w8tRM8ygTKMAUrOyijOkT4CH+FTAiM9IQrS R89pDHiNH1AoLBOcysc2zXqdVQz3OFcs2XBvat0bpjR0DIGWUu8ThQcAXzHca//o 7fEL89TousBSK0fSZWJX4YcefeNZXug7c2ZXbvH5XDUOpk71dkw+qaBGhHMwRBfN JE5lYWwgSC4gV2FsZmllbGQgPG5lYWxAd2FsZmllbGQub3JnPsLBaAQTAQoAPgIb AwgLCQgHDQwLCgUVCgkICwIeAQIXgAIZARYhBI8Xd3EYoz3am6SOYqrLMkNjAFLZ BQJoPu+XBQkU/IguAAoJEKrLMkNjAFLZ86MOn0bYAE/UQDl1qMoz2QxC4Q0LSNkx MawPl9LAgrYRagrlQhHzCvuyKdETrEuNHjFWWkKZvy7q9TeqOWI4d2wbxdUfgcV5 dQ2x2OyzqAqReWKSMv4sB8p0oquFxbWgMqWuAq0e+bhF9lfK7pshTclzMhasv/zm UCDSHG8ZApSxYgV5NMbfSPd5DiY7jXDcX2/sJRoxs8QGYGaOtMraRqao9/JEb7yO BiyUcfYcv3WYIuDekIQ1g6AngfEvS9RNDtJ3jpPgQYGu9x6lYDA5O1n57QZIMKu0 nVJFAOWI9+OAc4nRkD9rED9Hox8yQXdjXweyqPkLh74kHOVvGuTdFE0GzWIpAQuf 878/Sd5BpKyYW7jSMNym7CgR/t+1bVyoh7HPEY7lRF4ctBw0xu0tu1h21bLfZqLL wet2cGa1wqxb6Wd0Lq4JpYeU3gYdtWCpvYR1y1g/45A/26/TyVDliN4PV8cy8cRe GCeo+lORGOkc+QQupeLQIJZm2VyvfFZ4s1YkscK26qcF8ozlUCw1nZZhu2jn4+Nz ybkM9lrhY9Qw357evxrUEuxuV4SQK8JcXV3L7g/ZVhGt3cZBzKBfs3nwZQGesSuw sgFU+BKoGP5EUI1/Psknv8LBaAQTAQoAPgIbAwgLCQgHDQwLCgUVCgkICwIeAQIX gAIZARYhBI8Xd3EYoz3am6SOYqrLMkNjAFLZBQJmzCmkBQkTDIO7AAoJEKrLMkNj AFLZ9HkOn1p4xeT4g8+FswnDWuz+kgqkw79X3xxim0VWW74bNy36K9bPoZ5+Dm0I U9BeHbZJ6OtNkbdbpbWhZcJZ3Hj2f5vpZiZqGYG91aUkrLys40AbX824IawAVVrA u7/neEFKgC/Nh76aILZglI2183O/U3GOZir+JhVmnDJ7VzLXLu2i/lb/I7mhNFiD ZUrSTH4/Ri+ByPsWFNcBNifwTDoIKoFBqTmHRAOSmJBe9GNZ6sQNOpVddNMORVhr IX0BOUDPoP//0GnMZjvkNVz+22PhpXdsnpfqJbKqTuYYQZqt3J2Njt0tmeTDMoMy +0Zm8t8ILGEhafZyacWZ8r5UUQq+wlXVhXAdCiIgUfy3euKP7rWSEDSoZvv7CwsR mwxRv2Zg8tu4kXFl9CDKKk6m4aZXOLNU758SDnNxZT5SzLL8Cul426CwP1XsfrFT Dx25gWoUT547AV0HP0Ag0qPejxW/l1WH/Q0kXk99EOyHOdsTqyJY+Tl8c+G+lpvv 4/1It1fEok0CxZnLaFFqaUz5CH3RRP48HcYV7tV68zsfEs3Pfn/xu4LnIBnIBQea FSuil6GOL+8yUQMYCDczfKQZBxMChCyqekG75hbihxdhjKG18Z1oF2OpoMLBaAQT AQoAPgIbAwgLCQgHDQwLCgUVCgkICwIeAQIXgAIZARYhBI8Xd3EYoz3am6SOYqrL MkNjAFLZBQJl3aSeBQkRp1e1AAoJEKrLMkNjAFLZP+EOnAw9drSqLDdY2Ik7fFl6 JqT7l+YyZJR77MNnM/dyjxwmqmqU1tkDgrSZe8T4Vf9cnDrHXh7kxyXh3BMigfbQ HJPRra4nRth9ZFdXQ6G2UdmZ1afGlTzndNjmaIaDqW9aotWinivr9P0aMyFaCYBf /gF7dc9rQ46AWXjJrJS8eYU5rLs7wh4Lokx+CLU9nJ/UHHc14nhQ//TYLMCEirSM 0EI82D3FAtSrp2j9T1RH07jTTTHlOwKX/rIEF8dhSGHAk9EDVVujRiHECi7lRUiH 94/rZCpHaSvExMoWMgSVBomwq0zvbuC8wa+nornr+67YsOs4VF7k3j57gT78UH52 VAx6cR+BBMx9QuX8FjcjzNZ9zQaUZ5lVICnSEdD/8044Cm1G3u3+ZN2hCMLFxbOG YDWMZynxt/zKvVkS34do+nMPAApH4udlPbxm6UDopmub/oWqUaIS74mj/FI065xH 4RuQAUWi7wt58XaYVRVhEZbafbcTFz7oIFfv+iRuF5C+R7BeJpwnRJYitIyxmX4X Xg/NvbdmI3f2ySLsfyd12my6HOYb18d1r0QGqjI7Uoz8SRXlUDyCTKHFRmEcuxtq 59wZ01jiCa0dQwlVRlCBQqbsNu30tcLBaAQTAQoAPgIbAwgLCQgHDQwLCgUVCgkI CwIeAQIXgAIZARYhBI8Xd3EYoz3am6SOYqrLMkNjAFLZBQJkJqseBQkQ5EO1AAoJ EKrLMkNjAFLZgtoOnR8xmvB8saoLwsmJF8j4fBX7b0TsRZomY4XpLbix7CvW3//Z XPiZzjUTpsOmsewe17gkSbPIKdedeU9qhvI41ZQhthyczzvbebImaXQO7gS++8k9 SJ4yLAVbNF2YXFz95D7pp5NNSSc0kpWFjXz+qwCm9hMiS+aV4+AtBUa+mdipDUSW Ax/vrwonj6VUyYhPb58eUt7lHIlGVlyNWvJXkf187L5NCqEsuhMWHECIkEAJghQK CUaCNNB6OK9ddFeOG8ym+UHNLHAj2JkZlQvgUTWSMr316miJy032K/U7ldNlhOqR 0F/U8Pv0aJwcBsboijwXTvFH8KKNmXbuXqstvnn3/fcizCLoxryZ2eZ5MnV36Kj1 du0AaE9morEzZe0ptU7y4mWLpgKp4SlU/79bLZyDhFKPjZvnKumVupW+dt13Hxlg KblJcPo9v9mutSYqVrK4zZScyz36ycgYb9IxFS9vHY6rp6qv1Dvha79YtrcPITRR TiuITvgwfk4uLNZLa1ZMec3GWpStO1avMrGdMLZuslkq1Z50XhItWljV+SpSgHyY zZsA8C5QA/BgHWNCZUYLtUtz9/Zr0pkDeFCIIDFKfr1q6T0VkY9C0FYubklFtnbF ocLBaAQTAQoAPgIbAwgLCQgHDQwLCgUVCgkICwIeAQIXgAIZARYhBI8Xd3EYoz3a m6SOYqrLMkNjAFLZBQJjN4MHBQkPDROeAAoJEKrLMkNjAFLZxKsOn0R9ommmBh85 jdn2+05r0oDthUZpRQAEAV6mzkGzPWERJA210Hy5ierwYzQypAscmqVeL4hW331W xu16a8UUF9lR/KPp2pS0wey+9TrDLYpGRb27iTKml3wEKzOOt9RdgN/CcTVGnsTZ ukNMEJKV+gBaBVPd7r3V28De7GDf2m5tgv++Cyg0G08JxXV8omGbIjk/ZGQsNyBS i3OfQppA9XWx75WmcBSjI9S9iGL+NxpTq4EP6unaDWir9mCryMBLHVdG5RhrwVB1 CCwDxaQ7RyaFbo/Ws/GpLjbJuGnGJtTUoEzcpy5g6ohi7Fia3b4rIXJIRkHe1q/j qJ2CAA7+RIAg/RjKNaAbjJCpZP4XnRwEW9stIOPOcCdu1HG9NAkK0rdEjoByDtvi kaWqi1bneHC2CJbRuFDIrBTxnhV19+U7p2RSM3xZ1T4pW5jk9SnBh3EYOf114S4n rRVsuwr9j35eupAWNf1l28P6+fTRomuU2cAoOgX30P8LjmmuznTer0avTg4JEpJm eCBDNs8edrhYm5kZ5ph1SPMNftPMlWHMnSK1Aldn5QzNTK4lSMXB1DATJAMyFMDD suS2GfQ5DwiVDVEdMPi4WciAce36SpC2tJzZeMLBaAQTAQoAPgIbAwgLCQgHDQwL CgUVCgkICwIeAQIXgAIZARYhBI8Xd3EYoz3am6SOYqrLMkNjAFLZBQJiRkCQBQkO G9EnAAoJEKrLMkNjAFLZYZ0On2UvYdjhAsLQZcb0K9EWVrEIp7kUS0u0C2LTioCI kb32ye9aRYMoauJgil13975W9TL7/AEXSc5Qg/QgFua187ELqCsCslckS4OQLdub lOrpVX0kKAhM+W7rxJuobDOTSHGBloW56OU9tGxSkwpm/ESTR7Zv7gJDJC/GlBCe 2bl6rw8ykWwQiIhfvIOsgfE5ojp8JjktgvpB7IUyzstvC65ejr16PsoDeGdju0OT oDGagxlplIZ5r6MlcmPc4e7cTCh62ZT4o5FR52afcTx9MqR8TEc1Ij5MyQurQtqw /zS7qMAjeu8xaVCcFmQHvr/Z2ZjepL36O4cN1Up3boByGTH+oHpaL9WdTsM4n5Zr 2sMwhevI308UYwt2/ROEOHpbLv7uc45X775BUQAmNSgcLUhteqddIILz0lVBbmsV 1CIeZiEO+Pz6se434EKaUGIIlYqE7Oqf1tLOYm9knpyYtB/CqK+dyzz2od7QKez8 B7e8yx7Ps1yj9KzeAQb5nHnpEekkwG/5TpLMWGqN33fgeJcZtR1PM3atN/6UIOU8 j4z47S5dC/hsCVGmsSxysARY119AqUZYUz/f+97+yD51eP3nknfyPn789A1JkcXg Iktl1scMkMLBaAQTAQoAPgIbAwgLCQgHDQwLCgUVCgkICwIeAQIXgAIZARYhBI8X d3EYoz3am6SOYqrLMkNjAFLZBQJhVk+2BQkNK+BNAAoJEKrLMkNjAFLZKOcOnRG6 kdoBqmizCtc6UmEtzBIHSjS0TyM36pYbVdOggGKYsV95pEDXDDsXcrHCzIY9ZM++ ToTAKcx3Y53SZTFC57YtJbVwV/Vc1xZJV7R816BPOxXaPTtegu+3Yd94ckmck9SS 8rLtyJxn7sK/EDeDyG8upg4PIi73o23Tl/ZwITu8Rhfyz2AY01Ub2CbRyI6ePPM3 Lj/o7tGocYNCaF52kMUOvA2jCNel3lXaEfpBx2Qbt1POlCEdLa/+pJGrB/e3QpmN 7ozc3ysVs3fTPhvL5FOwUf9fspceTwNICAI8J/ZLObOeuNv8LAd146lP/GMLYzOY 7oivcFeJenE1GQubZVq1KpBrKKbkpnOJeF8bi48a2MlyGTYKU7KNPFgxVDc9dBI6 GauG3XZEN6dI4HVIkIDK2phsu8ENsNEE6Ze0F7I9EAbQPA0ytvYqRWRNjVeEA1XJ 58y1mjLoM8EWv8r9SjGWLyc1I/AX6F37uZptlo1EsiAYOLjgWt42/Q1tUTJFX7Dk hIazgaATrv3DnoencvJy81FGfgrcvaulkQBCsl3Zt1SyxorKd+xfTglRr6m5sHX1 bKqicEX03fjavkkowOeOAZGmLJVhpUYMFgLYNCwW2AW30MLBaAQTAQoAPgIbAwgL CQgHDQwLCgUVCgkICwIeAQIXgAIZARYhBI8Xd3EYoz3am6SOYqrLMkNjAFLZBQJg ZQ0GBQkMOp2dAAoJEKrLMkNjAFLZwBoOn1QierIhr15SUjmIttoxEe7EOaXd7WHy JVfnkpbggwbfHyl9/DiElK//ZAPmYqp7E1EnFbGal6qusf47Vqv0B4zKm5YjKadc 6B7rIUrAaXeenqqrsWgTw+gAGA0YgITLHEtH5Doy8TBxlOOveVz3EqjgvjGTLk2Z U+kcRjAfOSpo+xi6cJ7KJjdvMSU4Q7trFknF9TNDFkQzUG6rZblXB6rAivvFYgCK jy2MGqxWqAFzZ8N++jC/cyDaaTBlQgvDF+dxHfMuof8p5UKp8zfa/4SuBVLKue1j D+f9LJzpPwHBtqa/EtBUx1etbAjqflBUKdrHZ+vmNO35bb6swqJg5crDwIddXEaI nw+JOPFhPHSRRl76KHvU/yAHCW2LrUyGmmYrgGEIW/s7wVP/V+WCLUaT4WF4Hizx eibRaQUcnXbc2Stuwmm32YEHW2upa3H7+TH148EAx2KORyVaOgYtS8heFFpiqUGw GLD/CAPRP3MvcySSCZpFulsYWs4uvO6lsnjEZ5g1ZxEbxpaJ4cxMeIx1OyZieYHv eAQx/PWx/4t64UcK6jHpMs4zYsPFG3xzhLRJCCm4FJPeyGCGq+nhqrCAyTQJpBeQ adyCmXQKsb/ICx1jUsLBaAQTAQoAPgIbAwgLCQgHDQwLCgUVCgkICwIeAQIXgAIZ ARYhBI8Xd3EYoz3am6SOYqrLMkNjAFLZBQJcsIi6BQkLT1TRAAoJEKrLMkNjAFLZ 76YOnA5g0BjPqLGeKhfEdntDVCs6Yt2bubYvXnnt11JlCmH8A+DKdcabnPpphrHP OrdBGlstm/+KhVJ2otV0y7LpZ0mid+foX/nc1YVjqLCTeIDKO+ES6Yt7/08e/vnQ KPgBxlF6qGsUsH6ckdBSzRJIInrFNziBbiHqdKJFw93vYVTJ9m5IGP8bRJ3lsS7l Oy63DEHMJDXqk9q6+Y7yVqz2+wH4lhgJ0KJllYqQGT/0Vv2od47cjRSimCbLIb3W JnbGdSufXtUKKriz272eNGC9RNQ7mYxqvm+XWiMUzXeDEaegaUP5BGPsTGu6hRjZ YHKjbcAyApYmk/8OUvK2f0FJaZHRsIpDsv2WBQAKXwkgap1nm3PiYfSS0daP9Kob TzP2m+lr/r4d5uixDLryiWnUK2jCAxONuy2gNwd0rRJxMesOS/1kV9Kop4xIpGdx /wzTh86fq2bDRmZKpv2NdZkNR+gOPd7VwZyJmVNoAINKP/MN48o2w+j8xVRhVkJU /SpqOXL7pyR8dUcnL9lyS4Z8rxeuk+uHXypa2TGlsWn0judK0PkLSLfdZdmXqWHP dmfFK8sHMt72+I4RtaD0Xrd+2f2U5j5EnQjQRjbFhsPVnPZYjyCnR8LBaAQTAQoA PgIbAwgLCQgHDQwLCgUVCgkICwIeAQIXgAIZARYhBI8Xd3EYoz3am6SOYqrLMkNj AFLZBQJZzTZ7BQkHhUwSAAoJEKrLMkNjAFLZg9gOoJauK/dNNH+tyzxaiExoDTQ8 QW77GrVx3XQMwqxg3iOZ2T7ekh09gQeX/4j+Lv/XI9Co1Aj3dw8zfLnVt+f2pvHi QbnWCTTRIuNv4ymcEZUTV2xZ1m7U8iEKbsddYBHHdHHYgPATs8U/S2pUU6/WSx6b hI1jtjq4WI4fFDj1TCf+OkCQ6ygAYSc8tqPlIaIJr6yFa7v3T+9Dh/+/S+GpWnoI lcFbAqFvNOYp9VNnrlvL1YZ2BXGKQNIAD1CJ+1ZtUgHb5RtiSphsoGVX8kegOY9x pGRFVJQ17pqODE7VEh8QbNgcPPAb15xAKYdz3fj0sfMVe+r6fl3lWzQCrRLvwZ8R 0MtS1cmS0pjS4d4MC71kkH4qSUwjhWA9FzEvP8tsH5mnHaPFBsi0J4/XN+JBuVEN 7zD+wRLrHhSRB/+3SGHdkrN2JqFlWNqYNoOiSng7/vAfdLq6L4WEaawuT4dYv69Y oyDtIrMtAnDz8TeBrmzteTApqCaTuC3/YT68QGoKUVzd5asJMa+0kwBe0L5lrVOd PIwO+3T2nDmBgOFZyCFRrwZpCvdvH8volakM/dtwP591ZYhvg235h8lewnstynZN 4pAmmJj1uNXhSERM2X6z5/CqT8LBaAQTAQoAJwIbAwUJEswDAAgLCQgHDQwLCgUV CgkICwIeAQIXgAUCVSOlpAIZAQAhCRCqyzJDYwBS2RYhBI8Xd3EYoz3am6SOYqrL MkNjAFLZ9PcOn0Hm3hibYknfxnru43LdXA0osBOBevQp7jHa2eXlubj736z73goN 5x2lfDuL3+VT8dUpfsVkwBxVczih7JB42GeNQVe9IvIODdgsj4zpguVlhkBVIen9 SzDl9l/tmbTbF6KA9Ja1B0LDs3YmzTmJg+B0QTBSxZrbwOuu3xyMCxDJNklHwNJn Z8CM/pu1Pi229rjTWOWSTrc7SYgrsmlqGUauwbyPrR2bYPoMZhOSEn1NGJYTh1L9 4M59ZuY5GTmDTab2jTCwIE15gcmMl3XHCikCQHgZgVGkxpcu5qtsL0KeUIEKc+h1 ifL0nwmt/FPpjryOrgMQmFk8cv0JWzGSa5GPBZ9A8ava4ZdtFTSv2c00oOKGhjk6 LhE5DbDroGdGf4N3XSs2k0IjBD7NxiQeAxvAdPSkaPZAwINOLWD6INzGZ0Jy+2On t3cupETR6JZLhUl+k9LTYdp9vJDSyDcxRQQMeR4rD44MclJcxikTlJ9Aq8v6t7E4 vgY/gAxDYo5slzc8g/GUM4+xsuD300voX82t4kiw2VqZ2PEFc4IUpRaJlAO1ROpe MnG76XqbqfloQ1gxCpDaaf31DeXECr5EC3iu0P0NJ91rUY039IOHjRTX9UQJCsLB ZQQTAQoAJAUCVSOa6QIbAwUJEswDAAgLCQgHDQwLCgUVCgkICwIeAQIXgAAhCRCq yzJDYwBS2RYhBI8Xd3EYoz3am6SOYqrLMkNjAFLZvZsOoKjvoLatMgPVKXFFLZis 08PVqEruJkUFY7RVR8Dsf7exCDt9t5IEwqoi66aC3gkaDRwCNq1sBLCI8vxklE0u bU5fqvpjN5Az903U+xwYTFupqQASh8ARybHpAB6UBn2Q+aHUZoJGq76drO8HVMcJ lC+R5KsQDN8YSTAdc7uST6PXYi5KW1BgKh+owr3B2KmacxG7GPDog9SPIvp/MJrc SnR2+SX5oO+oUCFpUDU73Mkqp8dxhoczm1176T8I82S4yJGJlIqGGSlaRM5l+ttw 0llZRNlORb1GXqMo13Ka7cYLeLA39SIOAPCh7c17Z06SSG2VhzaUTeqidE9Jz96Y 9vaW++v6tW03qC9hqu7ThFN1PRQCoOpqzKpepdeLBUdnW1TWOmGhxKN4M3K1ZmI4 EG/bytBNhrRTfni3eg3ak2i3dYuOHtMzLaSLAF7EFflmik1OVeNCRTCYJ46sYBnp fQCeRNgTViiMJ+mCFcTxf6IHyl3uKPDOFQExnvjINxgP3ouaEPNQiA2EYBoi+xYe XYM9KXZ55RC1Z1PZCfdmaDTcvMJ4qLP1OVrzwj8RwhrL4ZUpILx84T54PaJquj6B jgEPd0To5ILPR+zc8Qmx4iWcTUR5f87ATQRVI6aWAQgApzVRJF/utO+M73xwoHzr okN49VSBcxkBHh7G6GxzX6N5DvBBhkh4mGVZ81q4dlMFMtpq1xBb17Hgw5iWmjv4 Px/2rAeCfOzk1zSC4OMl5Lby36/CmiDjcJGqgR0tqXiJif4B82MBgJINibabiGG+ +iHQm9kmkWSVquAMsq7YIyTdgAdbuSd5BVih1hjqzBrB7AMyfRzDSCaIDM/ztKjb qDnsD/c9tFvMQFMMSYzGfkcdUD3HTLBxye1XD5Iav9AGP5KNMuYko2M/cnKemxl3 KIZlpcZR+aErj5U5XaOhOt+kuAUwC6RcLtPK4YhOA5tCTe9lHODdbk/k7BGXXeSQ VQARAQABwsJvBBgBCgAmAhsCFiEEjxd3cRijPdqbpI5iqssyQ2MAUtkFAmg+78gF CRT8fLIBKQkQqssyQ2MAUtnAXSAEGQEKAAYFAlUjppYACgkQciO1ZnjgJSimlAf/ eQTOhj7aSKAgiMYK3JaMXd5Xa8YHPi1OJKzeYLWEJDQ4/2LW6Ke2r0a36bTkuVJM W4q73VDRjcCsIMA0BtqAOZLtFVKFsg2E9lfsjcIX87z1NvcMoNMJ1oOG546uZOkp fonb1zajlMNtUiMZyNUyy9BDIz+jr/LsNc/wF2TIj97B2wS3y6T1PhWllMFXWKDj 33zRjNFrKSlgWg6fQv4xdx//yiQCmO0x6FEjM1F6NhPfTfDVHxd9AgoNZFEYPBBc 715VJqLD5nYRsTihE6RjkiZqUP6wrgViezGcrjDc7vh8aVgJ3dDZwCNJJ/JUSpyP K5SiDpaByZZ4ozEJMtmk7/ySDp4yKg+YTwmnkwW2cwYyX6bjqStiXq9j1+Vt4a5S 8t1hseJuKKi2eI0uypOaP+viBZukbYt4VWcMJ84zPRSAtdeBO4/4fdinHzVij98G v6b/4KVs9IE0aj3Ow/QakE/sf4nye7PWRFMH4hCVafkFnsYTZqMz62EZlLxAd2nE YfxtVoMM3MtP7AFy3U21AyUnoiddrfw8AkYVp3PEQajvEB4aXM+YaADmwncVLu/F Bn9EGCCTejFD12WyTTidiXcCnTDwQ7+K2jTs7kqLEVxga5M3Wbrgp3TwXSs2nbeu +vAuVSMuZ3mdSQI4LWnPpmc9kjbVCrtfAfZFJOZspLgUqTij2DKlxjbCVbjInyFA feBiX5YP6IZeY+wySLBoBZoEH5eMdf93FnV9OXARzotjVrE2r2eSYN++bJk6PA8y PJXtLC9CgycrnWHOqpmpM7Wo6fmvb32C83TbkUah1qWN+Wkc1hOiY48f1nCPvRU3 8y1KcqSlkLyebbWLOWrHEKMjVitBWiA4OFL+YG67OykFTqEUkutK1oUcNwV5QsXY lEsroiRbd4YvGCNzcUAALzvZsptvm44QiVD9iYgeGxpItXjVzDIcqmnoGWiBkHu4 a6NMulCEs4/CwmkEGAEKACACGwIWIQSPF3dxGKM92pukjmKqyzJDYwBS2QUCZswq CgEpCRCqyzJDYwBS2cBdIAQZAQoABgUCVSOmlgAKCRByI7VmeOAlKKaUB/95BM6G PtpIoCCIxgrcloxd3ldrxgc+LU4krN5gtYQkNDj/Ytbop7avRrfptOS5Ukxbirvd UNGNwKwgwDQG2oA5ku0VUoWyDYT2V+yNwhfzvPU29wyg0wnWg4bnjq5k6Sl+idvX NqOUw21SIxnI1TLL0EMjP6Ov8uw1z/AXZMiP3sHbBLfLpPU+FaWUwVdYoOPffNGM 0WspKWBaDp9C/jF3H//KJAKY7THoUSMzUXo2E99N8NUfF30CCg1kURg8EFzvXlUm osPmdhGxOKETpGOSJmpQ/rCuBWJ7MZyuMNzu+HxpWAnd0NnAI0kn8lRKnI8rlKIO loHJlnijMQky2aTvdkkOoJ80A3rZBsdstzkg4/OsXzzxQrI82QoUs1BPWa20UJRi sxHiiyXBAQ5OyHi2xEl5ZBV6KPTQrfTLHle84p+k3RCErKoFSu5iOWaKJIvZEDM6 d3jCyDfyheLKWWEy4f06uDtGjDmPSM2jivnGz2IHJ09VwwC/VQAbr3Y1jAFKeO12 ILBx+OzzZvMHz2aaFF7zJZbPuNZQ2rr2Falq40aMt07Fn8vTDCMUAjcKBOdlDgkA vKX+QCu0s/eXBK0SRpyHMR8LgSjoVhcLK1Q11wKf3SUWiP0+nBzbZ7WA5cltw7ip EvhNbgTWl5/LhVgrHfC/8UBF6qmHz/C3Wo5Uc/9ynFZQGR0BlfwNxU25IQFdeHvV 06cIxKYI/pM7fw8vy7cJaeQ0SRPYE6YUdGlidQzf8YhbO87FNqd+AgplBSJ5isZA rYpDJNAfcSgH5EhoGCYVk1z//6BZUedFOhOit17LzRrdEZ7B0x5A2cv3nb1KVpRg qPj6viF6uCt3YxAPLpExhWMonO5SePSF3i2GxpvQZ/h9vV/XxGpivj2KvhaWrOmU GDJzxxof14Ns5j4X7yhd5YoveB3P7CeYfQWCMTieI3Zh5wHwOXx5os1mbWTeAaqm nnO6xsLCbwQYAQoAJgIbAhYhBI8Xd3EYoz3am6SOYqrLMkNjAFLZBQJl3aS+BQkR p0woASkJEKrLMkNjAFLZwF0gBBkBCgAGBQJVI6aWAAoJEHIjtWZ44CUoppQH/3kE zoY+2kigIIjGCtyWjF3eV2vGBz4tTiSs3mC1hCQ0OP9i1uintq9Gt+m05LlSTFuK u91Q0Y3ArCDANAbagDmS7RVShbINhPZX7I3CF/O89Tb3DKDTCdaDhueOrmTpKX6J 29c2o5TDbVIjGcjVMsvQQyM/o6/y7DXP8BdkyI/ewdsEt8uk9T4VpZTBV1ig4998 0YzRaykpYFoOn0L+MXcf/8okApjtMehRIzNRejYT303w1R8XfQIKDWRRGDwQXO9e VSaiw+Z2EbE4oROkY5ImalD+sK4FYnsxnK4w3O74fGlYCd3Q2cAjSSfyVEqcjyuU og6WgcmWeKMxCTLZpO+0cA6ff+ngQvCXy5w7chZDslpe6843hSZIpFm4wOS2rpj/ TJBDwAa5FLOSdpNUx9GkwPUBWe6w6yHkEZDtGEDv/LNEy0+XPNK9WSv1NJJ1qZnc S696cBYS0DWe93+79/OUXI08bPv0glZB1yoilfWCL/LYCL6rtTte0IjQn9py63gg PmgCuoLcVDzkSGKmog78cJIBRJSDUApL8dzOxJWTiX3WC3GLmXHJ7/vFg7AJBE97 0LIkZDESDfJ4uDA5SGYJPGnhup8IjNZuO3HyD/FQV19IqtcrJLUBEWjtaPTF6lLO dQtMQT5fd58jGyI7vt9Ehi7znYfEE0NEIFQCcXMg6+PRANqnLBxvMhtQESZNhXHG XxPkRv7BP0wUzvZqJbl2Ap66vq0NrasdsEiUzPBhKri+NNFSAI3ZWrRL4e6rJ1yg HRm8KqU1XUVRKZXl64MS9xGQpsWsWSwOWr5oBiG1m0gj25R0fmj52pZwxdI4DYF6 f8g0cxQmDOENeXO1daLx23Y8A9H9Zvdbb63uzxZcvCsJIutcygTGqic96aJGejXF cG9icp1gK2nPXIDIz9m9BteNNuBIDLczknLjBwB3Ert9hG49ww2bTZ8mzDPwLDPZ RWYwNQyYwsJvBBgBCgAmAhsCFiEEjxd3cRijPdqbpI5iqssyQ2MAUtkFAmQmqz0F CRDkOCcBKQkQqssyQ2MAUtnAXSAEGQEKAAYFAlUjppYACgkQciO1ZnjgJSimlAf/ eQTOhj7aSKAgiMYK3JaMXd5Xa8YHPi1OJKzeYLWEJDQ4/2LW6Ke2r0a36bTkuVJM W4q73VDRjcCsIMA0BtqAOZLtFVKFsg2E9lfsjcIX87z1NvcMoNMJ1oOG546uZOkp fonb1zajlMNtUiMZyNUyy9BDIz+jr/LsNc/wF2TIj97B2wS3y6T1PhWllMFXWKDj 33zRjNFrKSlgWg6fQv4xdx//yiQCmO0x6FEjM1F6NhPfTfDVHxd9AgoNZFEYPBBc 715VJqLD5nYRsTihE6RjkiZqUP6wrgViezGcrjDc7vh8aVgJ3dDZwCNJJ/JUSpyP K5SiDpaByZZ4ozEJMtmk74O7DqCf//4bVzrgfhCMmTXoYi9SlNhZI1WuEKYjXW9A fvjs3d0KFxKTA+synu9PUfVt6Y7LJkNjd58omZoxYBBsRWKKWYb+mnA7Cm6EEaor qWQWIQ2iwgVyvHF6kh7EG7jbDruTr/oQp25ZhezByoVlIh6Ytr5qR8/5/zFBoNZ5 PfLycaQUwsqpWeQ244CVd0qfnfrSXgW/nkFctNIKMFsm6qjft5Av26ZJRWRLnnFE NcBhzBXXdEnq5G2rbwAT83oLx3p63I/fp0TTcbKtlFhE16Ppm2nRWdHKbb9ZD7pk a9p8ubGUYai9OmZv3RaA10eOialWRY1tkBbWA6N955N3JJoSAv7vaBe7pP3qPHOZ giSYQWWfneST4b1HpZeHi+2NWBu9BmbbBQG4Itg9VtImlAGHJHVhC4h2XKzWWIIA nmoeL1yWl+CxcTCmipWRPk1lFOUS8QW5VBIO+u9BGh5FPvVVNYSa0tK9GF5DrNtF kL/NhzQXA6Uf+2aej/QKp0d4HkG17SXvfykEqLTobHA3vxWpMFnZZhI4/SVfhM3+ KafactySTrJWt3ZuOnfxv3Gfek7QEimOCV7+ylIu/xLZlbBlRw6UHurMKZXpRoYO dl0qOUig0EfCwm8EGAEKACYCGwIWIQSPF3dxGKM92pukjmKqyzJDYwBS2QUCYzeD FwUJDw0IAQEpCRCqyzJDYwBS2cBdIAQZAQoABgUCVSOmlgAKCRByI7VmeOAlKKaU B/95BM6GPtpIoCCIxgrcloxd3ldrxgc+LU4krN5gtYQkNDj/Ytbop7avRrfptOS5 UkxbirvdUNGNwKwgwDQG2oA5ku0VUoWyDYT2V+yNwhfzvPU29wyg0wnWg4bnjq5k 6Sl+idvXNqOUw21SIxnI1TLL0EMjP6Ov8uw1z/AXZMiP3sHbBLfLpPU+FaWUwVdY oOPffNGM0WspKWBaDp9C/jF3H//KJAKY7THoUSMzUXo2E99N8NUfF30CCg1kURg8 EFzvXlUmosPmdhGxOKETpGOSJmpQ/rCuBWJ7MZyuMNzu+HxpWAnd0NnAI0kn8lRK nI8rlKIOloHJlnijMQky2aTvoqkOniNhCrQ9vugGyHKRWrTK8NWAIrEGQeN22IQN EVBlmfEV/O6miL1byeTEH701sbHEYhCjDvda18V8wQlG8+RBnx+BZpqjGyOWbC/8 1JJoYVB4+w0wCzp+6Vg3f6Hl7xN/6k/ebo49F/CxFeGLeyGPYieeytibWtO0jj9U jZXqHliaXQsW++tQfnHfqFcm0qCBaviZKWg+rdIDs6tOdy9OV/r0PQCnp6T2K/ni dNoANbvQ6u7hsH5A9bwSPVm8bPrJZ5827wb3jeJb2LxR6AjjyiGfuAcnvPX2a88c CCMdjalWJ4fIzPblULpNRVzGt2SlAZRfCs/PKxDbGTkbe4aWbxtukrbFu4fJCbyZ 6FDqRQ8qnlsc/n4t6bP4jRixBZl8IWXwl/mDwBVzZvr4S0/IbMc2HXwuuwqXk8dS F72cejETD0q1rf+odld69MSGrZ2UvkjgSiSlJOC8yJU+blECdcuyqSDgTPn6bVU8 OAbFDaGFWZ2cLT3pBiCOpiKd1sgJclZO6sknUmejVnOH8YPqwQMS2BcfxR6jkjGn V8dq0VYwFFq4K13x4Yy3rEmmy5gj8RWWm8UO+jrQCJ0VUSLeBUk9iyLlxL7qTA/X pfwgXPuyEIl6PMLCbwQYAQoAJgIbAhYhBI8Xd3EYoz3am6SOYqrLMkNjAFLZBQJi RkCmBQkOG8WQASkJEKrLMkNjAFLZwF0gBBkBCgAGBQJVI6aWAAoJEHIjtWZ44CUo ppQH/3kEzoY+2kigIIjGCtyWjF3eV2vGBz4tTiSs3mC1hCQ0OP9i1uintq9Gt+m0 5LlSTFuKu91Q0Y3ArCDANAbagDmS7RVShbINhPZX7I3CF/O89Tb3DKDTCdaDhueO rmTpKX6J29c2o5TDbVIjGcjVMsvQQyM/o6/y7DXP8BdkyI/ewdsEt8uk9T4VpZTB V1ig49980YzRaykpYFoOn0L+MXcf/8okApjtMehRIzNRejYT303w1R8XfQIKDWRR GDwQXO9eVSaiw+Z2EbE4oROkY5ImalD+sK4FYnsxnK4w3O74fGlYCd3Q2cAjSSfy VEqcjyuUog6WgcmWeKMxCTLZpO9qHw6dES2t2FRWeltMIX5M8iynSZwSlwm4Q0yq z3wW/xmTeFxSsUEzjc5BQ1HKCfCOR7ERI30FejEU0kPnaXoA4mqykuzqYS+CFTm3 EOmDkRay8aAJgoMg4bSWskceH+PtTluIXz4/ynSMQW/zDPdURLJdVafYj0fgTVKZ dbbRL5Mk+xBQhaMs/LinJSLhIlIgSBEOcKRyee9cAlyfe1hl4kOTrcrM+JqumM24 Z9KtNFy7c900i7hHOiCiBnZElquoBakIOgujACmwLvGtETbX8EYyUQUZ994MWuMU 6RH2dHlRMrK7XuaFajJMXRq7sx9qOZ63ioc8tVWvCJi9p9G4mIiKY2KWjNXsQlR0 C1n1d/eRMJ3wRZiyBNIovo54lxIXHLUwrxbUsDqKRsoX4ivUBi5bCW4ZEy71tp9b PTWj7L42xRNlgAp/wQo/0Cqt2lOj/TKFQ0xTd8BsmRXL3N0LTEntlivpMhdzc1zb Nf/QnAz3+NOUn2ab/Q2IJ9aqzGPdmEaEyQ0QP0/AMeruJp4Oindd6e+qClJFzw7T mADrMXs4imU+zuoszYnxFchbW9sG0sOUZpNyZ9AKVtON/LkS/ATIHXn3kinqPXcl JGOHrNuELTYqkOMbwsJvBBgBCgAmAhsCFiEEjxd3cRijPdqbpI5iqssyQ2MAUtkF AmFWT50FCQ0r1IcBKQkQqssyQ2MAUtnAXSAEGQEKAAYFAlUjppYACgkQciO1Znjg JSimlAf/eQTOhj7aSKAgiMYK3JaMXd5Xa8YHPi1OJKzeYLWEJDQ4/2LW6Ke2r0a3 6bTkuVJMW4q73VDRjcCsIMA0BtqAOZLtFVKFsg2E9lfsjcIX87z1NvcMoNMJ1oOG 546uZOkpfonb1zajlMNtUiMZyNUyy9BDIz+jr/LsNc/wF2TIj97B2wS3y6T1PhWl lMFXWKDj33zRjNFrKSlgWg6fQv4xdx//yiQCmO0x6FEjM1F6NhPfTfDVHxd9AgoN ZFEYPBBc715VJqLD5nYRsTihE6RjkiZqUP6wrgViezGcrjDc7vh8aVgJ3dDZwCNJ J/JUSpyPK5SiDpaByZZ4ozEJMtmk76sZDp9LZ+GWUteIVNsX401MyWU6WI5CDZ+s 3m7xkDqgLH8LiBoyVrbPDJGltQPvlvu/ML+KPQbQ+VUQIY/eczhSJU0gsHPX16A/ lYaAgqvs3Mm30V03Jax/ZApffrACiz7zTXEkhj9ckSAjJ71EYbAr1SDhktmobZnt 6mYMFHR6VuiUSL5Qo6HdxJi3b6tAzzsAJY5HtX9ZjuUuym5LVwIaMDBhIOmwXbfC iviAL/MY2R0BfSZHNA/vfFqs2XyIzl2RpCLLTHtHT/gxcnQ04e5kcXHLNGgblcaD oUyfkuUsGKN+ZasVBG2vu3UEGeN9mJ+WWlyNE9W7vvBlgUS7ufA6feT4CGXC4UL4 OIVb8po73oMq99oaiSuNoTuUWux6qtnv0oah7ga+zqlm7+msIkGTQoea94EJ9CMu 3KMJbLlPJgd8ObRdmc0eZw+GnJU6qNvtP8zg53W5s76bMJNL3u2AsGx114pkTjkh YRGIRJngT0rWzbE4fa3bh3QDFA9b4ddocqNNEwE0+k/5GwgPDqH5lX6X/7wDEVNo haQrlWjB/VqYxqToIHJqvoxDVZcDRpBN9D4Hw/06qmVX/VkVFKmcx4dXMuy5ADye opA0A8JYxy6Vmae0IKvCwm8EGAEKACYCGwIWIQSPF3dxGKM92pukjmKqyzJDYwBS 2QUCYGUNFAUJDDqR/gEpCRCqyzJDYwBS2cBdIAQZAQoABgUCVSOmlgAKCRByI7Vm eOAlKKaUB/95BM6GPtpIoCCIxgrcloxd3ldrxgc+LU4krN5gtYQkNDj/Ytbop7av RrfptOS5UkxbirvdUNGNwKwgwDQG2oA5ku0VUoWyDYT2V+yNwhfzvPU29wyg0wnW g4bnjq5k6Sl+idvXNqOUw21SIxnI1TLL0EMjP6Ov8uw1z/AXZMiP3sHbBLfLpPU+ FaWUwVdYoOPffNGM0WspKWBaDp9C/jF3H//KJAKY7THoUSMzUXo2E99N8NUfF30C Cg1kURg8EFzvXlUmosPmdhGxOKETpGOSJmpQ/rCuBWJ7MZyuMNzu+HxpWAnd0NnA I0kn8lRKnI8rlKIOloHJlnijMQky2aTvBD4On1znhsMFzW3PpQ7Nq2JcjXC4waki +gj60K3EnwTCvJeJ2u4RTea3wm0lHUT9m0r33S3tDlRZmRrwJARL4LyujR5ZkKEu 4FrLfTMleqP79TazSB6ajrG4lmTPP6/J9K3gZCqG31DT7kelDONe0nZPhmKdU1NO +G0wlwx3CufXfmhK8XJsC+b3gGtGTkI4nQ6HtU6hTmFvKuTpsFw8I6CPBBHvUq29 0tjfp2WHD1nSMUmyxe519rViPuvWrtsludZ0TyjLiQ6b5j/zapiaen+IOrnsdJBJ QPKF8755OneenmCIRYYLrW4HS7YYAr9TeN243X4UbzMa9NuH5v/EX8QBAky2/0ng EgHSX/NkHu0q9kQn5YsL/qQKuLE79lMsThXCq5eo50r+j0uoguXkGSRPaFcSFImM XHTBnMK1iWJHPsY8/K5sidRRa1vE+qYqIrtf/cTpMCEcQkDlU/8xdWknTwYDgxIE NAzrVqTcze0coBgdhEC6vnAPM7eylDYBg18Xrj/Mfd2xwjDC5RiH8PjFdLx6Zqui qsa7K8SFqdJj7TXmXyqNLKD40igup9WdG0aTpXc9qrUJ14Nx6DaOpxG75E1loY/l m8WsXD5CuNIqjZaH5aG4AMLCbwQYAQoAJgIbAhYhBI8Xd3EYoz3am6SOYqrLMkNj AFLZBQJcsIjcBQkLT0lGASkJEKrLMkNjAFLZwF0gBBkBCgAGBQJVI6aWAAoJEHIj tWZ44CUoppQH/3kEzoY+2kigIIjGCtyWjF3eV2vGBz4tTiSs3mC1hCQ0OP9i1uin tq9Gt+m05LlSTFuKu91Q0Y3ArCDANAbagDmS7RVShbINhPZX7I3CF/O89Tb3DKDT CdaDhueOrmTpKX6J29c2o5TDbVIjGcjVMsvQQyM/o6/y7DXP8BdkyI/ewdsEt8uk 9T4VpZTBV1ig49980YzRaykpYFoOn0L+MXcf/8okApjtMehRIzNRejYT303w1R8X fQIKDWRRGDwQXO9eVSaiw+Z2EbE4oROkY5ImalD+sK4FYnsxnK4w3O74fGlYCd3Q 2cAjSSfyVEqcjyuUog6WgcmWeKMxCTLZpO9lmg6eKqb7zs4QukYeDf8OBpeJtn3U 4D8WtbgmeI55rHZ5nCgWJoxb3x6fW7uEDq4Uo4oEaIEg5k7ERIU5NI1/cBAZrBQl EiZns7j6nSybuDfunm9VUtulyNk1n7cw+1SRf+y5kiqIwnSSGajrHkJ7RDCN6Apd EO7XFdPLCdWXNvvs4ZyI/rcnSOqlTJ/WFoRvSubj44QPIHXw+Mb/xFKpRccqyDbo eU+a9h57JGFwUAp/XKr07NrDQUZn5QhRvrfQ8D0O17BSyqcpxGjZOliXruzFz7t5 G+CyUrvpm++KHmVrtJAGBItUHJGFwghXFWe8NfRy+g7Xod/EsJmVOXNOLSenjjK+ qVH5KbMGry+Aleu8CRWkeUZyR26kUMqsD0DDL1pZg7hZKlfyct0CR/xW8d576vFh L5EE6zqPggbg+IfJ29NZqw1RyelXGxDT8YpHM2Q+DZV1bYitTqEWCe3RTl2Kr7cq oVXNMeCdg6d7XYUNNqxiTUDBp1VkmSSBcE/LB7scxjwjcc4i5ePxSTj23wbrvH/w wEbHRpsfFn742cwDMrCUwguQSu/7bzSTkU+/5WvyqTou5m1i1323BfbWPaGbuaT7 eUoIAFRjuBrovU8kY/SJ02MywsJvBBgBCgAPAhsCBQJY5iO2BQkHhOQgAUAJEKrL MkNjAFLZwF0gBBkBCgAGBQJVI6aWAAoJEHIjtWZ44CUoppQH/3kEzoY+2kigIIjG CtyWjF3eV2vGBz4tTiSs3mC1hCQ0OP9i1uintq9Gt+m05LlSTFuKu91Q0Y3ArCDA NAbagDmS7RVShbINhPZX7I3CF/O89Tb3DKDTCdaDhueOrmTpKX6J29c2o5TDbVIj GcjVMsvQQyM/o6/y7DXP8BdkyI/ewdsEt8uk9T4VpZTBV1ig49980YzRaykpYFoO n0L+MXcf/8okApjtMehRIzNRejYT303w1R8XfQIKDWRRGDwQXO9eVSaiw+Z2EbE4 oROkY5ImalD+sK4FYnsxnK4w3O74fGlYCd3Q2cAjSSfyVEqcjyuUog6WgcmWeKMx CTLZpO8WIQSPF3dxGKM92pukjmKqyzJDYwBS2R+5Dp4woT/lj4uJl4664XLdybPt fsMFUOtGYhBK66fyPXbPai+XbvXZSSAOe74QPO1coAQrfTnZmhWk2y8aSLYwwHHa StSa0XCgCGTowSCmqUZFx+GzkGvl948cL+fK0KalU80axs7MMTBblxVbjh2D3L7Y ib65wLTyei/HgMT3inbUp/2XgsrO1AUfd5JLKM0oVhnyLmx3BB7IZBEDytQEBXSH g7+xhru3wBpOyXLb+CXNb0PeL+rubdh2YH4zkq4KqkhWEitWetDBMaf9bcSA0h/D Y+AC+cBXCQ0QhWlQz5Xkyj2yHkX25jgRDlry3nrTTmTtbPxraDyLTyRBt0UhtyYi Ey7XdIP3yLvssieUKnwfP+32iumVYbiVGDUvmzYeEM5A3892WIR6cKBQCs6H2LD6 wyiTl1Z41u5AhfaAAhPd7mzb4GM5U3dwx+7iCCWyp07l2DcOjJ/p6Lx6iYAzAERV wzM/4e9jcsQ1tZXUHgti/pnpdFm7l39xkN4VrQB/RNGBqKSYf3/iU3EveDe7vLCQ tBUp0n6Ch/++Vv1oeGlwC8IaRIXpHAqelPEo99RqPFaI5C+0VBqFarGr6zk4o07u j2UBP1AeJOarz9WV1/8nlucFjLTCwm8EGAEKAA8FAlUjppYCGwIFCQPCZwABQAkQ qssyQ2MAUtnAXSAEGQEKAAYFAlUjppYACgkQciO1ZnjgJSimlAf/eQTOhj7aSKAg iMYK3JaMXd5Xa8YHPi1OJKzeYLWEJDQ4/2LW6Ke2r0a36bTkuVJMW4q73VDRjcCs IMA0BtqAOZLtFVKFsg2E9lfsjcIX87z1NvcMoNMJ1oOG546uZOkpfonb1zajlMNt UiMZyNUyy9BDIz+jr/LsNc/wF2TIj97B2wS3y6T1PhWllMFXWKDj33zRjNFrKSlg Wg6fQv4xdx//yiQCmO0x6FEjM1F6NhPfTfDVHxd9AgoNZFEYPBBc715VJqLD5nYR sTihE6RjkiZqUP6wrgViezGcrjDc7vh8aVgJ3dDZwCNJJ/JUSpyPK5SiDpaByZZ4 ozEJMtmk7xYhBI8Xd3EYoz3am6SOYqrLMkNjAFLZJ0UOoJCGTE+1TxtIIPTfhdM1 +2NGwoBk1OVR/3xHfy81E0UC9KjM/jC8fzF1t4nGkBftbD3jIkigwFNcbrmLfJ96 VeYKZtcw9PrggRDM6sMMURg6Iw2kbNtQiIEburwkUNzAxaYiSo2KmuhF7ZIm3k1i OsqPcPXVB/josNLJDSxfzCcRsLxO+CdQa9qbwbEzrjwi9DXZNN9tO7hYUY1suKJ6 mxCeMwPi+u/2Ecwgm7Vm7qDrrJ/a6kMIq9FR2wXJuxSL8FvFN/WpcViSJc6d/+bE Rti6uMZmCMtKMR0DWh658NSjo+e3fBRFnOK2HmY0zmJJMjnnnc/WoLpes+spGtTj y9O0Cm/pWeSJXNMvmDkrTRunyH4l6LSQXCUAXcjsXcYKv2g1TqS/RAac2OKtdaG/ PXPHqrKJ5s/EIKOz4oWkzMl6WRP36TwdoRxBvyMRrF9znOX/fXHCR/yuf4Eilp8J 6P4B8UMmy6WlPsva4QWFhmc+5c8n2SBdUCAV2mkgwqCpDTR5Cs9jYal1UcxmFZBV OIXakAhe6468IOtyFEyNVYF9taIMDL9YFukl6ZoX7UgdwfnKbQQWBRjjFpcigaNF XuwkEDh4MBNicSOdhoItEg89lEbzxQ== =VIUO -----END PGP PUBLIC KEY BLOCK----- """ rpm-sequoia-1.10.2/rpm-sequoia.pc.in000064400000000000000000000002611046102023000153410ustar 00000000000000prefix=@PREFIX@ libdir=@LIBDIR@ Name: @NAME@ Description: @DESCRIPTION@ URL: @HOMEPAGE@ Version: @VERSION@ Requires.private: @REQUIRES@ Cflags: Libs: -L${libdir} -lrpm_sequoia rpm-sequoia-1.10.2/src/digest.rs000064400000000000000000000065721046102023000145750ustar 00000000000000// This is a reimplementation of rpm/rpmio/digest_openssl.c / // rpm/rpmio/digest_libgcrypt.c using Sequoia. use libc::{ c_int, size_t, }; use sequoia_openpgp as openpgp; use openpgp::crypto::{HashAlgorithm, hash}; use crate::Error; use crate::Result; #[derive(Clone)] pub struct DigestContext { pub(crate) ctx: hash::Context, } impl DigestContext { pub(crate) fn digest_size(&self) -> usize { self.ctx.digest_size() } pub(crate) fn update>(&mut self, data: T) { self.ctx.update(data.as_ref()); } pub(crate) fn digest(&mut self, digest: &mut [u8]) -> Result<()> { Ok(self.ctx.digest(digest)?) } pub(crate) fn into_digest(self) -> Result> { Ok(self.ctx.into_digest()?) } } ffi!( /// DIGEST_CTX rpmDigestInit(int hashalgo, rpmDigestFlags flags) /// /// rpmDigestFlags currently does not define any flags. fn _rpmDigestInit(hashalgo: c_int, flags: c_int) -> *mut DigestContext { if hashalgo < 0 || hashalgo > u8::MAX as c_int { return Err(Error::Fail("Out of range".into())); } let hashalgo = HashAlgorithm::from(hashalgo as u8); if flags != 0 { return Err(Error::Fail(format!("Unsupported flags: {}", flags))); } let ctx = DigestContext { ctx: hashalgo.context()?.for_digest(), }; Ok(move_to_c!(ctx)) }); ffi!( /// DIGEST_CTX rpmDigestDup(DIGEST_CTX octx) fn _rpmDigestDup(ctx: *const DigestContext) -> *mut DigestContext { let ctx = check_ptr!(ctx); Ok(Box::into_raw(Box::new(ctx.clone()))) }); ffi!( /// size_t rpmDigestLength(int hashalgo) fn _rpmDigestLength(hashalgo: c_int) -> size_t[0] { if hashalgo < 0 || hashalgo > u8::MAX as c_int { return Ok(0); } let hashalgo = HashAlgorithm::from(hashalgo as u8); use HashAlgorithm::*; let len = match hashalgo { MD5 => 16, SHA1 => 20, RipeMD => 20, SHA256 => 32, SHA384 => 48, SHA512 => 64, SHA224 => 28, SHA3_256 => 32, SHA3_512 => 64, _ => 0, }; Ok(len) }); ffi!( /// int rpmDigestUpdate(DIGEST_CTX ctx, const void * data, size_t len) fn _rpmDigestUpdate(ctx: *mut DigestContext, data: *const u8, len: size_t) -> ErrorCode { let ctx = check_mut!(ctx); let data = check_slice!(data, len); ctx.update(data); Ok(()) }); ffi!( /// int rpmDigestFinal(DIGEST_CTX ctx, void ** datap, size_t *lenp, int asAscii) fn _rpmDigestFinal(ctx: *mut DigestContext, datap: *mut *mut u8, lenp: *mut size_t, as_ascii: c_int) -> Binary { let ctx = claim_from_c!(ctx); let datap = check_optional_mut!(datap); let lenp = check_optional_mut!(lenp); let mut digest = ctx.into_digest()?; if as_ascii != 0 { digest = digest .iter() .map(|x| { let x = format!("{:02x}", x); let x = x.as_bytes(); std::iter::once(x[0]).chain(std::iter::once(x[1])) }) .flatten() // Add a NUL. .chain(std::iter::once(0)) .collect(); } digest.shrink_to_fit(); if let Some(lenp) = lenp { *lenp = digest.len() as size_t; } if let Some(datap) = datap { *datap = digest.as_mut_ptr(); // Pass ownership to the caller. std::mem::forget(digest); } Ok(()) }); rpm-sequoia-1.10.2/src/ffi.rs000064400000000000000000000254171046102023000140610ustar 00000000000000pub(crate) fn idempotent(x: T) -> T { x } pub(crate) fn zero(_: T) -> libc::c_int { 0 } pub(crate) fn minus_one(_: T) -> libc::c_int { -1 } pub(crate) fn unit(_: T) -> () { () } // Wraps an ffi function, which returns an arbitrary type. // // The inner function returns `Result<$rt>`. This wrapper maps // `Ok($rt)` to `$Crt` using `$rt_to_crt` and `Err(err)` to // `$err_to_crt`. macro_rules! ffi { // Wraps an ffi function, which returns 0 on success and -1 on error. // // fn func(...) -> Binary ($(#[$outer:meta])* fn $f:ident($($v:ident: $t:ty),*) -> Binary $body:block) => { ffi!($(#[$outer])* fn $f($($v: $t),*) -> Result<(), crate::Error> -> (crate::ErrorCode; $crate::ffi::zero; $crate::ffi::minus_one) { $body }); }; // Wraps an ffi function, which returns an RC. // // fn func(...) -> ErrorCode ($(#[$outer:meta])* fn $f:ident($($v:ident: $t:ty),*) -> ErrorCode $body:block) => { ffi!($(#[$outer])* fn $f($($v: $t),*) -> Result<(), crate::Error> -> (crate::ErrorCode; $crate::ffi::zero; |err| crate::ErrorCode::from(err)) { $body }); }; // Wraps an ffi function, which returns a PgpArmorError. // // fn func(...) -> PgpArmor ($(#[$outer:meta])* fn $f:ident($($v:ident: $t:ty),*) -> PgpArmor $body:block) => { ffi!($(#[$outer])* fn $f($($v: $t),*) -> Result -> (crate::ErrorCode; c_int::from; c_int::from) { $body }); }; // Wraps an ffi function, which returns an object whose type is // *const T. Returns NULL on error. // // fn func(...) -> *const u8 ($(#[$outer:meta])* fn $f:ident($($v:ident: $t:ty),*) -> *const $value:ty $body:block) => { ffi!($(#[$outer])* fn $f($($v: $t),*) -> Result<*const $value, crate::Error> -> (*const $value; $crate::ffi::idempotent; |_| std::ptr::null()) { $body }); }; // Wraps an ffi function, which returns an object whose type is // *mut T. Returns NULL on error. // // fn func(...) -> *mut u8 ($(#[$outer:meta])* fn $f:ident($($v:ident: $t:ty),*) -> *mut $value:ty $body:block) => { ffi!($(#[$outer])* fn $f($($v: $t),*) -> Result<*mut $value, crate::Error> -> (*mut $value; $crate::ffi::idempotent; |_| std::ptr::null_mut()) { $body }); }; // Wraps an ffi function, which returns a value. The value is passed // through as is and errors are mapped to `$err`. // // Example: A function that returns an int. If the function // returns Err, that is mapped to 1: // // fn func(...) -> c_int[1] ($(#[$outer:meta])* fn $f:ident($($v:ident: $t:ty),*) -> $value:ty[$err:expr] $body:block) => { ffi!($(#[$outer])* fn $f($($v: $t),*) -> Result<$value, crate::Error> -> ($value; $crate::ffi::idempotent; |_| $err) { $body }); }; // Wraps an ffi function, which returns void. // // The inner function returns `Result<()>` and this is mapped to `()`. // // Note: inner body returns Ok(()) by default. // // Example: // // fn func(...) // // Note: there is no default type in the declaration. ($(#[$outer:meta])* fn $f:ident($($v:ident: $t:ty),*) $body:block) => { ffi!( $(#[$outer])* fn $f($($v: $t),*) -> Result<(), crate::Error> -> ((); $crate::ffi::unit; $crate::ffi::unit) { let () = $body; Ok(()) }); }; // $Crt is the C function's return type. It must be possible to // convert an Error value v of type $rt to a value of type $Crt by doing: // $Crt::from($rt). // // $ok is the value (of type $rt) to map Ok to. ($(#[$outer:meta])* fn $f:ident($($v:ident: $t:ty),*) -> Result<$rt:ty, $et:ty> -> ($Crt:ty; $rt_to_crt:expr; $err_to_crt: expr) $body:block ) => { // The wrapper. It calls $f and turns the result into an // error code. $(#[$outer])* #[allow(unused)] #[no_mangle] pub extern "C" fn $f($($v: $t),*) -> $Crt { tracer!(*crate::TRACE, stringify!($f)); // The actual function. fn inner($($v: $t),*) -> std::result::Result<$rt, $et> { $body } t!("entered"); // We use AssertUnwindSafe. This is safe, because if we // catch a panic, we abort. If we turn the panic into an // error, then we need to reexamine this assumption. let r = std::panic::catch_unwind(::std::panic::AssertUnwindSafe(|| { match inner($($v,)*) { Ok(v) => { t!("-> success"); let rt: $Crt = $rt_to_crt(v); rt } Err(err) => { t!("-> error: {}{}", err, { use std::error::Error; let mut causes = String::new(); let mut cause = err.source(); while let Some(e) = cause { causes.push_str("\n because: "); causes.push_str(&e.to_string()); cause = e.source(); } causes }); let rt: $Crt = $err_to_crt(err); rt } } })); match r { Ok(code) => code, Err(_) => { t!("-> panic!"); unsafe { ::libc::abort() }; } } } } } // Creates a stub for a ffi, which returns an error. #[allow(unused_macros)] macro_rules! stub { ($f:ident) => { #[no_mangle] pub extern "C" fn $f() -> crate::ErrorCode { tracer!(*crate::TRACE, stringify!($f)); t!("{} is a stub", stringify!($f)); crate::Error::Fail( format!("Unimplemented: {}", stringify!($f))).into() } }; } // Checks if a `*const T` pointer is NULL if so, returns an error. // Otherwise, returns `&T`. macro_rules! check_ptr { ($p:ident) => {{ let p: *const _ = $p; if p.is_null() { return Err(Error::Fail( format!("{} must not be NULL", stringify!($p))).into()); } else { t!("{}: & <- {:?}", stringify!($p), $p); unsafe { &*p } } }} } // Returns an Option<&T> from a *const T. macro_rules! check_optional_ptr { ($p:ident) => {{ let p: *const _ = $p; if p.is_null() { None } else { t!("{}: Option<&> <- {:?}", stringify!($p), $p); Some(unsafe { &*p }) } }} } // Checks if a `*mut T` pointer is NULL if so, returns an error. // Otherwise, returns `&mut T`. macro_rules! check_mut { ($p:ident) => {{ let p: *mut _ = $p; if p.is_null() { return Err(Error::Fail( format!("{} must not be NULL", stringify!($p))).into()); } else { t!("{}: &mut <- {:?}", stringify!($p), $p); unsafe { &mut *p } } }} } // Returns an Option<&mut T> from a *mut T. macro_rules! check_optional_mut { ($p:ident) => {{ let p: *mut _ = $p; if p.is_null() { None } else { t!("{}: Option<&mut> <- {:?}", stringify!($p), $p); Some(unsafe { &mut *p }) } }} } // Checks if a `*const T` pointer is NULL if so, returns an error. // Otherwise, returns a slice `&[T]` with `l` elements. macro_rules! check_slice { ($p:ident, $l:expr) => { if $p.is_null() { return Err(Error::Fail( format!("{} must not be NULL", stringify!($p)))); } else { t!("{}: &[] <- {:?}", stringify!($p), $p); unsafe { std::slice::from_raw_parts($p as *const u8, $l) } } } } // Checks if a `*mut T` pointer is NULL if so, returns an error. // Otherwise, returns a slice `&mut [T]` with `l` elements. macro_rules! check_mut_slice { ($p:ident, $l:expr) => {{ let p: *mut _ = $p; if p.is_null() { return Err(Error::Fail( format!("{} must not be NULL", stringify!($p)))); } else { t!("{}: &[] <- {:?}", stringify!($p), p); unsafe { std::slice::from_raw_parts_mut($p as *mut u8, $l) } } }} } // Checks if a `*const c_char` pointer is NULL if so, returns an // error. Otherwise, returns a CStr. macro_rules! check_cstr { ($s:ident) => {{ let _: *const libc::c_char = $s; let s = check_ptr!($s); unsafe { std::ffi::CStr::from_ptr(s) } }} } // Moves ownership of a parameter of type T to C. // // Given a T, returns a *mut T. macro_rules! move_to_c { ($expr:expr) => {{ let p = Box::into_raw(Box::new($expr)); t!("{}: returning {:?}", stringify!($expr), p); p }} } // Moves ownership of an object owned by C. // // This is the opposite of move_to_c. macro_rules! claim_from_c { ($p:ident) => {{ if $p.is_null() { return Err(Error::Fail( format!("{} must not be NULL", stringify!($p)))); } unsafe { t!("{}: owned <- {:?}", stringify!($p), $p); Box::from_raw($p) } }}; } // Moves ownership of a parameter of type Option to C. macro_rules! move_option_to_c { ($expr:expr) => { $expr.map(|x| box_raw!(x)).unwrap_or(::std::ptr::null_mut()) } } /// Transfers ownership from C to Rust, then frees the object. /// /// NOP if called with NULL. macro_rules! free { ($p:ident) => {{ if let Some(ptr) = $p { let ptr = unsafe { Box::from_raw(ptr) }; drop(ptr); } }}; } rpm-sequoia-1.10.2/src/lib.rs000064400000000000000000002435411046102023000140630ustar 00000000000000//! An implementation of RPM's OpenPGP interface. //! //! This library provides an implementation of [RPM's OpenPGP //! interface](https://github.com/rpm-software-management/rpm/blob/master/include/rpm/rpmpgp.h). //! //! **You should not link to this library directly**. //! //! If you are looking for an OpenPGP interface, consider using //! [Sequoia], which this library is based on. If you want to use //! RPM's OpenPGP interface, which you should only do if you are //! interacting with RPM, then you should link against [RPM], which //! reexports this interface. //! //! [Sequoia]: https://gitlab.com/sequoia-pgp/sequoia //! [RPM]: http://rpm.org //! //! If you are investigating a bug in this library, set the //! `RPM_TRACE` environment variable to 1 to get a verbose trace of //! the library's execution: //! //! ```sh //! $ LD_LIBRARY_PATH=/tmp/rpm-sequoia/release RPM_TRACE=1 ./rpmkeys \ //! --import ../tests/data/keys/CVE-2021-3521-badbind.asc //! _rpmInitCrypto: entered //! _rpmInitCrypto: -> success //! _pgpParsePkts: entered //! ... //! ``` //! //! # Policy //! //! When Sequoia evaluates the validity of an object (e.g., a //! cryptographic signature) it consults a policy. The policy is user //! defined. This library uses [Sequoia's standard policy]. //! //! [Sequoia's standard policy]: https://docs.sequoia-pgp.org/sequoia_openpgp/policy/struct.StandardPolicy.html //! //! Sequoia's standard policy allows self-signatures (i.e., the //! signatures that bind a User ID or subkey to a certificate) made //! with SHA-1 until February 2023. It completely disallows data //! signatures made with SHA-1. The reason for this is that SHA-1 //! collision resistance is broken, but its second pre-image //! resistance is still okay. //! //! As an added protection, Sequoia uses [SHA-1 collision detection], //! which is a variant of SHA-1, which mitigates known attacks against //! SHA-1. SHA-1 CD has a very low [false positive rate] (2^-90) so //! it can be treated as a drop-in, fully compatible replacement for //! SHA-1. //! //! [SHA-1 collision detection]: https://github.com/cr-marcstevens/sha1collisiondetection //! [false positive rate]: https://github.com/cr-marcstevens/sha1collisiondetection#about //! //! # Configuration File //! //! This library reads the [crypto policy configuration] in //! `/etc/crypto-policies/back-ends/sequoia.config`. If that file //! doesn't exist, it tries //! `/usr/share/crypto-policies/back-ends/rpm-sequoia.config`. This //! can be overridden using the `SEQUOIA_CRYPTO_POLICY` environment //! variable. If set to the empty string, then no crypto policy will //! be read and instead [Sequoia's default policy] will be used. //! //! Refer to the [Fedora Crypto Policy] project for information about //! the crypto policy. //! //! [crypto policy configuration]: https://docs.rs/sequoia-policy-config/latest/sequoia_policy_config/ //! [Sequoia's default policy]: https://docs.sequoia-pgp.org/sequoia_openpgp/policy/struct.StandardPolicy.html //! [Fedora Crypto Policy]: https://gitlab.com/redhat-crypto/fedora-crypto-policies/ use std::env; use std::ffi::{ CString, }; use std::fmt::Debug; use std::io::Read; use std::io::Write; use std::path::PathBuf; use std::sync::RwLock; use std::time::{ Duration, SystemTime, UNIX_EPOCH, }; #[allow(unused_imports)] use anyhow::Context; use libc::{ c_char, c_int, c_uint, c_void, size_t, }; use chrono::{ DateTime, Utc, }; use sequoia_openpgp as openpgp; use openpgp::armor; use openpgp::Cert; use openpgp::cert::prelude::*; use openpgp::Fingerprint; use openpgp::KeyID; use openpgp::packet::key::{ PublicParts, }; use openpgp::packet::{ Packet, Signature, Tag, }; use openpgp::parse::{ PacketParser, PacketParserResult, PacketParserBuilder, Dearmor, }; use openpgp::parse::Parse; use openpgp::policy::{ NullPolicy, StandardPolicy, Policy, }; use openpgp::serialize::SerializeInto; use openpgp::types::RevocationStatus; use openpgp::parse::buffered_reader; #[macro_use] mod log; #[macro_use] mod ffi; #[macro_use] pub mod rpm; use rpm::{ Error, ErrorCode, PgpArmor, PgpArmorError, Result, }; pub mod digest; lazy_static::lazy_static! { static ref P: RwLock> = RwLock::new(StandardPolicy::new()); } const NP: &NullPolicy = unsafe { &NullPolicy::new() }; // Set according to the RPM_TRACE environment variable (enabled if // non-zero), or if we are built in debug mode. lazy_static::lazy_static! { static ref TRACE: bool = { if let Ok(v) = env::var("RPM_TRACE") { let v: isize = v.parse().unwrap_or(1); v != 0 } else { false } }; } /// Prints the error and causes, if any. pub fn print_error_chain(err: &anyhow::Error) { eprintln!(" {}", err); err.chain().skip(1).for_each(|cause| eprintln!(" because: {}", cause)); } // Sometimes the same error cascades, e.g.: // // ``` // $ sq-wot --time 20230110T0406 --keyring sha1.pgp path B5FA089BA76FE3E17DC11660960E53286738F94C 231BC4AB9D8CAB86D1622CE02C0CE554998EECDB FABA8485B2D4D5BF1582AA963A8115E774FA9852 "" // [ ] FABA8485B2D4D5BF1582AA963A8115E774FA9852 : not authenticated (0%) // ◯ B5FA089BA76FE3E17DC11660960E53286738F94C ("") // │ No adequate certification found. // │ No binding signature at time 2023-01-10T04:06:00Z // │ No binding signature at time 2023-01-10T04:06:00Z // │ No binding signature at time 2023-01-10T04:06:00Z // ... // ``` // // Although technically correct, it's just noise. Compress them. fn error_chain(err: &anyhow::Error) -> Vec { let mut errs = std::iter::once(err.to_string()) .chain(err.chain().map(|source| source.to_string())) .collect::>(); errs.dedup(); errs } fn error_chain_display(err: &anyhow::Error) -> String { error_chain(err).join(", because ") } // Generate macros for working with lints. // // Note: $dollar is a hack, which we use because nested macros with // repetitions don't currently work. See: // https://github.com/rust-lang/rust/pull/95860 macro_rules! linter { ($dollar:tt, $lints:ident) => { // A helper macro to add a lint. // // If `$err` is `None`, `$msg` is turned into an `anyhow::Error` and // appended to `lints`. // // If `$err` is `Some`, `$msg` is added as context to `$err` and is // then appended to `lints`. macro_rules! add_lint { ($err:expr, $msg:expr $dollar(, $args:expr)*) => {{ let err: Option = $err; let msg = format!("{}", format_args!($msg $dollar(, $args)*)); let err = if let Some(err) = err { err.context(msg) } else { anyhow::anyhow!(msg) }; $lints.push(err); }}; } // A helper to return an error. // // This adds a lint using `lint!` and then returns // `Error::Fail($msg)`. macro_rules! return_err { ($err:expr, $msg:expr $dollar(, $args:expr)*) => {{ add_lint!($err, $msg $dollar(, $args)*); return Err(Error::Fail( format!("{}", format_args!($msg $dollar(, $args)*)))); }}; } } } // By default we prefer this environment variable and this file, but // if that is not present, we fallback to the default configuration. const RPM_SEQUOIA_CONFIG_ENV: &'static str = "RPM_SEQUOIA_CRYPTO_POLICY"; const RPM_SEQUOIA_CONFIG: &[&str] = &[ "/etc/crypto-policies/back-ends/rpm-sequoia.config", "/usr/share/crypto-policies/back-ends/rpm-sequoia.config", ]; ffi!( /// int rpmInitCrypto(void) fn _rpmInitCrypto() -> Binary { // XXX: Remove this once v4 signatures are ubiquitous. // // Unfortunately, much of the rpm ecosystem is still (2022) // generating v3 signatures. As they aren't completely broken, // accept them by default, but still let them be overridden by the // system policy. // // See https://bugzilla.redhat.com/show_bug.cgi?id=2141686 let mut p = openpgp::policy::StandardPolicy::new(); p.accept_packet_tag_version(openpgp::packet::Tag::Signature, 3); let mut p = sequoia_policy_config::ConfiguredStandardPolicy ::from_policy(p); // We can only specify a single file to // `ConfiguredStandardPolicy::parse_config_file`. We work around // it (for now) by taking the first file that exists. let rpm_sequoia_config = RPM_SEQUOIA_CONFIG .iter() .find(|path| { PathBuf::from(path).exists() }) .unwrap_or(&RPM_SEQUOIA_CONFIG[0]); match p.parse_config(RPM_SEQUOIA_CONFIG_ENV, rpm_sequoia_config) { Ok(false) => { // Fallback to the default configuration. if let Err(err) = p.parse_default_config() { print_error_chain(&err); return Err(err.into()); } } Ok(true) => (), Err(err) => { print_error_chain(&err); return Err(err.into()); } } *crate::P.write().unwrap() = p.build(); Ok(()) }); ffi!( /// int rpmFreeCrypto(void) fn _rpmFreeCrypto() -> Binary { Ok(()) }); // These are still implemented in C due to internationalization, and // to avoid translating the string tables, which is a fair amount of // error prone work, and doesn't improve safety. // // stub!(pgpValString); // stub!(pgpIdentItem); // This is implemented in C: it is just a wrapper around pgpParsePkts, // which uses some internal rpm functions. // // stub!(pgpReadPkts); /// An OpenPGP object. /// /// This data structure can hold either a signature, a certificate, or /// a subkey. enum PgpDigParamsObj { Cert(Cert), Subkey(Cert, Fingerprint), Signature(Signature), } pub struct PgpDigParams { obj: PgpDigParamsObj, signid: [u8; 8], userid: Option, } impl PgpDigParams { fn cert(&self) -> Option<&Cert> { match &self.obj { PgpDigParamsObj::Cert(cert) => Some(cert), PgpDigParamsObj::Subkey(cert, _) => Some(cert), PgpDigParamsObj::Signature(_) => None, } } fn key(&self) -> Option> { match &self.obj { PgpDigParamsObj::Cert(cert) => { Some(cert.primary_key().into()) } PgpDigParamsObj::Subkey(cert, fpr) => { Some(cert.keys().subkeys() .key_handle(fpr) .next() .expect("subkey missing") .into()) } PgpDigParamsObj::Signature(_) => None, } } fn signature(&self) -> Option<&Signature> { match &self.obj { PgpDigParamsObj::Cert(_) => None, PgpDigParamsObj::Subkey(_, _) => None, PgpDigParamsObj::Signature(sig) => Some(sig), } } } ffi!( /// Returns the signature's type. /// /// If `dig` is NULL or does not contain a signature, then this /// function returns -1. fn _pgpSignatureType(dig: *const PgpDigParams) -> c_int[-1] { let dig = check_ptr!(dig); dig.signature() .ok_or_else(|| Error::Fail("Not a signature".into())) .map(|sig| { u8::from(sig.typ()).into() }) }); ffi!( /// Frees the parameters. fn _pgpDigParamsFree(dig: Option<&mut PgpDigParams>) { free!(dig); }); ffi!( /// "Compares" the two parameters and returns 1 if they differ and 0 if /// they match. /// /// Two signatures are considered the same if they have the same /// parameters (version, signature type, public key and hash /// algorithms, and the first issuer packet). Note: this function /// explicitly does not check that the MPIs are the same, nor that the /// signature creation time is the same! This is intended. The only /// use of this function in the rpm code base is to check whether a key /// has already made a signature (cf. sign/rpmgensig.c:haveSignature). /// /// Two certificates are considered the same if they have the same /// fingerprint. (rpm does not currently use this functionality.) /// /// Two subkeys are considered the same if they have the same /// fingerprint. (rpm does not currently use this functionality.) fn _pgpDigParamsCmp(p1: *const PgpDigParams, p2: *const PgpDigParams) -> c_int[1] { let p1 = check_ptr!(p1); let p2 = check_ptr!(p2); let r = match (&p1.obj, &p2.obj) { (PgpDigParamsObj::Cert(c1), PgpDigParamsObj::Cert(c2)) => { c1.fingerprint() == c2.fingerprint() } (PgpDigParamsObj::Subkey(_, f1), PgpDigParamsObj::Subkey(_, f2)) => { f1 == f2 } (PgpDigParamsObj::Signature(s1), PgpDigParamsObj::Signature(s2)) => { t!("s1: {:?}", s1); t!("s2: {:?}", s2); s1.hash_algo() == s2.hash_algo() && s1.pk_algo() == s2.pk_algo() && s1.version() == s2.version() && s1.typ() == s2.typ() && p1.signid == p2.signid } _ => { false } }; Ok(if r { 0 } else { 1 }) }); const PGPVAL_PUBKEYALGO: c_uint = 6; const PGPVAL_HASHALGO: c_uint = 9; ffi!( /// Returns the object's public key or algorithm algorithm. /// /// `algotype` is either `PGPVAL_PUBKEYALGO` or `PGPVAL_HASHALGO`. /// Other algo types are not support and cause this function to return /// 0. fn _pgpDigParamsAlgo(dig: *const PgpDigParams, algotype: c_uint) -> c_uint[0] { let dig = check_ptr!(dig); match (algotype, &dig.obj) { // pubkey algo. (PGPVAL_PUBKEYALGO, PgpDigParamsObj::Cert(cert)) => { Ok(u8::from(cert.primary_key().key().pk_algo()).into()) } (PGPVAL_PUBKEYALGO, PgpDigParamsObj::Subkey(_, _)) => { Ok(u8::from(dig.key().expect("valid").key().pk_algo()).into()) } (PGPVAL_PUBKEYALGO, PgpDigParamsObj::Signature(sig)) => { Ok(u8::from(sig.pk_algo()).into()) } // hash algo. (PGPVAL_HASHALGO, PgpDigParamsObj::Cert(cert)) => { match cert.with_policy(&*P.read().unwrap(), None) { Ok(vc) => { let algo = vc.primary_key().binding_signature().hash_algo(); Ok(u8::from(algo).into()) } Err(err) => { Err(Error::Fail( format!("Using {}: {}", cert.fingerprint(), err))) } } } (PGPVAL_HASHALGO, PgpDigParamsObj::Subkey(_, fpr)) => { let ka = dig.key().expect("valid"); match ka.with_policy(&*P.read().unwrap(), None) { Ok(ka) => { let algo = ka.binding_signature().hash_algo(); Ok(u8::from(algo).into()) } Err(err) => { Err(Error::Fail( format!("Using {}: {}", fpr, err))) } } } (PGPVAL_HASHALGO, PgpDigParamsObj::Signature(sig)) => { Ok(u8::from(sig.hash_algo()).into()) } // Unknown algo. (t, PgpDigParamsObj::Cert(_)) | (t, PgpDigParamsObj::Subkey(_, _)) | (t, PgpDigParamsObj::Signature(_)) => { Err(Error::Fail(format!("Invalid algorithm type: {}", t))) } } }); ffi!( /// Returns the issuer or the Key ID. /// /// If `dig` is a signature, then this returns the Key ID stored in the /// first Issuer or Issuer Fingerprint subpacket as a hex string. /// (This is not authenticated!) /// /// If `dig` is a certificate or a subkey, then this returns the key's /// Key ID. /// /// The caller must *not* free the returned buffer. fn _pgpDigParamsSignID(dig: *const PgpDigParams) -> *const u8 { let dig = check_ptr!(dig); t!("SignID: {}", dig.signid.iter().map(|v| format!("{:02X}", v)).collect::()); Ok(dig.signid.as_ptr()) }); ffi!( /// Returns the primary User ID, if any. /// /// If `dig` is a signature, then this returns `NULL`. /// /// If `dig` is a certificate or a subkey, then this returns the /// certificate's primary User ID, if any. /// /// This interface does not provide a way for the caller to recognize /// any embedded `NUL` characters. /// /// The caller must *not* free the returned buffer. fn _pgpDigParamsUserID(dig: *const PgpDigParams) -> *const c_char { let dig = check_ptr!(dig); if let Some(ref userid) = dig.userid { Ok(userid.as_ptr()) } else { Ok(std::ptr::null()) } }); ffi!( /// Returns the object's version. /// /// If `dig` is a signature, then this returns the version of the /// signature packet. /// /// If `dig` is a certificate, then this returns the version of the /// primary key packet. /// /// If `dig` is a subkey, then this returns the version of the subkey's /// key packet. fn _pgpDigParamsVersion(dig: *const PgpDigParams) -> c_int[0] { let dig = check_ptr!(dig); let version = match &dig.obj { PgpDigParamsObj::Cert(cert) => { cert.primary_key().key().version() } PgpDigParamsObj::Subkey(_, _) => { dig.key().unwrap().key().version() } PgpDigParamsObj::Signature(sig) => { sig.version() } }; Ok(version as c_int) }); ffi!( /// Returns the object's time. /// /// If `dig` is a signature, then this returns the signature's creation /// time. /// /// If `dig` is a certificate, then this returns the primary key's key /// creation time. /// /// If `dig` is a subkey, then this returns the subkey's key creation /// time. fn _pgpDigParamsCreationTime(dig: *const PgpDigParams) -> u32[0] { let dig = check_ptr!(dig); let t = match &dig.obj { PgpDigParamsObj::Cert(cert) => { cert.primary_key().key().creation_time() } PgpDigParamsObj::Subkey(cert, fpr) => { cert.keys().subkeys() .key_handle(fpr) .next() .expect("subkey missing") .key() .creation_time() } PgpDigParamsObj::Signature(sig) => { sig.signature_creation_time().unwrap_or(UNIX_EPOCH) } }; Ok(t.duration_since(UNIX_EPOCH) .map_err(|_| Error::Fail("time".into()))? .as_secs() as u32) }); ffi!( /// Returns a signature's salt. /// /// Version 6 signatures are salted. The salt needs to be fed into /// the digest buffer before the actual message, which is processed /// already by RPM. /// /// The caller must *not* free the returned buffer. /// /// Returns an error if the signature does not include a salt. /// Version 6 signatures always have a salt; version 3 and version 4 /// signatures never have a salt and thus will always return an error. fn _pgpDigParamsSalt(dig: *const PgpDigParams, datap: *mut *const u8, lenp: *mut size_t) -> ErrorCode { let dig = check_ptr!(dig); let datap = check_mut!(datap); let lenp = check_mut!(lenp); let sig = dig.signature().ok_or_else(|| { Error::Fail("dig parameter does not designate a signature".into()) })?; if let Some(salt) = sig.salt() { t!("Salt: {}", salt.iter().map(|v| format!("{:02X}", v)).collect::()); *lenp = salt.len() as size_t; *datap = salt.as_ptr(); Ok(()) } else { Err(Error::Fail("The provided signature does not have any salt".into())) } }); ffi!( /// Verifies the signature. /// /// If `key` is `NULL`, then this computes the hash and checks it /// against the hash prefix. /// /// If `key` is not `NULL`, then this checks that the signature is /// correct. /// /// This function does not modify `ctx`. Instead, it first duplicates /// `ctx` and then hashes the the meta-data into that context. /// /// This function fails if the signature is not valid, or a supplied /// key is not valid. /// /// A signature is valid if: /// /// - The signature is alive now (not created in the future, and not /// yet expired) /// /// - It is accepted by the [policy]. /// /// A key is valid if as of the *signature's* creation time if: /// /// - The certificate is valid according to the [policy]. /// /// - The certificate is alive /// /// - The certificate is not revoke /// /// - The key is alive /// /// - The key is not revoke /// /// - The key has the signing capability set. /// /// [policy]: index.html#policy fn _pgpVerifySignature(key: *const PgpDigParams, sig: *const PgpDigParams, ctx: *const digest::DigestContext) -> ErrorCode { match _pgpVerifySignature2(key, sig, ctx, std::ptr::null_mut()) { 0 => Ok(()), ec => Err(Error::from(ec)), } }); ffi!( /// Like _pgpVerifySignature, but returns error messages and lints in /// `lint_str`. fn _pgpVerifySignature2(key: *const PgpDigParams, sig: *const PgpDigParams, ctx: *const digest::DigestContext, lint_str: *mut *mut c_char) -> ErrorCode { let key: Option<&PgpDigParams> = check_optional_ptr!(key); let sig: &PgpDigParams = check_ptr!(sig); // This function MUST NOT free or even change ctx. let mut ctx = check_ptr!(ctx).clone(); let mut lint_str: Option<&mut _> = check_optional_mut!(lint_str); if let Some(lint_str) = lint_str.as_mut() { **lint_str = std::ptr::null_mut(); } let mut lints = Vec::new(); let r = pgp_verify_signature(key, sig, ctx, &mut lints); // Return any lint / error messages. if lints.len() > 0 { let mut s: String = if let Some(key) = key { format!( "Verifying a signature using certificate {} ({}):", key.cert() .map(|cert| cert.fingerprint().to_string()) .unwrap_or_else(|| "".to_string()), key.cert() .and_then(|cert| { cert.userids().next() .map(|userid| { String::from_utf8_lossy(userid.userid().value()).into_owned() }) }) .unwrap_or_else(|| { "".into() })) } else { format!( "Verifying a signature, but no certificate was \ provided:") }; // Indent the lints. let sep = "\n "; let lints_count = lints.len(); for (err_i, err) in lints.into_iter().enumerate() { for (cause_i, cause) in error_chain(&err).into_iter().enumerate() { if cause_i == 0 { s.push_str(sep); if lints_count > 1 { s.push_str(&format!("{}. ", err_i + 1)); } } else { s.push_str(sep); s.push_str(" because: "); } s.push_str(&cause); } } t!("Lints: {}", s); if let Some(lint_str) = lint_str.as_mut() { // Add a trailing NUL. s.push('\0'); **lint_str = s.as_mut_ptr() as *mut c_char; // Pass ownership to the caller. std::mem::forget(s); } } r }); // Verifies the signature. // // Lints are appended to `lints`. Note: multiple lints may be added. fn pgp_verify_signature(key: Option<&PgpDigParams>, sig: &PgpDigParams, mut ctx: digest::DigestContext, lints: &mut Vec) -> Result<()> { tracer!(*crate::TRACE, "pgp_verify_signature"); linter!($, lints); // Whether the verification relies on legacy cryptography. let mut legacy = false; let sig = sig.signature().ok_or_else(|| { Error::Fail("sig parameter does not designate a signature".into()) })?; let sig_id = || { let digest_prefix = sig.digest_prefix(); format!("{:02x}{:02x} created at {}", digest_prefix[0], digest_prefix[1], if let Some(t) = sig.signature_creation_time() { DateTime::::from(t) .format("%c").to_string() } else { "".to_string() }) }; let sig_time = if let Some(t) = sig.signature_creation_time() { t } else { return_err!( None, "Signature {} invalid: signature missing a creation time", sig_id()); }; // Allow some clock skew. if let Err(err) = sig.signature_alive(None, Duration::new(5 * 60, 0)) { return_err!( Some(err), "Signature {} invalid: signature is not alive", sig_id()); } { let policy = P.read().unwrap(); if let Err(err) = policy.signature(sig, Default::default()) { if NP.signature(sig, Default::default()).is_ok() { legacy = true; add_lint!( Some(err), "Signature {} invalid: signature relies on legacy cryptography", sig_id()); } else { return_err!( Some(err), "Signature {} invalid: policy violation", sig_id()); } } // XXX: As of sequoia-openpgp v1.11.0, this check is not done // by `policy.signature` (see issue #953). We do it manually, // but once rpm-sequoia depends on a newer version of // sequoia-openpgp that does this, remove this code. if let Err(err) = policy.packet(&Packet::from(sig.clone())) { if NP.packet(&Packet::from(sig.clone())).is_ok() { legacy = true; add_lint!( Some(err), "Signature {} invalid: signature relies on legacy cryptography", sig_id()); } else { return_err!( Some(err), "Signature {} invalid: policy violation", sig_id()); } } } // XXX: rpm only cares about the first issuer // subpacket. let issuer = match sig.get_issuers().into_iter().next() { Some(issuer) => issuer, None => return_err!( None, "Signature {} invalid: signature has no issuer subpacket", sig_id()), }; if let Some(key) = key { // Actually verify the signature. let cert = key.cert().ok_or_else(|| { Error::Fail("key parameter is not a cert".into()) })?; let subkey = key.key().expect("is a certificate").key().fingerprint(); t!("Checking signature {} using {} with {} / {}", sig_id(), sig.hash_algo(), cert.fingerprint(), subkey); // We evaluate the certificate as of the signature creation // time. let p = &*P.read().unwrap(); let vc = cert.with_policy(p, sig_time) .or_else(|err| { // Try again, but use the current time as a reference // time. It is quite common for old self-signatures // to be stripped. match cert.with_policy(p, None) { Ok(vc) => { // We'd really like to emit the following // lint, but for most users it is not // actionable. When the ecosystem changes so // that certificates include older self // signatures, enable it again. // add_lint!( // None, // "Certificate has no valid binding signature \ // as of the signature's creation time, but \ // is valid now. The certificate has probably \ // been stripped or minimized."); Ok(vc) } Err(err2) => { add_lint!( Some(err), "Certificate {} invalid: policy violation", cert.keyid()); Err(err2) } } }) .or_else(|err| { legacy = true; add_lint!( Some(err), "Certificate {} invalid: policy violation", cert.keyid()); cert.with_policy(NP, sig_time) })?; if let Err(err) = vc.alive() { legacy = true; add_lint!( Some(err), "Certificate {} invalid: certificate is not alive", vc.keyid()); } if let RevocationStatus::Revoked(_) = vc.revocation_status() { legacy = true; add_lint!( None, "Certificate {} invalid: certificate is revoked", vc.keyid()); } // Find the key. match vc.keys().key_handle(issuer.clone()).next() { Some(ka) => { if ka.key().fingerprint() != subkey { return_err!(None, "Key {} invalid: wrong subkey ({})", ka.key().keyid(), subkey); } if ! ka.for_signing() { return_err!(None, "Key {} invalid: not signing capable", ka.key().keyid()); } if let Err(err) = ka.alive() { legacy = true; add_lint!(Some(err), "Key {} invalid: key is not alive", ka.key().keyid()); } if let RevocationStatus::Revoked(_) = ka.revocation_status() { legacy = true; add_lint!(None, "Key {} is invalid: key is revoked", ka.key().keyid()); } // Finally we can verify the signature. sig.clone().verify_hash(ka.key(), ctx.ctx.clone())?; if legacy { return Err(Error::NotTrusted( "Verification relies on legacy crypto".into()) .into()); } else { return Ok(()); } } None => { return_err!(None, "Certificate {} does not contain key {} \ or it is not valid", vc.keyid(), issuer); } } } else { // We don't have a key, but we still check that the prefix is // correct. // These traits should be imported only where needed to avoid // bugs. use openpgp::serialize::Marshal; use openpgp::serialize::MarshalInto; // See https://datatracker.ietf.org/doc/html/rfc4880#section-5.2.4 let mut sig_data = Vec::with_capacity(128); // Hash the signature into the context. match sig.version() { 4 | 6 => { sig_data.push(sig.version()); sig_data.push(sig.typ().into()); sig_data.push(sig.pk_algo().into()); sig_data.push(sig.hash_algo().into()); let l = sig.hashed_area().serialized_len(); if sig.version() == 6 { // The v6 signatures encode the hashed length as 4 bytes sig_data.push((l >> 24) as u8); sig_data.push((l >> 16) as u8); } sig_data.push((l >> 8) as u8); sig_data.push((l >> 0) as u8); if let Err(err) = sig.hashed_area().serialize(&mut sig_data) { return Err(Error::Fail( format!("Hashing signature data: {}", err))); } let sig_len = sig_data.len(); // Trailer. sig_data.push(sig.version()); sig_data.push(0xFF); sig_data.push((sig_len >> 24) as u8); sig_data.push((sig_len >> 16) as u8); sig_data.push((sig_len >> 8) as u8); sig_data.push((sig_len >> 0) as u8); } 3 => { sig_data.push(sig.typ().into()); let ct = sig.signature_creation_time().unwrap_or(UNIX_EPOCH); let ct = ct.duration_since(UNIX_EPOCH) .map_err(|_| Error::Fail("time".into()))? .as_secs() as u32; sig_data.push((ct >> 24) as u8); sig_data.push((ct >> 16) as u8); sig_data.push((ct >> 8) as u8); sig_data.push((ct >> 0) as u8); } v => { return Err(Error::Fail( format!("Unsupported signature version: {}", v))); } } ctx.update(&sig_data); let digest_size = ctx.digest_size(); let mut digest: Vec = Vec::with_capacity(digest_size); for _ in 0..digest_size { digest.push(0); } ctx.digest(&mut digest[..])?; let p = sig.digest_prefix(); if p[0] != digest[0] || p[1] != digest[1] { return Err(Error::Fail("digest prefix mismatch".into())); } else { t!("digest prefix matches"); } if ! sig.pk_algo().is_supported() { return Err(Error::NotTrusted( format!("Signature relies on unknown or unsupported \ cryptographic algorithm {}", sig.pk_algo()))); } else if legacy { return Err(Error::NotTrusted( "Signature relies on legacy crypto".into()) .into()); } else { return Err(Error::NoKey( format!("Not provided (issuer: {})", issuer).into())); } } } ffi!( /// Returns the Key ID of the public key or the secret key stored in /// `pkt`. /// /// Returns -1 if `pkt` is not a public key or secret key. /// /// Note: this function does not handle public subkeys or secret /// subkeys! /// /// `keyid` must be allocated by the caller and points to at least 8 /// bytes of memory. /// /// Returns 0 on success and -1 on failure. fn _pgpPubkeyKeyID(pkt: *const u8, pktlen: size_t, keyid: *mut u8) -> Binary { let pkt = check_slice!(pkt, pktlen); let ppr = PacketParser::from_bytes(pkt)?; let k = if let PacketParserResult::Some(ref pp) = ppr { match &pp.packet { Packet::PublicKey(key) => Some(key.keyid()), Packet::SecretKey(key) => Some(key.keyid()), _ => None, } } else { None }; t!("Key ID: {}", k.as_ref() .map(|k| k.to_string()) .unwrap_or_else(|| String::from("none"))); if let Some(k) = k { let buffer = check_mut_slice!(keyid, 8); buffer.copy_from_slice(k.as_bytes()); Ok(()) } else { Err(Error::Fail("Not a key".into())) } }); ffi!( /// Calculate OpenPGP public key fingerprint. /// /// Returns -1 if `pkt` is not a public key or secret key. /// /// Note: this function does not handle public subkeys or secret /// subkeys! /// /// `*fprout` is allocated using `malloc` and must be allocated by the /// caller. /// /// Returns 0 on success and -1 on failure. fn _pgpPubkeyFingerprint(pkt: *const u8, pktlen: size_t, fprout: *mut *mut u8, fprlen: *mut size_t) -> Binary { let pkt = check_slice!(pkt, pktlen); let ppr = PacketParserBuilder::from_bytes(pkt)? .dearmor(Dearmor::Disabled) // Disable dearmoring. .build()?; let fpr = if let PacketParserResult::Some(ref pp) = ppr { match &pp.packet { Packet::PublicKey(key) => Some(key.fingerprint()), Packet::SecretKey(key) => Some(key.fingerprint()), _ => None, } } else { None }; t!("Fingerprint: {}", fpr.as_ref() .map(|fpr| fpr.to_string()) .unwrap_or_else(|| String::from("none"))); if let Some(fpr) = fpr { let fpr = fpr.as_bytes(); unsafe { let buffer = libc::malloc(fpr.len()); if buffer.is_null() { return Err(Error::Fail("out of memory".into())); } libc::memcpy(buffer, fpr.as_ptr() as *const c_void, fpr.len()); *fprout = buffer as *mut u8; *fprlen = fpr.len(); } Ok(()) } else { Err(Error::Fail("Not a key".into())) } }); ffi!( /// Wraps the data in ASCII armor. /// /// `atype` is the armor type. /// /// The caller must free the returned buffer. /// /// Returns `NULL` on failure. fn _pgpArmorWrap(atype: c_int, s: *const c_char, ns: size_t) -> *mut c_char { let atype = armor::Kind::try_from(PgpArmor::from(atype))?; let s = check_slice!(s, ns); let mut writer = armor::Writer::new(Vec::new(), atype) .map_err(|err| Error::Fail(format!("creating armor writer: {}", err)))?; writer.write(s) .map_err(|err| Error::Fail(format!("writing armor body: {}", err)))?; let mut buffer = writer.finalize() .map_err(|err| Error::Fail(format!("finalizing armor: {}", err)))?; // Add a trailing NUL. buffer.push(0); let ptr = buffer.as_mut_ptr() as *mut c_char; std::mem::forget(buffer); Ok(ptr) }); ffi!( /// Returns the length of the certificate in bytes. /// /// `pkts` points to a buffer. Fails if `pkts` does not point to /// exactly one valid OpenPGP certificate. /// /// Returns 0 on failure. fn _pgpPubKeyCertLen(pkts: *const u8, pktslen: size_t, certlen: *mut size_t) -> Binary { use openpgp::packet::Header; use openpgp::packet::header::PacketLengthType; use openpgp::packet::header::BodyLength; use openpgp::packet::header::CTB; use buffered_reader::BufferedReader; let pkts = check_slice!(pkts, pktslen); let certlen = check_mut!(certlen); // XXX: These functions are more or less copied from // sequoia/openpgp/src/parse.rs. When sequoia-openpgp makes them // public, we drop this copy. fn body_length_parse_new_format(bio: &mut T) -> openpgp::Result where T: BufferedReader, C: Debug + Send + Sync { let octet1 : u8 = bio.data_consume_hard(1)?[0]; match octet1 { 0..=191 => // One octet. Ok(BodyLength::Full(octet1 as u32)), 192..=223 => { // Two octets length. let octet2 = bio.data_consume_hard(1)?[0]; Ok(BodyLength::Full(((octet1 as u32 - 192) << 8) + octet2 as u32 + 192)) }, 224..=254 => // Partial body length. Ok(BodyLength::Partial(1 << (octet1 & 0x1F))), 255 => // Five octets. Ok(BodyLength::Full(bio.read_be_u32()?)), } } /// Decodes an old format body length as described in [Section /// 4.2.1 of RFC 4880]. /// /// [Section 4.2.1 of RFC 4880]: https://tools.ietf.org/html/rfc4880#section-4.2.1 fn body_length_parse_old_format(bio: &mut T, length_type: PacketLengthType) -> openpgp::Result where T: BufferedReader, C: Debug + Send + Sync { match length_type { PacketLengthType::OneOctet => Ok(BodyLength::Full(bio.data_consume_hard(1)?[0] as u32)), PacketLengthType::TwoOctets => Ok(BodyLength::Full(bio.read_be_u16()? as u32)), PacketLengthType::FourOctets => Ok(BodyLength::Full(bio.read_be_u32()? as u32)), PacketLengthType::Indeterminate => Ok(BodyLength::Indeterminate), } } fn parse_header(bio: &mut T) -> openpgp::Result
where T: BufferedReader, C: Debug + Send + Sync { let ctb = CTB::try_from(bio.data_consume_hard(1)?[0])?; let length = match ctb { CTB::New(_) => body_length_parse_new_format(bio)?, CTB::Old(ref ctb) => body_length_parse_old_format(bio, ctb.length_type())?, }; return Ok(Header::new(ctb, length)); } let mut br = buffered_reader::Memory::new(pkts); let mut found_cert = false; let len: Option = loop { // The start of this packet as a byte offset into buffer. let start_of_packet = br.total_out(); if start_of_packet == pkts.len() { // We're done. break Some(start_of_packet); } let header = match parse_header(&mut br) { Ok(header) => header, Err(err) => { t!("Error reading certificate at offset {}: {}", start_of_packet, err); break None; } }; use Tag::*; let t = header.ctb().tag(); t!("Found a {:?} at offset {}, length: {:?}", t, start_of_packet, header.length()); match t { // Start of a new certificate. PublicKey | SecretKey => { if found_cert { break Some(start_of_packet); } else { found_cert = true; } } // The body of a certificate. PublicSubkey | SecretSubkey | UserID | UserAttribute | Signature | Marker | Trust | Unknown(_) | Private(_) => { if start_of_packet == 0 { t!("Encountered a ({:?}) at offset {}, \ which is not a valid start of a certificate", t, start_of_packet); break None; } } Reserved | PKESK | SKESK | OnePassSig | CompressedData | SED | Literal | SEIP | MDC | AED => { t!("Encountered a ({:?}) at offset {}, \ which does not belong in a certificate", t, start_of_packet); break None; } t => if t.is_critical() { t!("Encountered a ({:?}) at offset {}, \ which does not belong in a certificate", t, start_of_packet); break None; } else { // Ignore unknown non-critical packet. }, } // Advance to the next packet. match header.length() { BodyLength::Full(l) => { let l = *l as usize; if let Err(err) = br.data_consume_hard(l) { t!("Error while reading packet: {}", err); break None; } } BodyLength::Partial(_) => { t!("Packet {} has partial body length, \ which is unsupported by keyring splitter", t); break None; } BodyLength::Indeterminate => { t!("Packet {} has intedeterminite length, \ which is unsupported by keyring splitter", t); break None; } } }; if let Some(len) = len { *certlen = len; Ok(()) } else { Err(Error::Fail("No certificate found".into())) } }); ffi!( /// Parses OpenPGP data. /// /// If `pkts` contains a signature and `pkttype` is 0 or /// `Tag::Signature`, this returns a `PgpDigParams` containing a /// signature. /// /// If `pkts` contains a certificate and `pkttype` is 0, /// `Tag::PublicKey`, or `Tag::SecretKey`, this returns a /// `PgpDigParams` containing a certificate. The certificate is /// checked for validity in the sense that it only contains packets /// that belong to a certificate; this function does **not** check the /// binding signatures, etc. That check is done when the key is used /// in [_pgpVerifySignature]. /// /// Returns 0 on success, -1 on failure. fn _pgpPrtParams(pkts: *const u8, pktlen: size_t, pkttype: c_uint, paramsp: *mut *mut PgpDigParams) -> Binary { match _pgpPrtParams2(pkts, pktlen, pkttype, paramsp, std::ptr::null_mut()) { 0 => Ok(()), ec => Err(Error::from(ec)), } }); ffi!( /// Like _pgpPrtParams, but returns error messages and lints in /// `lint_str`. fn _pgpPrtParams2(pkts: *const u8, pktlen: size_t, pkttype: c_uint, paramsp: *mut *mut PgpDigParams, lint_str: *mut *mut c_char) -> Binary { let mut lint_str: Option<&mut _> = check_optional_mut!(lint_str); if let Some(lint_str) = lint_str.as_mut() { **lint_str = std::ptr::null_mut(); } let mut lints = Vec::new(); let r = pgp_prt_params(pkts, pktlen, pkttype, paramsp, &mut lints); // Return any lint / error messages. if lints.len() > 0 { let mut s: String = format!("Parsing an OpenPGP packet:"); // Indent the lints. let sep = "\n "; let lints_count = lints.len(); for (err_i, err) in lints.into_iter().enumerate() { for (cause_i, cause) in error_chain(&err).into_iter().enumerate() { if cause_i == 0 { s.push_str(sep); if lints_count > 1 { s.push_str(&format!("{}. ", err_i + 1)); } } else { s.push_str(sep); s.push_str(" because: "); } s.push_str(&cause); } } t!("Lints: {}", s); if let Some(lint_str) = lint_str.as_mut() { // Add a trailing NUL. s.push('\0'); **lint_str = s.as_mut_ptr() as *mut c_char; // Pass ownership to the caller. std::mem::forget(s); } } r }); fn pgp_prt_params(pkts: *const u8, pktlen: size_t, pkttype: c_uint, paramsp: *mut *mut PgpDigParams, lints: &mut Vec) -> Result<()> { tracer!(*crate::TRACE, "pgp_prt_params"); linter!($, lints); let pkttype: Option = if pkttype == 0 { None } else { Some(Tag::from(pkttype as u8)) }; let pkts = check_slice!(pkts, pktlen); let paramsp = check_mut!(paramsp); *paramsp = std::ptr::null_mut(); let ppr = PacketParser::from_bytes(pkts)?; let (obj, issuer, userid) = if let PacketParserResult::Some(pp) = ppr { // Process the packet. match pp.packet { Packet::Signature(_) if pkttype.is_none() || pkttype == Some(Tag::Signature) => { let (packet, next_ppr) = pp.next()?; if let PacketParserResult::Some(p) = next_ppr { return_err!(None, "Expected a bare OpenPGP signature, \ but it's followed by a {}", p.packet.tag()); } let sig = if let Packet::Signature(sig) = packet { sig } else { panic!("it's a sig"); }; (PgpDigParamsObj::Signature(sig.clone()), // XXX: Although there is normally only one issuer // subpacket, there may be multiple such subpackets. // Unfortunately, the API only allows us to return // one. sig.get_issuers().into_iter().next() .map(|i| KeyID::from(&i)), None) } Packet::PublicKey(_) | Packet::SecretKey(_) if pkttype.is_none() || pkttype == Some(Tag::PublicKey) || pkttype == Some(Tag::SecretKey) => { let cert = match CertParser::from(PacketParserResult::Some(pp)).next() { Some(Ok(cert)) => cert, Some(Err(err)) => return_err!( Some(err), "Failed to read an OpenPGP certificate"), None => return_err!( None, "Failed to read an OpenPGP certificate"), }; let keyid = cert.keyid().clone(); let userid = if let Ok(vc) = cert.with_policy(&*P.read().unwrap(), None) { vc.primary_userid() .ok() .and_then(|u| { CString::new(u.userid().value()).ok() }) } else { None }; (PgpDigParamsObj::Cert(cert), Some(keyid), userid) } Packet::Unknown(mut u) => { let mut err = u.set_error(anyhow::anyhow!("Error")); if let Some(openpgp::Error::MalformedMPI(_)) = err.downcast_ref::() { err = err.context("\ Signature appears to be created by a \ non-conformant OpenPGP implementation, see \ ."); } return_err!(Some(err), "Failed to parse {}", u.tag()); } ref p => { return_err!( None, "Unexpected OpenPGP packet in this context {}", p.tag()); } } } else { return_err!( None, "Expected an OpenPGP packet, encountered the end of the file"); }; let mut buffer: [u8; 8] = [0; 8]; match issuer { Some(KeyID::Long(issuer)) => { assert_eq!(buffer.len(), issuer.len()); buffer.copy_from_slice(&issuer); } issuer => { return_err!( None, "Signature contains an invalid issuer packet: {:?}", issuer); } } *paramsp = move_to_c!(PgpDigParams { obj, signid: buffer, userid: userid, }); Ok(()) } ffi!( /// Returns a `PgpDigParams` data structure for each subkey. /// /// This does not return a `PgpDigParams` for the primary (just use /// this one). The subkeys are **not** checked for validity. That /// check is done when the key is used in [_pgpVerifySignature]. fn _pgpPrtParamsSubkeys(pkts: *const u8, pktlen: size_t, _mainkey: *const PgpDigParams, subkeys: *mut *mut PgpDigParams, subkeys_count: *mut c_int) -> Binary { let pkts = check_slice!(pkts, pktlen); let subkeys = check_mut!(subkeys); *subkeys = std::ptr::null_mut(); let subkeys_count = check_mut!(subkeys_count); let ppr = PacketParser::from_bytes(pkts)?; let cert = match ppr { PacketParserResult::Some(ref pp) => { match pp.packet { Packet::PublicKey(_) | Packet::SecretKey(_) => { let cert = CertParser::from(ppr) .next() .ok_or(Error::Fail("Not an OpenPGP certificate".into()))??; cert } ref p => { return Err(Error::Fail(format!("{}", p.tag()))); } } } _ => return Err(Error::Fail("Not an OpenPGP message".into())), }; let userid = if let Ok(vc) = cert.with_policy(&*P.read().unwrap(), None) { vc.primary_userid() .ok() .and_then(|u| { CString::new(u.userid().value()).ok() }) } else { None }; // We return all subkeys here. Subkeys are checked for validity // on demand. let mut keys: Vec<*mut PgpDigParams> = cert .keys().subkeys() .map(|ka| { t!("Subkey: {}", ka.key().keyid()); let zeros = [0; 8]; let mut dig = PgpDigParams { obj: PgpDigParamsObj::Subkey(cert.clone(), ka.key().fingerprint()), signid: zeros, userid: userid.clone(), }; dig.signid.copy_from_slice(ka.key().keyid().as_bytes()); move_to_c!(dig) }) .collect(); t!("Got {} subkeys", keys.len()); *subkeys_count = keys.len() as c_int; if keys.len() == 0 { *subkeys = std::ptr::null_mut(); } else { *subkeys = keys.as_mut_ptr() as *mut PgpDigParams; // Pass ownership to the caller. std::mem::forget(keys); } Ok(()) }); ffi!( /// Strips the ASCII armor and returns the decoded data in `pkt`. /// /// Despite its name, this function does not actually parse any OpenPGP /// packets; it just strips the ASCII armor encoding. /// /// Returns the type of armor on success (>0) or an error code /// indicating the type of failure (<0). fn _pgpParsePkts(armor: *const c_char, pkt: *mut *mut c_char, pktlen: *mut size_t) -> PgpArmor { let armor = check_cstr!(armor); let pkt = check_mut!(pkt); *pkt = std::ptr::null_mut(); let pktlen = check_mut!(pktlen); let mut reader = armor::Reader::from_reader( std::io::BufReader::new( armor.to_str().map_err(|_| PgpArmorError::BodyDecode)?.as_bytes()), armor::ReaderMode::Tolerant(None)); let mut buf = Vec::new(); reader.read_to_end(&mut buf).map_err(|_| PgpArmorError::BodyDecode)?; let kind = reader.kind(); *pktlen = buf.len() as size_t; *pkt = buf.as_mut_ptr() as *mut c_char; // Pass ownership to the caller. std::mem::forget(buf); Ok(kind.into()) }); ffi!( /// Lints the first certificate in pkts. /// /// This function lints the certificate according to the current /// [policy]. It warns about things like unusable subkeys, because they /// do not have a valid binding signature. It will also generate a /// warning if there are no valid, signing-capable keys. /// /// There are four cases: /// /// - The packets do not describe a certificate: returns an error and /// sets `*explanation` to `NULL`. /// /// - The packets describe a certificate and the certificate is /// completely unusable: returns an error and sets `*explanation` to /// a human readable explanation. /// /// - The packets describe a certificate and some components are not /// usable: returns success, and sets `*explanation` to a human /// readable explanation. /// /// - The packets describe a certificate and there are no lints: /// returns success, and sets `*explanation` to `NULL`. /// /// [policy]: index.html#policy fn _pgpPubKeyLint(pkts: *const c_char, pktslen: size_t, explanation: *mut *mut c_char) -> ErrorCode { let pkts = check_slice!(pkts, pktslen); let explanation = check_mut!(explanation); // Whether the key relies on legacy cryptography. let mut legacy = false; // Make sure we always set explanation to something. *explanation = std::ptr::null_mut(); let cert = CertParser::from_bytes(pkts)?.next() .ok_or(Error::Fail("Not an OpenPGP certificate".into()))??; let mut lints: Vec = Vec::new(); let mut lint = |l: &str| { lints.push(l.into()); }; #[allow(clippy::never_loop)] let usable = 'done : loop { match cert.with_policy(&*P.read().unwrap(), None) { Err(err) => { lint(&format!("Policy rejects {}: {}", cert.keyid(), error_chain_display(&err))); // If the signatures are valid, then assume it failed // due to the use of legacy cryptography. if let Ok(_) = cert.with_policy(NP, None) { legacy = true; } break 'done false; } Ok(vc) => { if let RevocationStatus::Revoked(revs) = vc.revocation_status() { for rev in revs { if let Some((reason, msg)) = rev.reason_for_revocation() { let mut l = format!( "The certificate was revoked: {}", reason); if ! msg.is_empty() { l.push_str(&format!( ", {}", String::from_utf8_lossy(msg))); } lint(&l); } else { lint("The certificate was revoked: \ unspecified reason"); } } } if let Err(err) = vc.alive() { if let Some(e) = vc.primary_key().key_expiration_time() { if e <= SystemTime::now() { lint(&format!("The certificate is expired: {}", error_chain_display(&err))); } else { lint(&format!("The certificate is not live: {}", error_chain_display(&err))); } } } } }; let mut have_signing = false; for ka in cert.keys() { let keyid = ka.key().keyid(); match ka.with_policy(&*P.read().unwrap(), None) { Err(err) => { lint(&format!("Policy rejects subkey {}: {}", keyid, error_chain_display(&err))); // If the signatures are valid, then assume it // failed due to the use of legacy cryptography. if let Ok(_) = ka.with_policy(NP, None) { legacy = true; } continue; } Ok(ka) => { if ! ka.for_signing() { // Silently ignore non-signing capable // subkeys. We don't care about them. continue; } if let RevocationStatus::Revoked(revs) = ka.revocation_status() { for rev in revs { if let Some((reason, msg)) = rev.reason_for_revocation() { let mut l = format!( "Subkey {} was revoked: {}", keyid, reason); if ! msg.is_empty() { l.push_str(&format!( ", {}", String::from_utf8_lossy(msg))); } lint(&l); } else { lint(&format!( "Subkey {} was revoked: \ unspecified reason", keyid)); } } continue; } if let Err(err) = ka.alive() { if let Some(e) = ka.key_expiration_time() { if e <= SystemTime::now() { lint(&format!("Subkey {} is expired: {}", keyid, error_chain_display(&err))); } else { lint(&format!("Subkey {} is not live: {}", keyid, error_chain_display(&err))); } } continue; } if ! ka.key().pk_algo().is_supported() { lint(&format!("Subkey {} is not supported \ (no support for {})", keyid, ka.key().pk_algo())); continue; } have_signing = true; } } } if ! have_signing { lint("Certificate does not have any usable signing keys"); } break true; }; if ! lints.is_empty() { // Indent the lints. let sep = "\n "; let mut s: String = format!("Certificate {}:{}", cert.keyid(), sep); s.push_str(&lints.join(sep)); s.push('\0'); *explanation = s.as_mut_ptr() as *mut c_char; // Pass ownership to the caller. std::mem::forget(s); } if usable { Ok(()) } else if legacy { Err(Error::NotTrusted( format!("Certificate {} relies on legacy crypto", cert.keyid()))) } else { Err(Error::Fail(format!("Certificate {} is unusable", cert.keyid()))) } }); /// An optional OpenPGP certificate *and* an optional signature. /// /// This data structure is deprecated and is scheduled for removal in /// rpm 4.19. pub struct PgpDig { cert: Option>, sig: Option>, } /// Dump the packets to stderr. /// /// This is used by _pgpPrtPkts, which is deprecated and is scheduled /// for removal in rpm 4.19. It is intended to be bug compatible with /// rpm's internal implementation. fn dump_packets(pkts: &[u8]) -> Result<()> { use openpgp::types::CompressionAlgorithm; use openpgp::types::KeyServerPreferences; use openpgp::types::PublicKeyAlgorithm; use openpgp::types::SignatureType; use openpgp::types::SymmetricAlgorithm; use openpgp::packet::signature::subpacket::Subpacket; use openpgp::packet::signature::subpacket::SubpacketTag; use openpgp::packet::signature::subpacket::SubpacketValue; let mut ppr = PacketParser::from_bytes(pkts)?; fn pk_algo(a: PublicKeyAlgorithm) -> &'static str { use PublicKeyAlgorithm::*; #[allow(deprecated)] match a { RSAEncryptSign => "RSA", RSAEncrypt => "RSA(Encrypt-Only)", RSASign => "RSA(Sign-Only)", ElGamalEncrypt => "Elgamal(Encrypt-Only)", DSA => "DSA", ECDH => "Elliptic Curve", ECDSA => "ECDSA", ElGamalEncryptSign => "Elgamal", EdDSA => "EdDSA", _ => "Unknown public key algorithm", } } fn sigtype(t: SignatureType) -> &'static str { use SignatureType::*; match t { Binary => "Binary document signature", Text => "Text document signature", Standalone => "Standalone signature", GenericCertification => "Generic certification of a User ID and Public Key", PersonaCertification => "Persona certification of a User ID and Public Key", CasualCertification => "Casual certification of a User ID and Public Key", PositiveCertification => "Positive certification of a User ID and Public Key", SubkeyBinding => "Subkey Binding Signature", PrimaryKeyBinding => "Primary Key Binding Signature", DirectKey => "Signature directly on a key", KeyRevocation => "Key revocation signature", SubkeyRevocation => "Subkey revocation signature", CertificationRevocation => "Certification revocation signature", Timestamp => "Timestamp signature", _ => "Unknown signature type", } } fn symalgo(a: SymmetricAlgorithm) -> &'static str { use SymmetricAlgorithm::*; #[allow(deprecated)] match a { Unencrypted => "Plaintext", IDEA => "IDEA", TripleDES => "3DES", CAST5 => "CAST5", Blowfish => "BLOWFISH", AES128 => "AES(128-bit key)", AES192 => "AES(192-bit key)", AES256 => "AES(256-bit key)", Twofish => "TWOFISH(256-bit key)", _ => "Unknown symmetric key algorithm", } } fn compalgo(a: CompressionAlgorithm) -> &'static str { use CompressionAlgorithm::*; match a { Uncompressed => "Uncompressed", Zip => "ZIP", Zlib => "ZLIB", BZip2 => "BZIP2", _ => "Unknown compression algorithm", } } fn ksprefs(prefs: KeyServerPreferences) -> &'static str { // This is wrong, but this is what the internal implementation // does. if prefs.no_modify() { "No-modify(128)" } else if KeyServerPreferences::empty().normalized_eq(&prefs) { "" } else { "Unknown key server preference" } } fn subpacket(sp: &Subpacket) -> String { let mut output: Vec = Vec::new(); let tag = sp.tag(); let s = { use SubpacketTag::*; match tag { SignatureCreationTime => "signature creation time", SignatureExpirationTime => "signature expiration time", ExportableCertification => "exportable certification", TrustSignature => "trust signature", RegularExpression => "regular expression", Revocable => "revocable", KeyExpirationTime => "key expiration time", PlaceholderForBackwardCompatibility => "additional recipient request", PreferredSymmetricAlgorithms => "preferred symmetric algorithms", RevocationKey => "revocation key", Issuer => "issuer key ID", NotationData => "notation data", PreferredHashAlgorithms => "preferred hash algorithms", PreferredCompressionAlgorithms => "preferred compression algorithms", KeyServerPreferences => "key server preferences", PreferredKeyServer => "preferred key server", PrimaryUserID => "primary user id", PolicyURI => "policy URL", KeyFlags => "key flags", SignersUserID => "signer's user id", ReasonForRevocation => "reason for revocation", Features => "features", EmbeddedSignature => "embedded signature", _ => "Unknown signature subkey type", } }; output.push(s.into()); output.push(format!("({})", Into::::into(tag))); if sp.critical() { output.push(" *CRITICAL*".into()); } { use SubpacketValue::*; match sp.value() { PreferredSymmetricAlgorithms(algos) => { output.push(" ".into()); output.push( algos.iter() .map(|a| { format!("{}({})", symalgo(*a), Into::::into(*a)) }) .collect::>() .join(" ")) } PreferredHashAlgorithms(algos) => { output.push(" ".into()); output.push( algos.iter() .map(|a| { format!("{}({})", a, Into::::into(*a)) }) .collect::>() .join(" ")) } PreferredCompressionAlgorithms(algos) => { output.push(" ".into()); output.push( algos.iter() .map(|a| { format!("{}({})", compalgo(*a), Into::::into(*a)) }) .collect::>() .join(" ")) } KeyServerPreferences(prefs) => { output.push(format!(" {}", ksprefs(prefs.clone()))) } SignatureExpirationTime(d) | KeyExpirationTime(d) => { // expiration time is an offset from the creation // time, but rpm's internal OpenPGP implementation // treats it as an absolute time. As we're going // for bug-for-bug compatibility here, we do the // same. let t = DateTime::from_timestamp( d.as_secs() as i64, 0) // This is just compatibility, debugging // output. Fallback to the unix epoch. .unwrap_or_default(); output.push(format!(" {}(0x{:08x})", t.format("%c"), d.as_secs())); } SignatureCreationTime(_) | Issuer(_) | KeyFlags(_) => (), _ => { use sequoia_openpgp::serialize::MarshalInto; output.push(" ".into()); output.extend( sp.value() .to_vec() .unwrap_or(Vec::new()) .into_iter() .map(|b| format!("{:02x}", b))) } } } output.join("") } while let PacketParserResult::Some(pp) = ppr { let (packet, next_ppr) = pp.recurse()?; ppr = next_ppr; // We only dump what rpm's internal OpenPGP implementation // dumps. Other packets we silently ignore. #[allow(deprecated)] match packet { Packet::Signature(sig) => { // V4 Signature(2) DSA(17) SHA512(10) Generic certification of a User ID and Public Key(16) // signature creation time(2) // issuer key ID(16) // signhash16 1418 eprintln!("V{} Signature(2) {}({}) {}({}) {}({})", sig.version(), pk_algo(sig.pk_algo()), Into::::into(sig.pk_algo()), sig.hash_algo(), Into::::into(sig.hash_algo()), sigtype(sig.typ()), Into::::into(sig.typ())); sig.hashed_area().iter().for_each(|sb| { eprintln!(" {}", subpacket(sb)); }); sig.unhashed_area().iter().for_each(|sb| { eprintln!(" {}", subpacket(sb)); }); eprintln!(" signhash16 {:02x}{:02x}", sig.digest_prefix()[0], sig.digest_prefix()[1]); }, Packet::PublicKey(key) => { // V4 Public Key(6) RSA(1) Tue Apr 7 08:52:57 2015(0x55239ae9) let secs = key.creation_time() .duration_since(SystemTime::UNIX_EPOCH) .map(|d| d.as_secs()) .unwrap_or(0); let t: DateTime:: = key.creation_time().into(); eprintln!("V{} Public Key(6) {}({}) {}(0x{:08x})", key.version(), pk_algo(key.pk_algo()), Into::::into(key.pk_algo()), t.format("%c"), secs); } Packet::PublicSubkey(key) => { // Public Subkey(14) 045523a696010... use sequoia_openpgp::serialize::MarshalInto; let secs = key.creation_time() .duration_since(SystemTime::UNIX_EPOCH) .map(|d| d.as_secs()) .unwrap_or(0); eprintln!("Public Subkey(14) {:02}{:08x}{:02x}{}", key.version(), secs, Into::::into(key.pk_algo()), key.mpis().to_vec() .unwrap_or_else(|_| Vec::new()) .into_iter() .map(|b| format!("{:02x}", b)) .collect::()); } Packet::UserID(userid) => { // User ID(13) "Neal H. Walfield " eprintln!("User ID(13) {:?}", String::from_utf8_lossy(userid.value())); } Packet::Unknown(_pkt) => (), Packet::OnePassSig(_ops) => (), Packet::SecretKey(_key) => (), Packet::SecretSubkey(_key) => (), Packet::Marker(_marker) => (), Packet::Trust(_trust) => (), Packet::UserAttribute(_ua) => (), Packet::Literal(_lit) => (), Packet::CompressedData(_cd) => (), Packet::PKESK(_pkesk) => (), Packet::SKESK(_skesk) => (), Packet::SEIP(_seip) => (), Packet::MDC(_mdc) => (), _ => (), } } Ok(()) } ffi!( /// Parses and optionally prints to stdout a OpenPGP packet(s). /// /// This function is deprecated and is scheduled for removal in rpm /// 4.19. /// /// @param pkts OpenPGP packet(s) /// @param pktlen OpenPGP packet(s) length (no. of bytes) /// @param(out) dig parsed output of signature/pubkey packet parameters /// @param printing should packets be printed? /// /// Returns 0 on success, -1 on failure. fn _pgpPrtPkts(pkts: *const u8, pktslen: size_t, dig: *mut PgpDig, printing: c_int) -> Binary { let dig = check_mut!(dig); let mut params: *mut PgpDigParams = std::ptr::null_mut(); if printing != 0 { // We ignore any error here as this printing should not change // the functions semantics. let _ = dump_packets(check_slice!(pkts, pktslen)); } let result = _pgpPrtParams(pkts, pktslen, 0, &mut params); if result == -1 { return Err(Error::Fail("Parse error".into())); } let params = claim_from_c!(params); match params.obj { PgpDigParamsObj::Cert(_) => dig.cert = Some(params), PgpDigParamsObj::Subkey(_, _) => dig.cert = Some(params), PgpDigParamsObj::Signature(_) => dig.sig = Some(params), } Ok(()) }); ffi!( /// Create a container for parsed OpenPGP packet(s). /// /// This function is deprecated and is scheduled for removal in rpm /// 4.19. /// /// @return container fn _pgpNewDig() -> *mut PgpDig { Ok(move_to_c!(PgpDig { cert: None, sig: None, })) }); ffi!( /// Release (malloc'd) data from container. /// /// This function is deprecated and is scheduled for removal in rpm /// 4.19. /// /// @param dig container fn _pgpCleanDig(dig: *mut PgpDig) { let dig = check_mut!(dig); dig.cert = None; dig.sig = None; }); ffi!( /// Destroy a container for parsed OpenPGP packet(s). /// /// This function is deprecated and is scheduled for removal in rpm /// 4.19. /// /// @param dig container /// @return NULL always fn _pgpFreeDig(dig: Option<&mut PgpDig>) -> *mut PgpDig { free!(dig); Ok(std::ptr::null_mut()) }); ffi!( /// Retrieve parameters for parsed OpenPGP packet(s). /// /// This function is deprecated and is scheduled for removal in rpm /// 4.19. /// /// @param dig container /// @param pkttype type of params to retrieve (signature / pubkey) /// @return pointer to OpenPGP parameters, NULL on error/not found fn _pgpDigGetParams(dig: *const PgpDig, pkttype: c_uint) -> *const PgpDigParams { let dig = check_ptr!(dig); let ptr = match Tag::from(pkttype as u8) { Tag::PublicKey => { if let Some(ref cert) = dig.cert { cert.as_ref() } else { std::ptr::null() } } Tag::Signature => { if let Some(ref sig) = dig.sig { sig.as_ref() } else { std::ptr::null() } } _ => { std::ptr::null() } }; Ok(ptr) }); ffi!( /// Verify a PGP signature. /// /// This function is deprecated and is scheduled for removal in rpm /// 4.19. /// /// @param dig container /// @param hashctx digest context /// @return RPMRC_OK on success fn _pgpVerifySig(dig: *const PgpDig, ctx: *const digest::DigestContext) -> ErrorCode { Err( _pgpVerifySignature( _pgpDigGetParams(dig, u8::from(Tag::PublicKey) as u32), _pgpDigGetParams(dig, u8::from(Tag::Signature) as u32), ctx).into()) }); ffi!( /// Merge the PGP packets of two certificates /// /// The certificates must describe the same public key. The call should merge /// important pgp packets (self-signatures, new subkeys, ...) and remove duplicates. /// /// - `pkts1` - OpenPGP pointer to a buffer with the first certificate /// - `pkts1len` - length of the buffer with the first certificate /// - `pkts2` - OpenPGP pointer to a buffer with the second certificate /// - `pkts2len` - length of the buffer with the second certificate /// - `pktsm` - merged certificate (malloced) /// - `pktsmlen` - length of merged certificate /// - `flags` - merge flags (currently not used, must be zero) /// /// Returns `RPMRC_OK` on success. fn _pgpPubkeyMerge( pkts1: *const u8, pkts1len: size_t, pkts2: *const u8, pkts2len: size_t, pktsm: *mut *mut u8, pktsmlen: *mut size_t, flags: c_int) -> ErrorCode { let pkts1 = check_slice!(pkts1, pkts1len); let pkts2 = check_slice!(pkts2, pkts2len); let cert1 = Cert::from_bytes(pkts1)?; let cert2 = Cert::from_bytes(pkts2)?; if cert1.fingerprint() != cert2.fingerprint() { return Err(Error::Fail("Can't merge different certificates".into())); } let (merged, updated) = cert1.clone().insert_packets(cert2.into_packets())?; let merged_bytes_; let (result, result_len) = if ! updated { // The certificate is unchanged. Nevertheless, // Cert::from_bytes may have changed the bit representation, // e.g., by canonicalizing it. To avoid making rpm think that // the certificate has changed when it hasn't (it does a // memcmp), we return pkts1. (pkts1.as_ptr(), pkts1len) } else { merged_bytes_ = merged.to_vec()?; (merged_bytes_.as_ptr(), merged_bytes_.len()) }; unsafe { let buffer = libc::malloc(result_len); if buffer.is_null() { return Err(Error::Fail("out of memory".into())); } libc::memcpy(buffer, result as *const c_void, result_len); *pktsmlen = result_len as size_t; *pktsm = buffer as *mut u8; } Ok(()) }); #[cfg(test)] mod tests { use super::*; use openpgp::cert::CertBuilder; use openpgp::serialize::SerializeInto; use openpgp::types::KeyFlags; // Check that we can successfully merge two certificates. #[test] fn merge_certs() { let p = openpgp::policy::StandardPolicy::new(); let (cert, _rev) = CertBuilder::new() .add_userid("Alice") .generate() .unwrap(); let vc = cert.with_policy(&p, None).unwrap(); // We should only have a primary, which is certification capable. assert_eq!(vc.keys().for_signing().count(), 0); assert_eq!(vc.keys().for_transport_encryption().count(), 0); assert_eq!(vc.keys().for_storage_encryption().count(), 0); // Add a signing subkey. let cert2 = KeyBuilder::new( KeyFlags::empty().set_signing()) .subkey(vc.clone()).unwrap() .attach_cert().unwrap(); // Add an encryption subkey. let cert3 = KeyBuilder::new( KeyFlags::empty().set_transport_encryption()) .subkey(vc.clone()).unwrap() .attach_cert().unwrap(); let cert2_bytes = cert2.to_vec().unwrap(); let cert3_bytes = cert3.to_vec().unwrap(); let mut result: *mut u8 = std::ptr::null_mut(); let mut result_len: size_t = 0; eprintln!("About to run pgpPubkeyMerge"); let ec = _pgpPubkeyMerge( cert2_bytes.as_ptr(), cert2_bytes.len(), cert3_bytes.as_ptr(), cert3_bytes.len(), &mut result, &mut result_len, 0); assert_eq!(ec, 0); assert!(! result.is_null()); let result = unsafe { std::slice::from_raw_parts(result as *const u8, result_len) }; let result = Cert::from_bytes(result).expect("valid cert"); assert_eq!(cert.fingerprint(), result.fingerprint()); assert!(result != cert); assert!(result != cert2); assert!(result != cert3); let expected = cert2.clone().merge_public(cert3.clone()).unwrap(); assert_eq!(result, expected); let result_vc = result.with_policy(&p, None).unwrap(); assert_eq!(result_vc.keys().for_signing().count(), 1); assert_eq!(result_vc.keys().for_transport_encryption().count(), 1); assert_eq!(result_vc.keys().for_storage_encryption().count(), 0); } // Check that when we attempt to merge two different certificates, // we return an error. #[test] fn merge_certs_mismatch() { let (cert, _rev) = CertBuilder::new() .add_userid("Alice") .generate() .unwrap(); let (cert2, _rev) = CertBuilder::new() .add_userid("Bob") .generate() .unwrap(); let cert_bytes = cert.to_vec().unwrap(); let cert2_bytes = cert2.to_vec().unwrap(); let mut result: *mut u8 = std::ptr::null_mut(); let mut result_len: size_t = 0; eprintln!("About to run pgpPubkeyMerge"); let ec = _pgpPubkeyMerge( cert_bytes.as_ptr(), cert_bytes.len(), cert2_bytes.as_ptr(), cert2_bytes.len(), &mut result, &mut result_len, 0); assert_ne!(ec, 0); } } rpm-sequoia-1.10.2/src/log.rs000064400000000000000000000073511046102023000140730ustar 00000000000000use std::cell::RefCell; // Like eprintln! macro_rules! log { ($dst:expr $(,)?) => ( eprintln!("{}", $dst) ); ($dst:expr, $($arg:tt)*) => ( eprintln!("{}", std::format!($dst, $($arg)*)) ); } // The indent level. It is increased with each call to tracer and // decremented when the tracker goes out of scope. thread_local! { pub static INDENT_LEVEL: RefCell = const { RefCell::new(0) }; } // Like eprintln!, but the first argument is a boolean, which // indicates if the string should actually be printed. macro_rules! trace { ( $TRACE:expr, $fmt:expr, $($pargs:expr),* ) => { if $TRACE { let indent_level = crate::log::INDENT_LEVEL.with(|i| { *i.borrow() }); let ws = " "; log!("{}{}", &ws[0..std::cmp::min(ws.len(), std::cmp::max(1, indent_level) - 1)], format!($fmt, $($pargs),*)); } }; ( $TRACE:expr, $fmt:expr ) => { trace!($TRACE, $fmt, ); }; } macro_rules! tracer { ( $TRACE:expr, $func:expr ) => { // Currently, Rust doesn't support $( ... ) in a nested // macro's definition. See: // https://users.rust-lang.org/t/nested-macros-issue/8348/2 macro_rules! t { ( $fmt:expr ) => { trace!($TRACE, "{}: {}", $func, $fmt) }; ( $fmt:expr, $a:expr ) => { trace!($TRACE, "{}: {}", $func, format!($fmt, $a)) }; ( $fmt:expr, $a:expr, $b:expr ) => { trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr ) => { trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b, $c)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr ) => { trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b, $c, $d)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr ) => { trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b, $c, $d, $e)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr ) => { trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b, $c, $d, $e, $f)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr ) => { trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b, $c, $d, $e, $f, $g)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr ) => { trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b, $c, $d, $e, $f, $g, $h)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $i:expr ) => { trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b, $c, $d, $e, $f, $g, $h, $i)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $i:expr, $j:expr ) => { trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b, $c, $d, $e, $f, $g, $h, $i, $j)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr, $g:expr, $h:expr, $i:expr, $j:expr, $k:expr ) => { trace!($TRACE, "{}: {}", $func, format!($fmt, $a, $b, $c, $d, $e, $f, $g, $h, $i, $j, $k)) }; } struct Indent {} impl Indent { fn init() -> Self { crate::log::INDENT_LEVEL.with(|i| { i.replace_with(|i| *i + 1); }); Indent {} } } impl Drop for Indent { fn drop(&mut self) { crate::log::INDENT_LEVEL.with(|i| { i.replace_with(|i| *i - 1); }); } } let _indent = Indent::init(); } } rpm-sequoia-1.10.2/src/rpm.rs000064400000000000000000000117301046102023000141040ustar 00000000000000use libc::c_int; use sequoia_openpgp as openpgp; use openpgp::armor; // We use Error rather than anyhow's error so that we force the // function to convert the error into a form that we can easily pass // back to the engine. pub type Result = std::result::Result; pub type ErrorCode = c_int; #[non_exhaustive] #[derive(thiserror::Error, Debug, Clone)] #[allow(unused)] pub enum Error { #[error("Success")] Ok, #[error("Not found: {0}")] NotFound(String), #[error("Failure: {0}")] Fail(String), #[error("Signature is OK, but key is not trusted: {0}")] NotTrusted(String), #[error("Public key is unavailable: {0}")] NoKey(String), } impl From for ErrorCode { fn from(err: Error) -> ErrorCode { match err { Error::Ok => 0, Error::NotFound(_) => 1, Error::Fail(_) => 2, Error::NotTrusted(_) => 3, Error::NoKey(_) => 4, } } } impl From for Error { fn from(err: ErrorCode) -> Error { match err { 0 => Error::Ok, 1 => Error::NotFound("".into()), 2 => Error::Fail("".into()), 3 => Error::NotTrusted("".into()), 4 => Error::NoKey("".into()), _ => Error::Fail("".into()), } } } impl From for Error { fn from(err: anyhow::Error) -> Error { Error::Fail(format!("{}", err)) } } #[non_exhaustive] #[derive(thiserror::Error, Debug, Clone)] #[allow(unused)] pub enum PgpArmorError { #[error("Success")] Ok, #[error("unknown error")] UnknownError, #[error("armor crc check")] CrcCheck, #[error("armor body decode")] BodyDecode, #[error("armor crc decode")] CrcDecode, #[error("armor no end pgp")] NoEndPgp, #[error("armor unknown preamble tag")] UnknownPreambleTag, #[error("armor unknown armor type")] UnknownArmorType, #[error("armor no begin pgp")] NoBeginPgp, } impl From for ErrorCode { fn from(err: PgpArmorError) -> ErrorCode { match err { PgpArmorError::Ok => 0, PgpArmorError::UnknownError => -1, PgpArmorError::CrcCheck => -7, PgpArmorError::BodyDecode => -6, PgpArmorError::CrcDecode => -5, PgpArmorError::NoEndPgp => -4, PgpArmorError::UnknownPreambleTag => -3, PgpArmorError::UnknownArmorType => -2, PgpArmorError::NoBeginPgp => -1, } } } impl From for PgpArmorError { fn from(_err: Error) -> PgpArmorError { PgpArmorError::UnknownError } } impl From for PgpArmorError { fn from(_err: anyhow::Error) -> PgpArmorError { PgpArmorError::UnknownError } } #[non_exhaustive] #[allow(unused)] #[derive(Debug, Clone)] pub enum PgpArmor { None, Message, Pubkey, Signature, SignedMessage, File, Privkey, Seckey, } impl From for c_int { fn from(a: PgpArmor) -> c_int { match a { PgpArmor::None => 0, PgpArmor::Message => 1, PgpArmor::Pubkey => 2, PgpArmor::Signature => 3, PgpArmor::SignedMessage => 4, PgpArmor::File => 5, PgpArmor::Privkey => 6, PgpArmor::Seckey => 7, } } } impl From for PgpArmor { fn from(a: c_int) -> PgpArmor { match a { 0 => PgpArmor::None, 1 => PgpArmor::Message, 2 => PgpArmor::Pubkey, 3 => PgpArmor::Signature, 4 => PgpArmor::SignedMessage, 5 => PgpArmor::File, 6 => PgpArmor::Privkey, 7 => PgpArmor::Seckey, _ => PgpArmor::None, } } } impl TryFrom for armor::Kind { type Error = Error; fn try_from(a: PgpArmor) -> Result { let err = || Err(Error::Fail(format!("Unsupported armor type: {:?}", a))); match a { PgpArmor::None => err(), PgpArmor::Message => Ok(armor::Kind::Message), PgpArmor::Pubkey => Ok(armor::Kind::PublicKey), PgpArmor::Signature => Ok(armor::Kind::Signature), PgpArmor::SignedMessage => err(), PgpArmor::File => Ok(armor::Kind::File), PgpArmor::Privkey => err(), PgpArmor::Seckey => Ok(armor::Kind::SecretKey), } } } impl From> for PgpArmor { fn from(k: Option) -> PgpArmor { use armor::Kind::*; match k { None => PgpArmor::None, Some(Message) => PgpArmor::Message, Some(PublicKey) => PgpArmor::Pubkey, Some(SecretKey) => PgpArmor::Seckey, // XXX: PgpArmor::Privkey Some(Signature) => PgpArmor::Signature, // XXX: PgpArmor::SignedMessage Some(File) => PgpArmor::File, _ => PgpArmor::File, // XXX } } } rpm-sequoia-1.10.2/src/symbols.txt000064400000000000000000000020161046102023000151660ustar 00000000000000# #include # Implemented by rpmpgp.c. # pgpValString _pgpPubkeyFingerprint _pgpPubkeyKeyID _pgpPrtParams _pgpPrtParams2 _pgpPrtParamsSubkeys _pgpPrtPkts # Implemented by rpmpgp.c. # pgpReadPkts _pgpParsePkts _pgpPubKeyCertLen _pgpPubKeyLint _pgpArmorWrap _pgpNewDig _pgpCleanDig _pgpFreeDig _pgpDigGetParams _pgpDigParamsCmp _pgpDigParamsAlgo _pgpDigParamsSignID _pgpDigParamsUserID _pgpDigParamsVersion _pgpDigParamsCreationTime _pgpDigParamsSalt _pgpDigParamsFree _pgpPubkeyMerge _pgpVerifySignature _pgpVerifySignature2 _pgpVerifySig _pgpSignatureType # Implemented by rpmpgp.c. # pgpIdentItem # #include _rpmInitCrypto _rpmFreeCrypto _rpmDigestDup _rpmDigestLength _rpmDigestInit _rpmDigestUpdate _rpmDigestFinal # These are implemented in terms of the above. # # rpmDigestBundleFree # rpmDigestBundleAdd # rpmDigestBundleAddID # rpmDigestBundleUpdate # rpmDigestBundleDupCtx # Threse symbols are exposed by Rust :/ # See: https://gitlab.com/sequoia-pgp/rpm-sequoia/-/issues/3 ?rust_eh_personality rpm-sequoia-1.10.2/tests/Dockerfile000064400000000000000000000003011046102023000152750ustar 00000000000000FROM rpm-tests as src MAINTAINER jakuje@gmail.com WORKDIR /srv/rpm COPY /target/debug/librpm_sequoia.so /usr/local/lib64/ COPY /target/debug/librpm_sequoia.so.1 /usr/local/lib64/ RUN ldconfig rpm-sequoia-1.10.2/tests/symbols.rs000064400000000000000000000103071046102023000153500ustar 00000000000000use std::env; use std::fs::File; use std::io::Read; use std::path::PathBuf; use assert_cmd::Command; use assert_cmd::assert::OutputAssertExt; #[test] fn symbols() -> anyhow::Result<()> { // Make sure the library is built. let skip_build_library = env::var("TEST_DONT_BUILD_LIB").is_ok(); if ! skip_build_library { Command::new("cargo").arg("build").ok()?; } // We want the location of the build directory (e.g., // `/tmp/rpm-sequoia/debug`). // // OUT_DIR gives us // `/tmp/rpm-sequoia/debug/build/rpm-sequoia-HASH/out`. let out_dir = PathBuf::from(env!("OUT_DIR")); let mut build_dir = out_dir; let lib = loop { let mut lib = build_dir.clone(); lib.push("librpm_sequoia.so"); if lib.exists() { break lib; } if ! build_dir.pop() { panic!("Failed to find librpm_sequoia.so"); } }; let cmd = Command::new("objdump") .arg("-T") .arg(lib) .unwrap(); let assert = cmd.assert().success(); let output = String::from_utf8_lossy(&assert.get_output().stdout); let mut symbols = Vec::new(); for line in output.split("\n") { if line.contains("g DF .text") || line.contains("g DO .data") || line.contains("g DF .opd") { let symbol = line.split(' ').last().expect("a word"); symbols.push(symbol); } } symbols.sort(); eprintln!("Found {} symbols:", symbols.len()); for symbol in symbols.iter() { eprintln!(" {}", symbol); } let mut expected_symbols_txt_fn = PathBuf::from(env!("CARGO_MANIFEST_DIR")); expected_symbols_txt_fn.push("src/symbols.txt"); let mut expected_symbols_txt = Vec::new(); File::open(expected_symbols_txt_fn) .expect("src/symbols.txt exists") .read_to_end(&mut expected_symbols_txt) .unwrap(); let expected_symbols_txt = String::from_utf8_lossy(&expected_symbols_txt); let mut expected_symbols = Vec::new(); for symbol in expected_symbols_txt.split("\n") { if symbol.starts_with("#") { continue; } let symbol = symbol.trim(); if symbol.is_empty() { continue; } if symbol.chars().nth(0) == Some('?') { expected_symbols.push((&symbol[1..], true)); } else { expected_symbols.push((symbol, false)); } } expected_symbols.sort(); eprintln!("Expected {} symbols:", expected_symbols.len()); for (symbol, optional) in expected_symbols.iter() { eprint!(" {}", symbol); if *optional { eprintln!(" (optional)"); } else { eprintln!(""); } } let mut i = 0; let mut j = 0; let mut bad = false; loop { if i == symbols.len() && j == expected_symbols.len() { break; } if i < symbols.len() && j < expected_symbols.len() && symbols[i] == expected_symbols[j].0 { i += 1; j += 1; } else if (i < symbols.len() && j < expected_symbols.len() && symbols[i] < expected_symbols[j].0) || j == expected_symbols.len() { eprintln!("Found unexpected symbol {}", symbols[i]); if symbols[i] == "bz_internal_error" { eprintln!(" It looks like you forgot to disable compression.") } i += 1; bad = true; } else if (i < symbols.len() && j < expected_symbols.len() && symbols[i] > expected_symbols[j].0) || i == symbols.len() { if ! expected_symbols[j].1 { eprintln!("Missing expected symbol {}", expected_symbols[j].0); bad = true; } j += 1; } else { unreachable!(); } } if bad { eprintln!("\ *** If you see unexpected symbols like SHA1DCInit..., \ then you need version 0.2.6 or later of \ sha1collisiondetection. ***"); Err(anyhow::anyhow!("symbol mismatch")) } else { Ok(()) } }