sequoia-octopus-librnp-1.11.1/.cargo_vcs_info.json0000644000000001360000000000100155410ustar { "git": { "sha1": "2c903a4df4366ba3bbfcccd29cca68fe67735b8f" }, "path_in_vcs": "" }sequoia-octopus-librnp-1.11.1/.gitignore000064400000000000000000000000461046102023000163210ustar 00000000000000/target /.dir-locals.el /.gdb_history sequoia-octopus-librnp-1.11.1/.gitlab-ci.yml000064400000000000000000000227261046102023000167760ustar 00000000000000# Only ever create pipelines for tags or branches. # Avoid creation of detached pipelines for merge requests. workflow: rules: - if: $CI_COMMIT_TAG - if: $CI_COMMIT_BRANCH stages: - pre-check - cargo_test - tb_test - release before_script: - if [ -d $CARGO_TARGET_DIR ]; then find $CARGO_TARGET_DIR | wc --lines; du -sh $CARGO_TARGET_DIR; fi - if [ -d $CARGO_HOME ]; then find $CARGO_HOME | wc --lines; du -sh $CARGO_HOME; fi - rustc --version - cargo --version - clang --version after_script: - find $CARGO_TARGET_DIR -type f -atime +7 -delete - du -sh $CARGO_TARGET_DIR - du -sh $CARGO_HOME trixie: tags: - linux stage: cargo_test image: jampot.sequoia-pgp.org/sequoia-pgp/build-docker-image/trixie:latest dependencies: - codespell script: - RUST_BACKTRACE=1 cargo test - if ! git diff --quiet Cargo.lock ; then echo "Cargo.lock changed. Please add the change to the corresponding commit." ; false ; fi - SEQUOIA_OCTOPUS_DISABLE_TRACING=1 cargo build - cp $CARGO_TARGET_DIR/debug/libsequoia_octopus_librnp.so libsequoia_octopus_librnp.so - NEEDLESSLY_EXPORTED_SYMBOLS="$(readelf --dyn-syms libsequoia_octopus_librnp.so | grep -v UND | grep -v rnp_ | grep DEFAULT)" - echo "$NEEDLESSLY_EXPORTED_SYMBOLS" - if [ "$(echo "$NEEDLESSLY_EXPORTED_SYMBOLS" | wc --lines)" -gt 1 ]; then echo "Regression in the number of needlessly exported symbols." >&2 ; false ; fi variables: CARGO_TARGET_DIR: $CI_PROJECT_DIR/../target.$CI_CONCURRENT_ID.trixie RUSTFLAGS: -D warnings -A unused-parens artifacts: paths: [libsequoia_octopus_librnp.so] codespell: tags: - linux stage: pre-check image: jampot.sequoia-pgp.org/sequoia-pgp/build-docker-image/trixie:latest before_script: - codespell --version script: - > codespell --disable-colors -L "crate,ede,iff,mut,nd,te,uint,KeyServer,keyserver,Keyserver,keyservers,Keyservers,keypair,keypairs,KeyPair,fpr,dedup,tru" -S "*.bin,*.gpg,*.pgp,./.git,data,highlight.js,*/target,Makefile,Cargo.lock" after_script: [] deny: tags: - linux stage: pre-check image: jampot.sequoia-pgp.org/sequoia-pgp/build-docker-image/rust-stable:latest before_script: - curl -o /tmp/deny.toml "https://gitlab.com/sequoia-pgp/common-ci/-/raw/main/deny.toml" - cargo deny --version script: - cargo deny --color always check --config /tmp/deny.toml after_script: [] windows-gnu: tags: - win - win2019 stage: cargo_test image: jampot.sequoia-pgp.org/sequoia-pgp/build-docker-image/windows-gnu before_script: - clang -v - rustup default stable - rustup show - rustc --version --verbose - cargo --version script: - cargo build --no-default-features --features crypto-cng - cargo test --no-default-features --features crypto-cng after_script: [] variables: CFLAGS: "" # Silence some C warnings when compiling under Windows windows-msvc: tags: - win - win2019 stage: cargo_test image: jampot.sequoia-pgp.org/sequoia-pgp/build-docker-image/windows-msvc before_script: - rustup default stable-x86_64-pc-windows-msvc - rustup show - rustc --version --verbose - cargo --version script: - cargo build --no-default-features --features crypto-cng - cargo test --no-default-features --features crypto-cng after_script: [] variables: CFLAGS: "" # Silence some C warnings when compiling with MSVC VCPKG_ROOT: "C:\\src\\vcpkg" windows-msvc-32: tags: - win - win2019 stage: cargo_test image: name: jampot.sequoia-pgp.org/sequoia-pgp/build-docker-image/windows-msvc # Set up a cross compilation environment for building x86 binaries on amd64, line copied from Dockerfile.windows.msvc # see https://renenyffenegger.ch/notes/Windows/dirs/Program-Files-x86/Microsoft-Visual-Studio/version/edition/Common7/Tools/VsDevCmd_bat # # Alternatively: ["C:\\BuildTools\\VC\\Auxiliary\\Build\\vcvarsamd64_x86.bat", "&&", "type", "README", "&&", "powershell.exe", "-NoLogo", "-ExecutionPolicy", "Bypass"] # see https://docs.microsoft.com/en-us/cpp/build/building-on-the-command-line?view=msvc-160 entrypoint: ["C:\\BuildTools\\Common7\\Tools\\VsDevCmd.bat", "-arch=x86", "-host_arch=amd64", "&&", "type", "README", "&&", "powershell.exe", "-NoLogo", "-ExecutionPolicy", "Bypass"] before_script: - rustup default stable-i686-pc-windows-msvc - rustup target add i686-pc-windows-msvc - rustup show - rustc --version --verbose - cargo --version script: - cargo build --no-default-features --features crypto-cng --target i686-pc-windows-msvc - cargo test --no-default-features --features crypto-cng --target i686-pc-windows-msvc after_script: [] variables: CFLAGS: "" # Silence some C warnings when compiling with MSVC VCPKG_ROOT: "C:\\src\\vcpkg" windows-msvc-release: tags: - win - win2019 rules: - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' needs: ["windows-msvc", "thunderbird-tests-beta", "thunderbird-tests-esr102"] stage: release image: jampot.sequoia-pgp.org/sequoia-pgp/build-docker-image/windows-msvc before_script: - rustup default stable-x86_64-pc-windows-msvc - rustup show - rustc --version --verbose - cargo --version script: - cargo build --release --no-default-features --features crypto-cng after_script: - Get-childItem target\release - Rename-Item target\release\sequoia_octopus_librnp.dll rnp.dll artifacts: name: "sequoia-octopus-librnp-64-bit-$CI_COMMIT_SHORT_SHA" paths: - C:\builds\sequoia-pgp\sequoia-octopus-librnp\target\release\rnp.dll variables: CFLAGS: "" # Silence some C warnings when compiling with MSVC VCPKG_ROOT: "C:\\src\\vcpkg" debug-windows-msvc-release: tags: - win - win2019 rules: - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' needs: ["windows-msvc", "thunderbird-tests-beta", "thunderbird-tests-esr102"] stage: release image: jampot.sequoia-pgp.org/sequoia-pgp/build-docker-image/windows-msvc before_script: - rustup default stable-x86_64-pc-windows-msvc - rustup show - rustc --version --verbose - cargo --version script: - cargo build --no-default-features --features crypto-cng after_script: - Get-childItem target\debug - Rename-Item target\debug\sequoia_octopus_librnp.dll rnp.dll artifacts: name: "sequoia-octopus-librnp-64-bit-$CI_COMMIT_SHORT_SHA-debug" paths: - C:\builds\sequoia-pgp\sequoia-octopus-librnp\target\debug\rnp.dll variables: CFLAGS: "" # Silence some C warnings when compiling with MSVC VCPKG_ROOT: "C:\\src\\vcpkg" windows-msvc-release-32: tags: - win - win2019 rules: - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH' needs: ["windows-msvc-32", "thunderbird-tests-beta", "thunderbird-tests-esr102"] stage: release image: name: jampot.sequoia-pgp.org/sequoia-pgp/build-docker-image/windows-msvc entrypoint: ["C:\\BuildTools\\Common7\\Tools\\VsDevCmd.bat", "-arch=x86", "-host_arch=amd64", "&&", "type", "README", "&&", "powershell.exe", "-NoLogo", "-ExecutionPolicy", "Bypass"] before_script: - rustup default stable-i686-pc-windows-msvc - rustup target add i686-pc-windows-msvc - rustup show - rustc --version --verbose - cargo --version script: - cargo build --release --no-default-features --features crypto-cng --target i686-pc-windows-msvc after_script: - Get-childItem -Path target\i686-pc-windows-msvc\ - Rename-Item target\i686-pc-windows-msvc\release\sequoia_octopus_librnp.dll rnp.dll artifacts: name: "sequoia-octopus-librnp-32-bit-$CI_COMMIT_SHORT_SHA" paths: - C:\builds\sequoia-pgp\sequoia-octopus-librnp\target\i686-pc-windows-msvc\release\rnp.dll variables: CFLAGS: "" # Silence some C warnings when compiling with MSVC .thunderbird-tests: stage: tb_test needs: ["trixie"] before_script: - if [ -d $CARGO_TARGET_DIR ]; then find $CARGO_TARGET_DIR | wc --lines; du -sh $CARGO_TARGET_DIR; fi - if [ -d $CARGO_HOME ]; then find $CARGO_HOME | wc --lines; du -sh $CARGO_HOME; fi - cargo --version - clang --version - sh -c 'cd /source/; hg summary' - sh -c 'cd /source/comm; hg summary' script: - mv -f libsequoia_octopus_librnp.so /source/obj-artifact/dist/bin/librnp.so - ldd /source/obj-artifact/dist/bin/librnp.so - cd /source/ - dbus-run-session ./mach test --headless comm/mail/extensions/openpgp/test mail/test/browser/openpgp after_script: [] tags: - self-hosted variables: CARGO_TARGET_DIR: $CI_PROJECT_DIR/../target.$CI_CONCURRENT_ID.trixie RUSTFLAGS: -D warnings -A unused-parens # We're pulling in the artifact from the build job, no need for the code. GIT_STRATEGY: none thunderbird-tests-beta: extends: .thunderbird-tests image: jampot.sequoia-pgp.org/sequoia-pgp/build-thunderbird/tbbuild_beta:for_ci thunderbird-tests-esr102: extends: .thunderbird-tests image: jampot.sequoia-pgp.org/sequoia-pgp/build-thunderbird/tbbuild_esr102:for_ci thunderbird-tests-esr115: extends: .thunderbird-tests image: jampot.sequoia-pgp.org/sequoia-pgp/build-thunderbird/tbbuild_esr115:for_ci thunderbird-tests-esr128: extends: .thunderbird-tests image: jampot.sequoia-pgp.org/sequoia-pgp/build-thunderbird/tbbuild_esr128:for_ci variables: DEBIAN_FRONTEND: noninteractive CARGO_HOME: $CI_PROJECT_DIR/../cargo CARGO_FLAGS: --color always CARGO_INCREMENTAL: 0 RUST_BACKTRACE: full RUSTFLAGS: -D warnings CFLAGS: -Werror QUICKCHECK_GENERATOR_SIZE: 500 # https://github.com/BurntSushi/quickcheck/pull/240 sequoia-octopus-librnp-1.11.1/Cargo.lock0000644000003043030000000000100135170ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "addr2line" version = "0.24.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" dependencies = [ "gimli", ] [[package]] name = "adler2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aead" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ "crypto-common", "generic-array", ] [[package]] name = "aes" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", "cpufeatures", ] [[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 = "ahash" version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" dependencies = [ "cfg-if", "once_cell", "version_check", "zerocopy 0.7.35", ] [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[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 = "anyhow" version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" [[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 = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" dependencies = [ "term 0.7.0", ] [[package]] name = "ascii-canvas" version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef1e3e699d84ab1b0911a1010c5c106aa34ae89aeac103be5ce0c3859db1e891" dependencies = [ "term 1.0.1", ] [[package]] name = "async-trait" version = "0.1.87" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d556ec1359574147ec0c4fc5eb525f3f23263a592b1a9c07e0a75b427de55c97" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "backtrace" version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", "windows-targets 0.52.6", ] [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bindgen" version = "0.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f49d8fed880d473ea71efb9bf597651e77201bdd4893efe54c9e5d65ae04ce6f" dependencies = [ "bitflags", "cexpr", "clang-sys", "itertools 0.13.0", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", "syn", ] [[package]] name = "bit-set" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" dependencies = [ "bit-vec 0.6.3", ] [[package]] name = "bit-set" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ "bit-vec 0.8.0", ] [[package]] name = "bit-vec" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" [[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.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "blake2" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ "digest", ] [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "block-padding" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" dependencies = [ "generic-array", ] [[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 = "buffered-reader" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db26bf1f092fd5e05b5ab3be2f290915aeb6f3f20c4e9f86ce0f07f336c2412f" dependencies = [ "bzip2", "flate2", "libc", ] [[package]] name = "bumpalo" version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "bzip2" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49ecfb22d906f800d4fe833b6282cf4dc1c298f5057ca0b5445e5c209735ca47" dependencies = [ "bzip2-sys", ] [[package]] name = "bzip2-sys" version = "0.1.13+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "225bff33b2141874fe80d71e07d6eec4f85c5c216453dd96388240f96e1acc14" dependencies = [ "cc", "pkg-config", ] [[package]] name = "capnp" version = "0.19.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e985a566bdaae9a428a957d12b10c318d41b2afddb54cfbb764878059df636e" dependencies = [ "embedded-io", ] [[package]] name = "capnp-futures" version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8f3ee810b3890498e51028448ac732cdd5009223897124dd2fac6b085b5d867" dependencies = [ "capnp", "futures", ] [[package]] name = "capnp-rpc" version = "0.19.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fe57ab22a5e121e6fddaf36e837514aab9ae888bcff2baa6fda5630820dfc501" dependencies = [ "capnp", "capnp-futures", "futures", ] [[package]] name = "cc" version = "1.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c" dependencies = [ "jobserver", "libc", "shlex", ] [[package]] name = "cexpr" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" dependencies = [ "nom", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" version = "0.4.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c" dependencies = [ "android-tzdata", "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 = "configparser" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e57e3272f0190c3f1584272d613719ba5fc7df7f4942fe542e63d949cf3a649b" [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "core-foundation" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", ] [[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 = "crc32fast" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8" dependencies = [ "crossbeam-channel", "crossbeam-deque", "crossbeam-epoch", "crossbeam-queue", "crossbeam-utils", ] [[package]] name = "crossbeam-channel" version = "0.5.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-deque" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-queue" version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "rand_core", "typenum", ] [[package]] name = "csv" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" dependencies = [ "csv-core", "itoa", "ryu", "serde", ] [[package]] name = "csv-core" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d02f3b0da4c6504f86e9cd789d8dbafab48c2321be74e9987593de5a894d93d" dependencies = [ "memchr", ] [[package]] name = "ctor" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" dependencies = [ "quote", "syn", ] [[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 = "data-encoding" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "575f75dfd25738df5b91b8e43e14d44bda14637a58fae779fd2b064f8bf3e010" [[package]] name = "dbl" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd2735a791158376708f9347fe8faba9667589d82427ef3aed6794a8981de3d9" dependencies = [ "generic-array", ] [[package]] name = "der" version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "zeroize", ] [[package]] name = "deranged" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" dependencies = [ "powerfmt", ] [[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 = "dirs" version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" dependencies = [ "dirs-sys 0.4.1", ] [[package]] name = "dirs" version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ "dirs-sys 0.5.0", ] [[package]] name = "dirs-next" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" dependencies = [ "cfg-if", "dirs-sys-next", ] [[package]] name = "dirs-sys" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" dependencies = [ "libc", "option-ext", "redox_users 0.4.6", "windows-sys 0.48.0", ] [[package]] name = "dirs-sys" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", "redox_users 0.5.0", "windows-sys 0.59.0", ] [[package]] name = "dirs-sys-next" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" dependencies = [ "libc", "redox_users 0.4.6", "winapi", ] [[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.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" [[package]] name = "dyn-clone" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" [[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 = "ed25519" version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ "pkcs8", "signature", ] [[package]] name = "ed25519-dalek" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ "curve25519-dalek", "ed25519", "rand_core", "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 = "embedded-io" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" [[package]] name = "ena" version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d248bdd43ce613d87415282f69b9bb99d947d290b10962dd6c56233312c2ad5" dependencies = [ "log", ] [[package]] name = "encoding_rs" version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" dependencies = [ "cfg-if", ] [[package]] name = "endian-type" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" [[package]] name = "enum-as-inner" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" dependencies = [ "heck", "proc-macro2", "quote", "syn", ] [[package]] name = "enumber" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e94171909dd76d846c1ee9d14704de157cf77d01560c883f74ddd1f74c5bdbf" dependencies = [ "quote", "syn", ] [[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.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ "libc", "windows-sys 0.59.0", ] [[package]] name = "fallible-iterator" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fallible-streaming-iterator" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "fd-lock" version = "4.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" dependencies = [ "cfg-if", "rustix 1.0.2", "windows-sys 0.59.0", ] [[package]] name = "fiat-crypto" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "fixedbitset" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "fixedbitset" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11faaf5a5236997af9848be0bef4db95824b1d534ebc64d0f0c6cf3e67bd38dc" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "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 = "form_urlencoded" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "fs2" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213" dependencies = [ "libc", "winapi", ] [[package]] name = "futures" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ "futures-channel", "futures-core", "futures-executor", "futures-io", "futures-sink", "futures-task", "futures-util", ] [[package]] name = "futures-channel" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" [[package]] name = "futures-executor" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ "futures-core", "futures-task", "futures-util", ] [[package]] name = "futures-io" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" [[package]] name = "futures-macro" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "futures-sink" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-channel", "futures-core", "futures-io", "futures-macro", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "gethostname" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc3655aa6818d65bc620d6911f05aa7b6aeb596291e1e9f79e52df85583d1e30" dependencies = [ "rustix 0.38.44", "windows-targets 0.52.6", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", "wasm-bindgen", ] [[package]] name = "getrandom" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" dependencies = [ "cfg-if", "libc", "wasi 0.13.3+wasi-0.2.2", "windows-targets 0.52.6", ] [[package]] name = "ghash" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" dependencies = [ "opaque-debug", "polyval", ] [[package]] name = "gimli" version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] name = "git2" version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b903b73e45dc0c6c596f2d37eccece7c1c8bb6e4407b001096387c63d0d93724" dependencies = [ "bitflags", "libc", "libgit2-sys", "log", "url", ] [[package]] name = "glob" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "h2" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", "http", "indexmap", "slab", "tokio", "tokio-util", "tracing", ] [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", ] [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "hashlink" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" dependencies = [ "hashbrown 0.14.5", ] [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "hickory-client" version = "0.24.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "156579a5cd8d1fc6f0df87cc21b6ee870db978a163a1ba484acd98a4eff5a6de" dependencies = [ "cfg-if", "data-encoding", "futures-channel", "futures-util", "hickory-proto", "once_cell", "radix_trie", "rand", "thiserror 1.0.69", "tokio", "tracing", ] [[package]] name = "hickory-proto" version = "0.24.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92652067c9ce6f66ce53cc38d1169daa36e6e7eb7dd3b63b5103bd9d97117248" dependencies = [ "async-trait", "cfg-if", "data-encoding", "enum-as-inner", "futures-channel", "futures-io", "futures-util", "idna", "ipnet", "once_cell", "openssl", "rand", "thiserror 1.0.69", "tinyvec", "tokio", "tracing", "url", ] [[package]] name = "hickory-resolver" version = "0.24.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cbb117a1ca520e111743ab2f6688eddee69db4e0ea242545a604dce8a66fd22e" dependencies = [ "cfg-if", "futures-util", "hickory-proto", "ipconfig", "lru-cache", "once_cell", "parking_lot", "rand", "resolv-conf", "smallvec", "thiserror 1.0.69", "tokio", "tracing", ] [[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 = "home" version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "hostname" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" dependencies = [ "libc", "match_cfg", "winapi", ] [[package]] name = "http" version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" dependencies = [ "bytes", "fnv", "itoa", ] [[package]] name = "http-body" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", ] [[package]] name = "http-body-util" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", "http", "http-body", "pin-project-lite", ] [[package]] name = "httparse" version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" dependencies = [ "bytes", "futures-channel", "futures-util", "h2", "http", "http-body", "httparse", "itoa", "pin-project-lite", "smallvec", "tokio", "want", ] [[package]] name = "hyper-rustls" version = "0.27.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" dependencies = [ "futures-util", "http", "hyper", "hyper-util", "rustls", "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", ] [[package]] name = "hyper-tls" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", "hyper", "hyper-util", "native-tls", "tokio", "tokio-native-tls", "tower-service", ] [[package]] name = "hyper-util" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" dependencies = [ "bytes", "futures-channel", "futures-util", "http", "http-body", "hyper", "pin-project-lite", "socket2", "tokio", "tower-service", "tracing", ] [[package]] name = "iana-time-zone" version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "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 = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" dependencies = [ "displaydoc", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_locid" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" dependencies = [ "displaydoc", "litemap", "tinystr", "writeable", "zerovec", ] [[package]] name = "icu_locid_transform" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" dependencies = [ "displaydoc", "icu_locid", "icu_locid_transform_data", "icu_provider", "tinystr", "zerovec", ] [[package]] name = "icu_locid_transform_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" [[package]] name = "icu_normalizer" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" dependencies = [ "displaydoc", "icu_collections", "icu_normalizer_data", "icu_properties", "icu_provider", "smallvec", "utf16_iter", "utf8_iter", "write16", "zerovec", ] [[package]] name = "icu_normalizer_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" [[package]] name = "icu_properties" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" dependencies = [ "displaydoc", "icu_collections", "icu_locid_transform", "icu_properties_data", "icu_provider", "tinystr", "zerovec", ] [[package]] name = "icu_properties_data" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" [[package]] name = "icu_provider" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" dependencies = [ "displaydoc", "icu_locid", "icu_provider_macros", "stable_deref_trait", "tinystr", "writeable", "yoke", "zerofrom", "zerovec", ] [[package]] name = "icu_provider_macros" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "idna" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" dependencies = [ "idna_adapter", "smallvec", "utf8_iter", ] [[package]] name = "idna_adapter" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" dependencies = [ "icu_normalizer", "icu_properties", ] [[package]] name = "indexmap" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" dependencies = [ "equivalent", "hashbrown 0.15.2", ] [[package]] name = "inout" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ "block-padding", "generic-array", ] [[package]] name = "io-uring" version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b86e202f00093dcba4275d4636b93ef9dd75d025ae560d2521b45ea28ab49013" dependencies = [ "bitflags", "cfg-if", "libc", ] [[package]] name = "ipconfig" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b58db92f96b720de98181bbbe63c831e87005ab460c1bf306eb2622b4707997f" dependencies = [ "socket2", "widestring", "windows-sys 0.48.0", "winreg", ] [[package]] name = "ipnet" version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" [[package]] name = "itertools" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" dependencies = [ "either", ] [[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.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jobserver" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" dependencies = [ "libc", ] [[package]] name = "js-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "keccak" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ "cpufeatures", ] [[package]] name = "lalrpop" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cb077ad656299f160924eb2912aa147d7339ea7d69e1b5517326fdcec3c1ca" dependencies = [ "ascii-canvas 3.0.0", "bit-set 0.5.3", "ena", "itertools 0.11.0", "lalrpop-util 0.20.2", "petgraph 0.6.5", "regex", "regex-syntax", "string_cache", "term 0.7.0", "tiny-keccak", "unicode-xid", "walkdir", ] [[package]] name = "lalrpop" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7047a26de42016abf8f181b46b398aef0b77ad46711df41847f6ed869a2a1d5b" dependencies = [ "ascii-canvas 4.0.0", "bit-set 0.8.0", "ena", "itertools 0.14.0", "lalrpop-util 0.22.1", "petgraph 0.7.1", "regex", "regex-syntax", "sha3", "string_cache", "term 1.0.1", "unicode-xid", "walkdir", ] [[package]] name = "lalrpop-util" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "507460a910eb7b32ee961886ff48539633b788a36b65692b95f225b844c82553" dependencies = [ "regex-automata", ] [[package]] name = "lalrpop-util" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8d05b3fe34b8bd562c338db725dfa9beb9451a48f65f129ccb9538b48d2c93b" 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 = "libc" version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "libgit2-sys" version = "0.17.0+1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10472326a8a6477c3c20a64547b0059e4b0d086869eee31e6d7da728a8eb7224" dependencies = [ "cc", "libc", "libz-sys", "pkg-config", ] [[package]] name = "libloading" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", "windows-targets 0.48.5", ] [[package]] name = "libm" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "libredox" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" dependencies = [ "bitflags", "libc", ] [[package]] name = "libsqlite3-sys" version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c10584274047cb335c23d3e61bcef8e323adae7c5c8c760540f73610177fc3f" dependencies = [ "cc", "pkg-config", "vcpkg", ] [[package]] name = "libz-sys" version = "1.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df9b68e50e6e0b26f672573834882eb57759f6db9b3be2ea3c35c91188bb4eaa" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "linked-hash-map" version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" [[package]] name = "linux-raw-sys" version = "0.4.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" [[package]] name = "linux-raw-sys" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db9c683daf087dc577b7506e9695b3d556a9f3849903fa28186283afd6809e9" [[package]] name = "litemap" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "643cb0b8d4fcc284004d5fd0d67ccf61dfffadb7f75e1e71bc420f4688a3a704" [[package]] name = "lock_api" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" [[package]] name = "lru-cache" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" dependencies = [ "linked-hash-map", ] [[package]] name = "match_cfg" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memsec" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c797b9d6bb23aab2fc369c65f871be49214f5c759af65bde26ffaaa2b646b492" [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5" dependencies = [ "adler2", ] [[package]] name = "mio" version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" dependencies = [ "libc", "wasi 0.11.0+wasi-snapshot-preview1", "windows-sys 0.52.0", ] [[package]] name = "native-tls" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c" dependencies = [ "libc", "log", "openssl", "openssl-probe", "openssl-sys", "schannel", "security-framework", "security-framework-sys", "tempfile", ] [[package]] name = "nettle" version = "7.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44e6ff4a94e5d34a1fd5abbd39418074646e2fa51b257198701330f22fcd6936" dependencies = [ "getrandom 0.2.15", "libc", "nettle-sys", "thiserror 1.0.69", "typenum", ] [[package]] name = "nettle-sys" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61a3f5406064d310d59b1a219d3c5c9a49caf4047b6496032e3f930876488c34" 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 = "nibble_vec" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" dependencies = [ "smallvec", ] [[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.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" dependencies = [ "byteorder", "lazy_static", "libm", "num-integer", "num-iter", "num-traits", "smallvec", ] [[package]] name = "num-conv" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" [[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 = "num_cpus" version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" dependencies = [ "hermit-abi", "libc", ] [[package]] name = "num_threads" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c7398b9c8b70908f6371f47ed36737907c87c52af34c268fed0bf0ceb92ead9" dependencies = [ "libc", ] [[package]] name = "object" version = "0.36.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87" dependencies = [ "memchr", ] [[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.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cde51589ab56b20a6f686b2c68f7a0bd6add753d697abf720d63f8db3ab7b1ad" [[package]] name = "opaque-debug" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "openpgp-cert-d" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd47b0b6df1022ca8a9a06791261c3153028abef191fe53aa326b7f443f2d6" dependencies = [ "anyhow", "dirs 6.0.0", "fd-lock", "libc", "sha1collisiondetection", "sha2", "tempfile", "thiserror 2.0.12", "walkdir", ] [[package]] name = "openssl" version = "0.10.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" 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-probe" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" version = "0.9.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90096e2e47630d78b7d1c20952dc621f957103f8bc2c8359ec81290d75238571" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "option-ext" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" [[package]] name = "parking_lot" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets 0.52.6", ] [[package]] name = "password-hash" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", "rand_core", "subtle", ] [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "petgraph" version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset 0.4.2", "indexmap", ] [[package]] name = "petgraph" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset 0.5.7", "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 = "pin-project-lite" version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkcs8" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der", "spki", ] [[package]] name = "pkg-config" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" [[package]] name = "polyval" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", "cpufeatures", "opaque-debug", "universal-hash", ] [[package]] name = "powerfmt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" [[package]] name = "ppv-lite86" version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" dependencies = [ "zerocopy 0.8.23", ] [[package]] name = "precomputed-hash" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "proc-macro2" version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] [[package]] name = "quick-error" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "radix_trie" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" dependencies = [ "endian-type", "nibble_vec", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom 0.2.15", ] [[package]] name = "rand_distr" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31" dependencies = [ "num-traits", "rand", ] [[package]] name = "rayon" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", ] [[package]] name = "rayon-core" version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] [[package]] name = "redox_syscall" version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1" dependencies = [ "bitflags", ] [[package]] name = "redox_users" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" dependencies = [ "getrandom 0.2.15", "libredox", "thiserror 1.0.69", ] [[package]] name = "redox_users" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" dependencies = [ "getrandom 0.2.15", "libredox", "thiserror 2.0.12", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" version = "0.12.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "989e327e510263980e231de548a33e63d34962d29ae61b467389a1a09627a254" dependencies = [ "base64", "bytes", "encoding_rs", "futures-core", "futures-util", "h2", "http", "http-body", "http-body-util", "hyper", "hyper-rustls", "hyper-tls", "hyper-util", "ipnet", "js-sys", "log", "mime", "native-tls", "once_cell", "percent-encoding", "pin-project-lite", "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "system-configuration", "tokio", "tokio-native-tls", "tower", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", "windows-registry", ] [[package]] name = "resolv-conf" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52e44394d2086d010551b14b53b1f24e31647570cd1deb0379e2c21b329aba00" dependencies = [ "hostname", "quick-error", ] [[package]] name = "ring" version = "0.17.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", "getrandom 0.2.15", "libc", "untrusted", "windows-sys 0.52.0", ] [[package]] name = "rusqlite" version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b838eba278d213a8beaf485bd313fd580ca4505a00d5871caeb1457c55322cae" dependencies = [ "bitflags", "fallible-iterator", "fallible-streaming-iterator", "hashlink", "libsqlite3-sys", "smallvec", ] [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustix" version = "0.38.44" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys 0.4.15", "windows-sys 0.59.0", ] [[package]] name = "rustix" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7178faa4b75a30e269c71e61c353ce2748cf3d76f0c44c393f4e60abf49b825" dependencies = [ "bitflags", "errno", "libc", "linux-raw-sys 0.9.2", "windows-sys 0.59.0", ] [[package]] name = "rustls" version = "0.23.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395" dependencies = [ "once_cell", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-pemfile" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ "rustls-pki-types", ] [[package]] name = "rustls-pki-types" version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" [[package]] name = "rustls-webpki" version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ "ring", "rustls-pki-types", "untrusted", ] [[package]] name = "rustversion" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "ryu" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[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 = "schannel" version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags", "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", ] [[package]] name = "security-framework-sys" version = "2.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "semver" version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0" [[package]] name = "sequoia-autocrypt" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11d65ff1c8589a3e505d36c8e1919483bf4a2d8d6eb65f84c3922fbdcae6928b" dependencies = [ "base64", "sequoia-openpgp", ] [[package]] name = "sequoia-cert-store" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc8987ed37e9931aee509c7ebc10e93b2ee5862849546c8a0c4588f3ed670b74" dependencies = [ "anyhow", "crossbeam", "dirs 5.0.1", "gethostname", "num_cpus", "openpgp-cert-d", "rayon", "rusqlite", "sequoia-openpgp", "smallvec", "thiserror 2.0.12", "url", ] [[package]] name = "sequoia-gpg-agent" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7f01803c82bdada34baa0f049e523c77b446ee347035df239a1f890c5d70c48" dependencies = [ "anyhow", "chrono", "futures", "lalrpop 0.22.1", "lalrpop-util 0.22.1", "libc", "sequoia-ipc", "sequoia-openpgp", "stfu8", "tempfile", "thiserror 2.0.12", "tokio", ] [[package]] name = "sequoia-ipc" version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c92579bbd37f62bbcc41e4dce7771fea395037bebaf9b8e10c20b765be8280ab" dependencies = [ "anyhow", "capnp-rpc", "ctor", "dirs 5.0.1", "fs2", "lalrpop 0.20.2", "lalrpop-util 0.20.2", "libc", "memsec", "rand", "sequoia-openpgp", "socket2", "tempfile", "thiserror 2.0.12", "tokio", "tokio-util", "winapi", ] [[package]] name = "sequoia-net" version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "956ef5d37e41f53259cd3c6caac5f135351ee92f76f3ac6ee9cf771ee6e33925" dependencies = [ "anyhow", "base64", "futures-util", "hickory-client", "hickory-resolver", "http", "hyper", "hyper-tls", "libc", "percent-encoding", "reqwest", "sequoia-openpgp", "thiserror 1.0.69", "tokio", "url", "z-base-32", ] [[package]] name = "sequoia-octopus-librnp" version = "1.11.1" dependencies = [ "anyhow", "chrono", "configparser", "csv", "enumber", "humantime", "libc", "num_cpus", "rand", "rand_distr", "rusqlite", "sequoia-autocrypt", "sequoia-gpg-agent", "sequoia-ipc", "sequoia-net", "sequoia-openpgp", "sequoia-openpgp-mt", "sequoia-policy-config", "sequoia-wot", "serde", "serde_json", "tempfile", "thiserror 1.0.69", "tokio", "vergen", ] [[package]] name = "sequoia-openpgp" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "015e5fc3d023418b9db98ca9a7f3e90b305872eeafe5ca45c5c32b5eb335c1e8" dependencies = [ "aes-gcm", "anyhow", "argon2", "base64", "botan", "buffered-reader", "bzip2", "chrono", "cipher", "dyn-clone", "eax", "ed25519", "ed25519-dalek", "flate2", "getrandom 0.2.15", "hkdf", "idna", "lalrpop 0.20.2", "lalrpop-util 0.20.2", "libc", "memsec", "nettle", "num-bigint-dig", "ocb3", "openssl", "openssl-sys", "rand_core", "regex", "regex-syntax", "sha1collisiondetection", "sha2", "thiserror 2.0.12", "win-crypto-ng", "winapi", "xxhash-rust", ] [[package]] name = "sequoia-openpgp-mt" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fb256a02274bc6cb6b6b7f514b6cbcffbd30c2dbeee1533de6e1c573cbec72a" dependencies = [ "anyhow", "buffered-reader", "num_cpus", "sequoia-openpgp", ] [[package]] name = "sequoia-policy-config" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e016b708d64857b6a97e1a331d9471b73e30ed450d247628e1a0ce236b1e597" dependencies = [ "anyhow", "chrono", "sequoia-openpgp", "serde", "thiserror 1.0.69", "toml", ] [[package]] name = "sequoia-wot" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7725fa3249ea6f786362408e7fc9ebd86e9250633991d97a2bd64d1197dce490" dependencies = [ "anyhow", "chrono", "crossbeam", "num_cpus", "sequoia-cert-store", "sequoia-openpgp", "thiserror 2.0.12", ] [[package]] name = "serde" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "serde_urlencoded" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", "itoa", "ryu", "serde", ] [[package]] name = "sha1collisiondetection" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f606421e4a6012877e893c399822a4ed4b089164c5969424e1b9d1e66e6964b" dependencies = [ "digest", "generic-array", ] [[package]] name = "sha2" version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "sha3" version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" 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 = [ "rand_core", ] [[package]] name = "siphasher" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" [[package]] name = "socket2" version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "spki" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", ] [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "stfu8" version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e51f1e89f093f99e7432c491c382b88a6860a5adbe6bf02574bf0a08efff1978" [[package]] name = "string_cache" version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "938d512196766101d333398efde81bc1f37b00cb42c2f8350e5df639f040bbbe" 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.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" dependencies = [ "futures-core", ] [[package]] name = "synstructure" version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "system-configuration" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" dependencies = [ "bitflags", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "tempfile" version = "3.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c317e0a526ee6120d8dabad239c8dadca62b24b6f168914bbbc8e2fb1f0e567" dependencies = [ "cfg-if", "fastrand", "getrandom 0.3.1", "once_cell", "rustix 1.0.2", "windows-sys 0.59.0", ] [[package]] name = "term" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" dependencies = [ "dirs-next", "rustversion", "winapi", ] [[package]] name = "term" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3bb6001afcea98122260987f8b7b5da969ecad46dbf0b5453702f776b491a41" dependencies = [ "home", "windows-sys 0.52.0", ] [[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.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ "thiserror-impl 2.0.12", ] [[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.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "time" version = "0.3.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dad298b01a40a23aac4580b67e3dbedb7cc8402f3592d7f49469de2ea4aecdd8" dependencies = [ "deranged", "itoa", "libc", "num-conv", "num_threads", "powerfmt", "serde", "time-core", "time-macros", ] [[package]] name = "time-core" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "765c97a5b985b7c11d7bc27fa927dc4fe6af3a6dfb021d28deb60d3bf51e76ef" [[package]] name = "time-macros" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8093bc3e81c3bc5f7879de09619d06c9a5a5e45ca44dfeeb7225bae38005c5c" dependencies = [ "num-conv", "time-core", ] [[package]] name = "tiny-keccak" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" dependencies = [ "crunchy", ] [[package]] name = "tinystr" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" dependencies = [ "displaydoc", "zerovec", ] [[package]] name = "tinyvec" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" version = "1.46.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cc3a2344dafbe23a245241fe8b09735b521110d30fcefbbd5feb1797ca35d17" dependencies = [ "backtrace", "bytes", "io-uring", "libc", "mio", "pin-project-lite", "slab", "socket2", "tokio-macros", "windows-sys 0.52.0", ] [[package]] name = "tokio-macros" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tokio-native-tls" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", ] [[package]] name = "tokio-rustls" version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" dependencies = [ "rustls", "tokio", ] [[package]] name = "tokio-util" version = "0.7.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7fcaa8d55a2bdd6b83ace262b016eca0d79ee02818c5c1bcdf0305114081078" dependencies = [ "bytes", "futures-core", "futures-io", "futures-sink", "pin-project-lite", "tokio", ] [[package]] name = "toml" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" dependencies = [ "serde", ] [[package]] name = "tower" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" dependencies = [ "futures-core", "futures-util", "pin-project-lite", "sync_wrapper", "tokio", "tower-layer", "tower-service", ] [[package]] name = "tower-layer" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ "pin-project-lite", "tracing-attributes", "tracing-core", ] [[package]] name = "tracing-attributes" version = "0.1.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tracing-core" version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ "once_cell", ] [[package]] name = "try-lock" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[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 = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] [[package]] name = "utf16_iter" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" [[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 = "vergen" version = "8.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2990d9ea5967266ea0ccf413a4aa5c42a93dbcfda9cb49a97de6931726b12566" dependencies = [ "anyhow", "cfg-if", "git2", "rustversion", "time", ] [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[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 = "want" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ "try-lock", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasi" version = "0.13.3+wasi-0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" dependencies = [ "wit-bindgen-rt", ] [[package]] name = "wasm-bindgen" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.50" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" dependencies = [ "cfg-if", "js-sys", "once_cell", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "widestring" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7219d36b6eac893fa81e84ebe06485e7dcbb616177469b142df14f1f4deb1311" [[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", "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.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys 0.48.0", ] [[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.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-link" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" [[package]] name = "windows-registry" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" dependencies = [ "windows-result", "windows-strings", "windows-targets 0.53.0", ] [[package]] name = "windows-result" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06374efe858fab7e4f881500e6e86ec8bc28f9462c47e5a9941a0142ad86b189" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" dependencies = [ "windows-link", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm 0.52.6", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows-targets" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" dependencies = [ "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", "windows_i686_gnullvm 0.53.0", "windows_i686_msvc 0.53.0", "windows_x86_64_gnu 0.53.0", "windows_x86_64_gnullvm 0.53.0", "windows_x86_64_msvc 0.53.0", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" [[package]] name = "windows_aarch64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_aarch64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" [[package]] name = "windows_i686_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" [[package]] name = "windows_i686_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_i686_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" [[package]] name = "windows_x86_64_gnu" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnu" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_gnullvm" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "windows_x86_64_msvc" version = "0.53.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" [[package]] name = "winreg" version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" dependencies = [ "cfg-if", "windows-sys 0.48.0", ] [[package]] name = "wit-bindgen-rt" version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" dependencies = [ "bitflags", ] [[package]] name = "write16" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" [[package]] name = "writeable" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" [[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.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" dependencies = [ "serde", "stable_deref_trait", "yoke-derive", "zerofrom", ] [[package]] name = "yoke-derive" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "z-base-32" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21bf7b4a78668416e1e8a332334e26fb2f377afe707f0c6feaf6ed5f9100133b" [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "zerocopy-derive 0.7.35", ] [[package]] name = "zerocopy" version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd97444d05a4328b90e75e503a34bad781f14e28a823ad3557f0750df1ebcbc6" dependencies = [ "zerocopy-derive 0.8.23", ] [[package]] name = "zerocopy-derive" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "zerocopy-derive" version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "zerofrom" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ec111ce797d0e0784a1116d0ddcdbea84322cd79e5d5ad173daeba4f93ab55" dependencies = [ "zerofrom-derive", ] [[package]] name = "zerofrom-derive" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" dependencies = [ "proc-macro2", "quote", "syn", "synstructure", ] [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" [[package]] name = "zerovec" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" dependencies = [ "yoke", "zerofrom", "zerovec-derive", ] [[package]] name = "zerovec-derive" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", "syn", ] sequoia-octopus-librnp-1.11.1/Cargo.toml0000644000000072220000000000100135420ustar # 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 = "sequoia-octopus-librnp" version = "1.11.1" authors = [ "Justus Winter ", "Neal H. Walfield ", "Nora Widdecke ", "Wiktor Kwapisiewicz ", ] build = "build.rs" autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Reimplementation of RNP's interface using Sequoia for use with Thunderbird" homepage = "https://sequoia-pgp.org/" readme = "README.md" keywords = [ "cryptography", "openpgp", "pgp", "encryption", "signing", ] categories = [ "cryptography", "authentication", "email", ] license = "LGPL-2.0-or-later" repository = "https://gitlab.com/sequoia-pgp/sequoia-octopus-librnp" [badges.gitlab] repository = "sequoia-pgp/sequoia-octopus-librnp" [badges.maintenance] status = "actively-developed" [features] crypto-botan = [ "sequoia-openpgp/compression", "sequoia-openpgp/crypto-botan", ] crypto-botan2 = [ "sequoia-openpgp/compression", "sequoia-openpgp/crypto-botan2", ] crypto-cng = [ "sequoia-openpgp/compression", "sequoia-openpgp/crypto-cng", ] crypto-nettle = [ "sequoia-openpgp/compression", "sequoia-openpgp/crypto-nettle", ] crypto-openssl = [ "sequoia-openpgp/compression", "sequoia-openpgp/crypto-openssl", ] default = ["sequoia-openpgp/default"] [lib] name = "sequoia_octopus_librnp" crate-type = ["cdylib"] path = "src/lib.rs" [dependencies.anyhow] version = "1" [dependencies.chrono] version = "0.4" [dependencies.configparser] version = ">=2, <4" [dependencies.csv] version = "1.1" [dependencies.enumber] version = "0.3" [dependencies.humantime] version = "2" [dependencies.libc] version = "0.2" [dependencies.num_cpus] version = "1" [dependencies.rand] version = "0.8" features = [ "std", "std_rng", ] default-features = false [dependencies.rand_distr] version = "0.4" default-features = false [dependencies.rusqlite] version = ">=0.24, <0.32" [dependencies.sequoia-autocrypt] version = "0.26" default-features = false [dependencies.sequoia-gpg-agent] version = "0.6" default-features = false [dependencies.sequoia-ipc] version = "0.36" default-features = false [dependencies.sequoia-net] version = "0.30" default-features = false [dependencies.sequoia-openpgp] version = "2" default-features = false [dependencies.sequoia-openpgp-mt] version = "0.2" default-features = false [dependencies.sequoia-policy-config] version = "0.8" default-features = false [dependencies.sequoia-wot] version = "0.14" default-features = false [dependencies.serde] version = "1" features = ["derive"] [dependencies.serde_json] version = "1" [dependencies.tempfile] version = "3.0" [dependencies.thiserror] version = ">=1, <3" [dependencies.tokio] version = "1" [build-dependencies] [target."cfg(not(windows))".build-dependencies.vergen] version = "8" features = [ "git", "git2", ] default-features = false [target."cfg(windows)".dependencies.rusqlite] version = ">=0.24, <0.32" features = ["bundled"] [target."cfg(windows)".build-dependencies.vergen] version = "8" features = [ "git", "gitcl", ] default-features = false sequoia-octopus-librnp-1.11.1/Cargo.toml.orig000064400000000000000000000055141046102023000172250ustar 00000000000000[package] name = "sequoia-octopus-librnp" description = "Reimplementation of RNP's interface using Sequoia for use with Thunderbird" version = "1.11.1" authors = [ "Justus Winter ", "Neal H. Walfield ", "Nora Widdecke ", "Wiktor Kwapisiewicz ", ] homepage = "https://sequoia-pgp.org/" repository = "https://gitlab.com/sequoia-pgp/sequoia-octopus-librnp" readme = "README.md" keywords = ["cryptography", "openpgp", "pgp", "encryption", "signing"] categories = ["cryptography", "authentication", "email"] license = "LGPL-2.0-or-later" edition = "2021" build = "build.rs" rust-version = "1.85" [badges] gitlab = { repository = "sequoia-pgp/sequoia-octopus-librnp" } maintenance = { status = "actively-developed" } [dependencies] libc = "0.2" anyhow = "1" chrono = "0.4" configparser = ">=2, <4" csv = "1.1" enumber = "0.3" humantime = "2" num_cpus = "1" rand = { version = "0.8", default-features = false, features = ["std", "std_rng"] } rand_distr = { version = "0.4", default-features = false } rusqlite = ">=0.24, <0.32" serde = { version = "1", features = ["derive"] } serde_json = "1" sequoia-openpgp = { version = "2", default-features = false } sequoia-openpgp-mt = { version = "0.2", default-features = false } sequoia-autocrypt = { version = "0.26", default-features = false } sequoia-gpg-agent = { version = "0.6", default-features = false } sequoia-net = { version = "0.30", default-features = false } sequoia-policy-config = { version = "0.8", default-features = false } sequoia-wot = { version = "0.14", default-features = false } tempfile = "3.0" thiserror = ">=1, <3" tokio = { version = "1" } sequoia-ipc = { version = "0.36", default-features = false } [target.'cfg(windows)'.dependencies] rusqlite = { version = ">=0.24, <0.32", features = ["bundled"] } [build-dependencies] [target.'cfg(windows)'.build-dependencies] # Use the git command line tool to get the version. # https://docs.rs/vergen/8.3.2/vergen/index.html vergen = { version = "8", default-features = false, features = ["git", "gitcl"] } [target.'cfg(not(windows))'.build-dependencies] # Use the git library to get the version. # https://docs.rs/vergen/8.3.2/vergen/index.html vergen = { version = "8", default-features = false, features = ["git", "git2"] } [lib] crate-type = ["cdylib"] [features] default = [ "sequoia-openpgp/default", ] crypto-openssl = [ "sequoia-openpgp/compression", "sequoia-openpgp/crypto-openssl", ] crypto-botan = [ "sequoia-openpgp/compression", "sequoia-openpgp/crypto-botan", ] crypto-botan2 = [ "sequoia-openpgp/compression", "sequoia-openpgp/crypto-botan2", ] crypto-nettle = [ "sequoia-openpgp/compression", "sequoia-openpgp/crypto-nettle", ] crypto-cng = [ "sequoia-openpgp/compression", "sequoia-openpgp/crypto-cng", ] sequoia-octopus-librnp-1.11.1/LICENSE.txt000064400000000000000000000627341046102023000161700ustar 00000000000000Sequoia PGP 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. Sequoia PGP 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! sequoia-octopus-librnp-1.11.1/NEWS000064400000000000000000000020141046102023000150250ustar 00000000000000 -*- org -*- #+TITLE: sequoia-octopus-librnp NEWS – history of user-visible changes #+STARTUP: content hidestars * Changes in 1.11.0 ** Notable changes - Ported to sequoia-openpgp 2. This brings in the machinery to support RFC9580, but without further changes to Thunderbird, it will not be able to make use of newer keys. It might be able to decrypt SEIPDv2 messages, though. * Changes in 1.10.0 ** Notable fixes - We now properly destroy background threads when a library context is freed. Previously, this lead to thread and resource leaks for the ephemeral contexts created by Thunderbird. ** Notable changes - If we don't support the RNP API version that Thunderbird requires, we now print a log message to stderr, which should be easier to discover than the message Thunderbird writes to the error console. - We use a more compact time representation in log messages now. ** New functionality - rnp_dearmor sequoia-octopus-librnp-1.11.1/README-Debian.md000064400000000000000000000024641046102023000167760ustar 00000000000000# Debian stable installation ### General On Debian stable it is not possible to build versions >= v1.2.1. We target Debian testing while developing, and thus the Rust toolchain shipped by Debian stable is often too old to compile current releases of the Octopus. You have two options to fix this. Either you install cargo and rustc in a current version from source [rust-lang.org](https://rust-lang.org/ "Rust Programming Language") Or you go the "debian way" and use the packages from the testing release. This is described below. ### APT preparation Copy your `sources.list` file to `/etc/apt/sources.list.d/testing.list` and edit as follow: ``` sudo cp /etc/apt/sources.list /etc/sources.list.d/testing.list sudo sed -i 's/stable/testing/g' /etc/apt/sources.list.d/testing.list ``` Add apt preferences (as root): ``` cat << EOF > /etc/apt/preferences.d/stable.pref Package: * Pin: release a=stable Pin-Priority: 900 EOF ``` ``` cat << EOF > /etc/apt/preferences.d/testing.pref Package: * Pin: release a=testing Pin-Priority: 400 EOF ``` ### Install Now, update sources lists and install cargo and rustc from testing. ``` sudo apt update sudo apt -t testing install rustc cargo ``` You're done. Now you can continue [here](https://gitlab.com/sequoia-pgp/sequoia-octopus-librnp#building-on-linux "Building on Linux") sequoia-octopus-librnp-1.11.1/README.md000064400000000000000000000603651046102023000156220ustar 00000000000000# A Sequoia-based OpenPGP Backend for Thunderbird This is a drop-in replacement for the [RNP] library supported and shipped by Thunderbird. [RNP]: https://github.com/rnpgp/rnp This project is called the Octopus, because octopuses can fit themselves in [unusual places], and in this project we reimplement the RNP API as used by Thunderbird using Sequoia. > [Octopuses'] soft bodies mean octopuses can fit into impossibly > small nooks and crannies, as long as the holes are not smaller than > the only hard parts of their bodies: their beaks. If all else fails, > octopuses can lose an arm to an attacker and regrow one later. [unusual places]: https://www.nationalgeographic.com/animals/invertebrates/facts/octopus-facts # A note of caution The Thunderbird developers do not support replacing [RNP], and have made it difficult to integrate our solution. We [regularly run] Thunderbird's tests using the Octopus, using the ESR91 and beta versions of Thunderbird. We are quite confident that the Octopus is safe to use, and offers at least the degree of security that you get with [RNP]. [regularly run]: https://gitlab.com/sequoia-pgp/sequoia-octopus-librnp/-/pipelines Nevertheless, it is a good idea to make regular backups of your Thunderbird profile and production keys (at least `pubring.gpg`, `secring.gpg`, and `openpgp.sqlite`) and know how to recover. ## Known limitations - There may be dragons. - When Thunderbird auto-updates, it overwrites the Octopus' version of `librnp.so` with its own version. This issue has been [reported to Thunderbird]. - Thunderbird reads and caches the available certificates on startup. Thus if you add a new certificate to gpg, even though the Octopus will load it, Thunderbird [will not notice it] until either you manually reimport the keys using Thunderbird's `OpenPGP Keyring Manager`, `File`, `Reload Key Cache`, or you restart Thunderbird. Note: Thunderbird only caches the list of keys and some meta-data, so it will normally take advantage of any updates inserted into the gpg keystore or fetched via Parcimonie. [reported to Thunderbird]: https://bugzilla.mozilla.org/show_bug.cgi?id=1698540 [will not notice it]: https://bugzilla.mozilla.org/show_bug.cgi?id=1703373 # Support The Octopus is actively maintained by the [Sequoia PGP] team. We will respond to issues and support requests, but we are not actively developing new features (however, the Octopus should have feature parity with Thunderbird's default backend). Our mandate is specifically to improve the OpenPGP ecosystem, and more generally to improve internet freedom tools. If you want to contribute financially to the Octopus development, or the Sequoia PGP project in general, please do get in touch. [Sequoia PGP]: https://sequoia-pgp.org/ In the past, we were primarily financed by the [pep foundation]. [pep foundation]: https://pep.foundation/ Currently, the Octopus is developed outside of Thunderbird. The main reasons for this is that the Thunderbird developers do not want to invest resources in supporting a new OpenPGP backend after having recently invested in RNP. In terms of testing, the Octopus includes a number of unit tests. Our CI runs these checks against all commits on Debian and Windows. It also runs the OpenPGP relevant [unit tests from Thunderbird] on Debian. [unit tests from Thunderbird]: https://gitlab.com/sequoia-pgp/sequoia-octopus-librnp/-/merge_requests/18 ## Building & Installing ### Using official Linux distribution packages #### Fedora The Octopus is available from the standard **Fedora** repositories since Fedora 34. The official Thunderbird package has also been adapted to allow users to choose which of the available PGP backends is used. If the `thunderbird` package is already installed (and at least at version 91), then swapping the RNP backend with the Octopus can be done by running the following command: ```sh sudo dnf swap thunderbird-librnp-rnp sequoia-octopus-librnp ``` Switching back to the default backend works the same way, only with reversed arguments for the `dnf swap` command: ```sh sudo dnf swap sequoia-octopus-librnp thunderbird-librnp-rnp ``` If Thunderbird is not yet installed, then the preferred OpenPGP backend can be specified at install time, as well: ```sh sudo dnf install thunderbird sequoia-octopus-librnp ``` #### openSUSE The package `sequoia-octopus-librnp` is available in openSUSE Tumbleweed. Like in Fedora, this package is replacing the default library shipped in `MozillaThunderbird-openpgp-librnp`. To install `sequoia-octopus-librnp` run the command ```sh sudo zypper install sequoia-octopus-librnp ``` and resolve the conflict by selecting to uninstall `MozillaThunderbird-openpgp-librnp`. Switching back is done in a similar way: ```sh sudo zypper install MozillaThunderbird-openpgp-librnp ``` and resolve the conflict by selecting to uninstall `sequoia-octopus-librnp`. If Thunderbird is not yet installed, then the preferred OpenPGP backend can be specified at install time, as well: ```sh sudo zypper install MozillaThunderbird sequoia-octopus-librnp ``` ### Building manually First of all, you will need to [install Sequoia's build dependencies]. Then, building the Octopus is only a matter of invoking `cargo build`, optionally with the `--release` flag to build an optimized build suitable for end users. Non-release builds print debugging and tracing information to stderr. You may optionally select one of Sequoia's cryptographic backends, for example, on Windows you'll probably want to use the [Windows CNG] backend instead of the [Nettle] backend. The result of the build is a shared library (i.e. a `*.so` on Linux and *BSD, a `*.dll` on Windows, etc.). To use it, make your Thunderbird pick it up instead of the `librnp.so` that it ships, e.g. by replacing it. The details are dependent on your deployment, and this is something distributors need to figure out. This document will demonstrate how to replace RNP in a local build of Thunderbird. [install Sequoia's build dependencies]: https://gitlab.com/sequoia-pgp/sequoia#requirements-and-msrv [Windows CNG]: https://docs.microsoft.com/en-us/windows/win32/seccng/cng-portal [Nettle]: http://www.lysator.liu.se/~nisse/nettle/ Note: the Octopus is fully backwards compatible with RNP. In particular, it uses the same format for storing keys and stores them in the same way. Thus, if you decide you don't like the Octopus, you can simply restore the original `librnp`, restart Thunderbird, and Thunderbird will work as before with all of your data. Of course, you should still make a backup (of at least `pubring.gpg`, `secring.gpg`, and `openpgp.sqlite`). #### Building on Linux To use a debug build, do the following: ```sh cargo build cp target/debug/libsequoia_octopus_librnp.so path/to/thunderbird/obj-x86_64-pc-linux-gnu/dist/bin/librnp.so ``` To use a release build, do the following: ```sh cargo build --release cp target/release/libsequoia_octopus_librnp.so path/to/thunderbird/obj-x86_64-pc-linux-gnu/dist/bin/librnp.so ``` On Debian stable it is not possible to build contemporary versions of the Octopus. You can follow [these instructions](README-Debian.md). #### Selecting a different cryptographic backend To select a different cryptographic backend, OpenSSL in this example, and create a debug build, do the following: ```sh cargo build --no-default-features --features crypto-openssl,net cp target/debug/libsequoia_octopus_librnp.so path/to/thunderbird/obj-x86_64-pc-linux-gnu/dist/bin/librnp.so ``` To use a release build, do the following: ```sh cargo build --no-default-features --features crypto-openssl,net --release cp target/release/libsequoia_octopus_librnp.so path/to/thunderbird/obj-x86_64-pc-linux-gnu/dist/bin/librnp.so ``` Please see https://gitlab.com/sequoia-pgp/sequoia/-/tree/main/openpgp#crypto-backends for a description of the available backends. #### Building on Windows (MSVC) There still exist some 32-bit installations of Thunderbird, even on 64-bit Windows. The bitness of Thunderbird and the Octopus *must* match, so please check with "About Thunderbird". The MSVC toolchain only supports the Windows CNG cryptographic backend. The following recipes create debug builds. To create a release build, add `--release` after `cargo build` (`cargo build --release ...`). For a 64-bit build, run: ```sh rustup target add x86_64-pc-windows-msvc cargo build --no-default-features --features crypto-cng,net --target x86_64-pc-windows-msvc cp target/x86_64-pc-windows-msvc/debug/sequoia_octopus_librnp.dll "C:\Program Files\Mozilla Thunderbird\rnp.dll" ``` For a 32-bit build, run: ```sh rustup target add i686-pc-windows-msvc cargo build --no-default-features --features crypto-cng --target i686-pc-windows-msvc cp target/i686-pc-windows-msvc/debug/sequoia_octopus_librnp.dll "C:\Program Files (x86)\Mozilla Thunderbird\rnp.dll" ``` #### Building on Windows (MSYS2) There still exist some 32-bit installations of Thunderbird, even on 64-bit Windows. The bitness of Thunderbird and the Octopus *must* match, so please check with "About Thunderbird". The MSYS2 toolchain supports both the Nettle and the Windows CNG cryptographic backend. The following recipes create debug builds. To create a release build, add `--release` after `cargo build` (`cargo build --release ...`). For a 64-bit build with CNG, run: ```sh rustup target add x86_64-pc-windows-gnu cargo build --no-default-features --features crypto-cng,net --target x86_64-pc-windows-gnu cp target/x86_64-pc-windows-gnu/debug/sequoia_octopus_librnp.dll "C:\Program Files\Mozilla Thunderbird\rnp.dll" ``` For a 32-bit build with CNG, run: ```sh rustup target add i686-pc-windows-gnu cargo build --no-default-features --features crypto-cng --target i686-pc-windows-gnu cp target/i686-pc-windows-gnu/debug/sequoia_octopus_librnp.dll "C:\Program Files (x86)\Mozilla Thunderbird\rnp.dll" ``` For a 64-bit build with Nettle, run: ```sh rustup target add x86_64-pc-windows-gnu cargo build --target x86_64-pc-windows-gnu cp target/x86_64-pc-windows-gnu/debug/sequoia_octopus_librnp.dll "C:\Program Files\Mozilla Thunderbird\rnp.dll" ``` For a 32-bit build with Nettle, run: ```sh rustup target add i686-pc-windows-gnu cargo build --target i686-pc-windows-gnu cp target/i686-pc-windows-gnu/debug/sequoia_octopus_librnp.dll "C:\Program Files (x86)\Mozilla Thunderbird\rnp.dll" ``` ### Using Debian's Thunderbird The following recipe was tested with version `1:115.5.0-1~deb12u1` of Thunderbird (from Debian Bookworm). This uses `dpkg`'s diversion mechanism to move `librnp.so` out of the way. ```sh $ sudo apt install thunderbird ... thunderbird is already the newest version (1:78.9.0-1~deb10u1). ... $ ls -l /usr/lib/thunderbird/librnp.so -rw-r--r-- 1 root root 723248 Mar 24 19:57 /usr/lib/thunderbird/librnp.so $ sudo dpkg-divert --divert /usr/lib/thunderbird/librnp-orig.so --rename /usr/lib/thunderbird/librnp.so Adding 'local diversion of /usr/lib/thunderbird/librnp.so to /usr/lib/thunderbird/librnp-orig.so' $ sudo cp .../target/release/libsequoia_octopus_librnp.so /usr/lib/thunderbird/ $ sudo ln -sf libsequoia_octopus_librnp.so /usr/lib/thunderbird/librnp.so $ ls -l /usr/lib/thunderbird/*rnp* -rw-r--r-- 1 root root 723248 Mar 24 19:57 /usr/lib/thunderbird/librnp-orig.so lrwxrwxrwx 1 root root 28 Apr 7 13:59 /usr/lib/thunderbird/librnp.so -> libsequoia_octopus_librnp.so -rwxr-xr-x 1 root root 129988280 Apr 7 14:01 /usr/lib/thunderbird/libsequoia_octopus_librnp.so ``` If you are using the debug version, then change 'release' to 'debug'. To uninstall the Octopus, you just need to remove the symlink and the diversion. ```sh $ sudo rm /usr/lib/thunderbird/librnp.so $ sudo dpkg-divert --rename --remove /usr/lib/thunderbird/librnp.so Removing 'local diversion of /usr/lib/thunderbird/librnp.so to /usr/lib/thunderbird/librnp-orig.so' $ ls -l /usr/lib/thunderbird/*rnp* -rw-r--r-- 1 root root 723248 Mar 24 19:57 /usr/lib/thunderbird/librnp.so -rwxr-xr-x 1 root root 129988280 Apr 7 14:01 /usr/lib/thunderbird/libsequoia_octopus_librnp.so ``` ### Precompiled Binaries for Windows If you are feeling particularly daring, you can download a precompiled `rnp.dll` for Windows: - [Octopus for 64-bit Thunderbird](https://gitlab.com/sequoia-pgp/sequoia-octopus-librnp/-/jobs/artifacts/main/download?job=windows-msvc-release) - [Octopus for 64-bit Thunderbird, debug version](https://gitlab.com/sequoia-pgp/sequoia-octopus-librnp/-/jobs/artifacts/main/download?job=debug-windows-msvc-release) - [Octopus for 32-bit Thunderbird](https://gitlab.com/sequoia-pgp/sequoia-octopus-librnp/-/jobs/artifacts/main/download?job=windows-msvc-release-32) These binaries are created by our CI. They correspond to the current version of the `main` branch. Before code is checked into the `main` branch, it must first pass our unit tests on both Debian and Windows, and it must past the Thunderbird unit tests on Debian. To use the Octopus, replace the `rnp.dll` binary in your Thunderbird installation. Thunderbird is usually installed to `C:\Program Files\Mozilla Thunderbird`. If you encounter any errors and wish to investigate, there is also a debug build available for 64-bit Thunderbird. Before starting Thunderbird with the Octopus, be sure to backup the files `pubring.gpg`, `secring.gpg`, and `openpgp.sqlite` in your Thunderbird profile. Your Thunderbird profile is usually located in `C:\Users\USERNAME\AppData\Roaming\Thunderbird\Profiles\PROFILEXXX.default`. # Additional Features This library includes a number of additional features that enhance Thunderbird. ## GnuPG Keyring Integration When Thunderbird starts, it asks RNP to parse its keyring. At this point, the Octopus also runs `gpg --export` and includes that in the results. This makes the user's GnuPG keyring available to Thunderbird. (From that point on the Octopus also monitors GnuPG's keystore for updates.) This can be disabled by setting the `GNUPGHOME` environment variable to `/dev/null` as follows: ```sh GNUPGHOME=/dev/null thunderbird ``` The certificates imported from GnuPG can be used as normal. It is possible to examine them in Thunderbird's OpenPGP Key Manager, set their "acceptance", etc. There are two known limitations. First, the first time an OpenPGP operation is performed, Thunderbird scans the keystore and creates an index of the available keys. Thunderbird [does not currently update this index] on its own. Thus, keys that are added to gpg's keystore will not be visible to Thunderbird until either the cache is manually flushed (`OpenPGP Keyring Manager`, `File`, `Reload Key Cache`), or you restart Thunderbird. Second, if you remove a certificate managed by GnuPG using the Thunderbird certificate manager, it will be removed from the in-memory keystore, but it is currently not actually removed from your gpg keystore. As such, it will reappear the next time Thunderbird loads the keyring. [does not currently update this index]: https://bugzilla.mozilla.org/show_bug.cgi?id=1703373 The Octopus carefully keeps track of what certificates were loaded from GnuPG and only writes them out to Thunderbird's keyring if they have been modified; modified certificates are not currently written back to GnuPG. But, [closer integration with GnuPG's keyring] is planned. [closer integration with GnuPG's keyring]: https://gitlab.com/sequoia-pgp/sequoia-octopus-librnp/-/issues/15 ## gpg agent Integration The Octopus automatically monitors what keys are loaded into gpg's agent, and reports to Thunderbird that secret key material is available for them. This means that it is trivial to mark a key managed by the agent as a personal key in Thunderbird [without modifying Thunderbird's configuration files]. Also, attempts to decrypt messages encrypted to a key managed by the agent are automatically forwarded to the agent for decryption. [without modifying Thunderbird's configuration files]: https://wiki.mozilla.org/Thunderbird:OpenPGP:Smartcards#Allow_the_use_of_external_GnuPG [Unlike Thunderbird] the Octopus talks directly to the agent. Thus, it is not necessary to install GPGME; you only need to have `gpg` in your `PATH`. [Unlike Thunderbird]: https://wiki.mozilla.org/Thunderbird:OpenPGP:Smartcards ## GnuPG's Web of Trust Data Thunderbird only supports a custom "acceptance" mechanism for authenticating OpenPGP certificate. Thunderbird ignores key signatures, and it is not possible to add [certification authorities]. [certification authorities]: https://openpgp-ca.org When Thunderbird starts up, the Octopus reads gpg's trust database, and merges it into Thunderbird's acceptance database. This means certificates that are considered authenticated by GnuPG are also considered authenticated by Thunderbird. This integration is done carefully. If a user has manually accepted a certificate in Thunderbird, that setting is not overridden. This happens not only during the initial import, but also later: when the user accepts a certificate in Thunderbird, the Octopus detects this and will no longer update that certificate's acceptance based on GnuPG's trust database. The Octopus monitors GnuPG for changes to its trust database. So, unlike when a new certificate is added to GnuPG's keystore, it is not necessary to restart Thunderbird to notice changes to the trust database. ## Parcimonie [Parcimonie] is a feature that automatically refreshes the user's OpenPGP certificates in the background using a number of privacy preserving techniques. In particular, updates are staggered, and the time between updates is drawn from a [memoryless distribution] to frustrate an attacker who wants to predict when a user will check for an update. Enigmail had its own version of this mechanism, but it was removed when Enigmail was integrated into Thunderbird. [Parcimonie]: https://github.com/EtiennePerot/parcimonie.sh [memoryless distribution]: https://en.wikipedia.org/wiki/Memorylessness The Parcimonie feature in the Octopus currently checks for updates on various OpenPGP key servers ([keys.openpgp.org], the [Proton keyserver], the [Mailvelope keyserver], and [keyserver.ubuntu.com]), and in the appropriate Web Key Directories ([WKDs]) using the aforementioned privacy preserving mechanisms. It checks for updates for all non-revoked, valid certificates about once a week, on average. It also supports merging updates from [User ID-less certificates]. [keys.openpgp.org]: https://keys.openpgp.org [Proton keyserver]: https://proton.me/blog/address-verification-pgp-support#:~:text=public%20key%20server [Mailvelope keyserver]: https://keys.mailvelope.com/ [keyserver.ubuntu.com]: https://keyserver.ubuntu.com/ [WKDs]: https://wiki.gnupg.org/WKD [User ID-less certificates]: https://bugzilla.mozilla.org/show_bug.cgi?id=1634524 Before importing a certificate, we first check if it appears to be [flooded]. If so, we strip third-party certifications from keys that we don't have a certificate for as those certifications are effectively useless. [flooded]: https://dkg.fifthhorseman.net/blog/openpgp-certificate-flooding.html ## Weak Cryptography The Octopus uses Sequoia, which [rejects cryptographic algorithms that are known to be weak by default]. Sequoia, and by extension, the Octopus, rejects certificates and messages that use weak cryptographic primitives. Because RNP does not have a mechanism to indicate that a certificate or component should not be used, the Octopus reports these keys as having expired one second after their creation time. [rejects cryptographic algorithms that are known to be weak by default]: https://docs.sequoia-pgp.org/sequoia_openpgp/policy/struct.StandardPolicy.html ## Honors system-wide crypto policy The Octopus reads a configuration file (by default `/etc/crypto-policies/back-ends/sequoia.config`) to configure its cryptographic policy. The file to load can be overridden using the `SEQUOIA_CRYPTO_POLICY` environment variable. For more information on the format, see [this document]. [this document]: https://docs.rs/sequoia-policy-config/latest/sequoia_policy_config/#format # Non-Functional Advantages Sequoia has a number of non-functional advantages relative to RNP. ## Keys Encrypted at Rest Sequoia automatically [encrypts unencrypted secret key material in memory] when it is not in use. This makes secret key exfiltration ala [Heartbleed] much harder, and protects against [Spectre], Rowhammer, etc.-style attacks. [OpenSSH] uses the same type of protection. RNP has the concept of locking and unlocking keys, but this is explicit, and Thunderbird does not always relock keys after use. [encrypts unencrypted secret key material in memory]: https://docs.sequoia-pgp.org/sequoia_openpgp/crypto/mem/struct.Encrypted.html [Heartbleed]: https://en.wikipedia.org/wiki/Heartbleed [Spectre]: https://en.wikipedia.org/wiki/Spectre_(security_vulnerability) [OpenSSH]: https://www.undeadly.org/cgi?action=article;sid=20190621081455 ## SHA-1 Mitigations [SHA-1 is broken]. Unfortunately, [SHA-1 is still widely used]. To deal with this Sequoia implements a number of countermeasures: - Sequoia uses [SHA1-CD], a variant of SHA-1 that detects and mitigates collision attacks. This protection is also used by [GitHub], among others. [SHA-1 is broken]: https://sha-mbles.github.io/ [SHA-1 is still widely used]: https://gitlab.com/sequoia-pgp/sequoia/-/issues/595 [SHA1-CD]: https://github.com/cr-marcstevens/sha1collisiondetection [GitHub]: https://github.blog/2017-03-20-sha-1-collision-detection-on-github-com/ ## Collision Protection Sequoia includes a salt in signatures and self-signatures to defend against collision attacks, among others. [OpenSSH does the same thing]. Should the collision resistance of another hash be broken, this will frustrate attackers trying to perform a Shambles-style attack. [OpenSSH does the same thing]: https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.certkeys#L151 ## No Split Brain Problem RNP maintains separate public and secret keyrings. This can lead to a so-called split-brain problem where a certificate is present in both keyrings, and confusingly one version is returned sometimes and the other version other times. This is also the model that GnuPG 1.x used, and is one of the reasons that GnuPG migrated to a single OpenPGP keystore in GnuPG 2.0 with only the secret key material held by the agent. To avoid this problem, the Octopus merges the two databases. To remain backwards compatible with RNP, when the Octopus writes out the certificates, certificates with secret key material are written to `secring.gpg` and those without are written to `pubring.gpg`. ## Multi-threading Thanks to Rust's [safer concurrency paradigms], it is less dangerous and less complicated for the Octopus to use threads than libraries written in other languages. The Octopus uses this, for instance, to [parse keyrings faster]. And to perform updates in the background. [safer concurrency paradigms]: https://doc.rust-lang.org/book/ch16-00-concurrency.html [parse keyrings faster]: https://gitlab.com/sequoia-pgp/sequoia-octopus-librnp/-/blob/595d1e20f71f3a62f8772ab457f11974bd9be2e9/src/keyring.rs#L173 ## OpenPGP Conformance Sequoia implements [nearly all] [of the OpenPGP RFC]. The missing bits are either obsolete or insecure. [nearly all]: https://sequoia-pgp.org/status/ [of the OpenPGP RFC]: https://tests.sequoia-pgp.org/ RNP doesn't implement a number of important parts. For example, RNP doesn't handle "esoteric" keys, like [shared keys] where only the encryption subkey's secret key material is shared. This is a compatibility problem. More examples can be found in the [OpenPGP interoperability test suite]. [shared keys]: https://bugzilla.mozilla.org/show_bug.cgi?id=1703368 [OpenPGP interoperability test suite]: https://tests.sequoia-pgp.org/ # Debugging When the Octopus is built in debug mode, it can emit tracing information to stderr (or `octopus.log` in your Thunderbird profile directory on Windows). There are several modes available, depending on the setting of the `SEQUOIA_OCTOPUS_TRACING` environment variable: modes are: - By default, only failures are traced. - `call` traces all function invocations with their arguments and results. - `full` selects full tracing mode. Please note that traces may contain sensitive information such as OpenPGP Fingerprints and User IDs, but they shouldn't contain secret key material. Always use the least verbose log level that demonstrates the problem, and review the log and remove sensitive information before submitting it to a public bug tracker. sequoia-octopus-librnp-1.11.1/build.rs000064400000000000000000000013541046102023000160010ustar 00000000000000 fn main() { vergen(); } fn vergen() { // Generate the "cargo:" instruction cargo:rustc-env=VERGEN_GIT_SHA= // // If the source directory is not a git repository, e.g. a tarball, this // produces an Error and VERGEN_GIT_SHA is not set. // In our case that is okay, because do not care for vergen variables other // than VERGEN_GIT_SHA and only use it if it is set (see // rnp_version_string_full in src/version.rs). // // Upstream issue: https://github.com/rustyhorde/vergen/issues/299#issuecomment-2095609569 let _ = vergen::EmitBuilder::builder() // Fail instead of setting vars to VERGEN_IDEMPOTENT_OUTPUT. .fail_on_error() .git_sha(/* short */ true) .emit(); } sequoia-octopus-librnp-1.11.1/notes.org000064400000000000000000000114651046102023000162010ustar 00000000000000* RNP ** API *** conflates certs and key This leads to man functions that are only applicable to either one, e.g. what should rnp_key_get_uid_count do when invoked with a subkey? Or, rnp_key_remove seemingly allows one to remove a primary key but keep the subkeys. *** key store model breaks down in corner cases As soon as a (sub)key belongs to two certificates, what happens is implementation defined. For example, the results of rnp_key_is_primary and rnp_key_is_sub is unpredictable and depends on the order the keys are in the keyrings. *** has many knobs, unclear defaults RNP allows many parameters to be tweaked, but the documentation doesn't state what the defaults are, or if they are sane. This may lead to downstream users making an explicit choice (better safe than sorry), but making an explicit choice requires domain knowledge, and the choice may become outdated. E.g. rnp_op_generate_set_hash. *** key unlock/lock mechanism RNP has the concept of unlocking keys. When a key is unlocked via rnp_key_unlock, it's encrypted secret part is decrypted and persisted in memory until it is locked again using rnp_key_lock. During this period, the key may be exfiltrated from the process' address space. *** unclear documentation "Generally ... not useful" pattern appears a lot, e.g. "Generally lock/unlock are not useful for unencrypted (not protected) keys." *** strings as identifiers The API uses strings as identifiers where an enum would have been the better choice: public key algorithms, symmetric algorithms, hash algorithms, curves, armor labels, identifier types. Possible values are documented with every function, but some lists are incomplete. Also, the documentation suggests a case-insensitive match in some cases (e.g. rnp_op_generate_create) but not in others (e.g. rnp_op_generate_set_hash). *** consistent use of rnp_result_t Every exported function returns a result, checks pointer arguments for NULL, and catches C++ exceptions turning them into error codes. The consistency is nice, trying hard not to crash is nice, but makes getters unergonomic and cannot handle all cases of course, e.g. bad but non-NULL pointers. *** rnp_export_public has odd flag RNP_KEY_EXPORT_PUBLIC. How can you ever not export the public key? *** rnp_key_have_public is odd How can you ever not have the public key? *** rnp conflates keys and certs RNP has a certring and a keyring. When doing rnp_locate_key, RNP searches both rings, and the returned rnp_key_handle_t can thus refer to two certificates, potentially different versions of the same, or even different certificates(!!!) if looked up by userid. It is not clear what to do, because the interface seems so broken. *** revocation status is binary This is in stark contrast to the fact that RNP seems to support creating third party revocations using rnp_key_export_revocation: > @param key primary key to be revoked. Must have secret key, > otherwise keyrings will be searched for the > authorized to issue revocation signature secret key. *** there is no way to automatically dearmor inputs There is rnp_input_dearmor_if_needed but it is not public. *** rnp_key_valid_till rnp_key_valid_till returns a 32-bit timestamp, but the expiration time is a 33-bit quantity (the expiration time is a 32-bit offset relative to the key's 32-bit creation time). https://github.com/rnpgp/rnp/issues/1480 ** implementation *** very little documentation in the code *** C-style runtime polymorphism with explicit vtables in C++ code E.g. struct pgp_dest_t *** poorly supported by ADTs E.g. the key iterator uses a json_object_object_add to deduplicate items. * TB's use of RNP ** unlocked keys may be exfiltrated RNP has the concept of unlocking keys. When a key is unlocked via rnp_key_unlock, it's encrypted secret part is decrypted and persisted in memory until it is locked again using rnp_key_lock. During this period, the key may be exfiltrated from TB's address space. Sequoia improves this, because Sequoia encrypts unencrypted secrets in memory. In some places, the key is only temporarily unlocked for some operation (e.g. attaching a subkey) and then locked again. This is fragile, because care must be take to run the locking on all exits (i.e. using try .. finally). In other places, the key is not locked again. ** Tests for example right now there is one xpcshell test, only, which you could request with mail/extensions/openpgp/test/unit/rnp/test_encryptAndOrSign.js - and there are many mochitests which you could request with directory mail/test/browser/openpgp as the parameter ./mach test mail/test/browser/openpgp/ mail/extensions/openpgp/test sequoia-octopus-librnp-1.11.1/src/armor.rs000064400000000000000000000011631046102023000166070ustar 00000000000000use sequoia_openpgp as openpgp; use openpgp::{ armor::{Reader, ReaderMode}, }; use crate::{ RnpResult, RnpInput, RnpOutput, error::*, }; #[no_mangle] pub unsafe extern "C" fn rnp_dearmor<'a>(input: *mut RnpInput, output: *mut RnpOutput<'a>) -> RnpResult { rnp_function!(rnp_dearmor, crate::TRACE); let input = assert_ptr_mut!(input); let output = assert_ptr_mut!(output); let mut reader = Reader::from_reader(input, ReaderMode::Tolerant(None)); rnp_try_or!(std::io::copy(&mut reader, output), RNP_ERROR_BAD_FORMAT); rnp_success!() } sequoia-octopus-librnp-1.11.1/src/buffer.rs000064400000000000000000000045101046102023000167370ustar 00000000000000//! Buffer management. //! //! RNP hands out buffers to both zero-terminated strings and binary //! data, and we need to be able to deallocate both with //! `rnp_buffer_destroy`. //! //! Our strategy is to use libc's allocator and let it keep track of //! the size of the allocations. use std::{ ptr::{ copy_nonoverlapping, }, }; use libc::{ malloc, free, c_char, c_void, }; /// Copies a Rust string to a buffer, adding a terminating zero. pub fn str_to_rnp_buffer>(s: S) -> *mut c_char { let s = s.as_ref(); let bytes = s.as_bytes(); unsafe { let buf = malloc(bytes.len() + 1); copy_nonoverlapping(bytes.as_ptr(), buf as *mut _, bytes.len()); *((buf as *mut u8).add(bytes.len())) = 0; // Terminate. buf as *mut c_char } } /// Copies a C string to a buffer, adding a terminating zero. /// /// Fails on embedded zeros. pub fn c_str_to_rnp_buffer>(s: S) -> Option<*mut c_char> { let bytes = s.as_ref(); if bytes.iter().any(|b| *b == 0) { return None; } unsafe { let buf = malloc(bytes.len() + 1); copy_nonoverlapping(bytes.as_ptr(), buf as *mut _, bytes.len()); *((buf as *mut u8).add(bytes.len())) = 0; // Terminate. Some(buf as *mut c_char) } } /// Copies a C string to a buffer, adding a terminating zero. /// /// Replaces embedded zeros with '_'. pub fn c_str_to_rnp_buffer_lossy>(s: S) -> *mut c_char { let bytes = s.as_ref(); unsafe { let buf = malloc(bytes.len() + 1); copy_nonoverlapping(bytes.as_ptr(), buf as *mut _, bytes.len()); // Replace embedded zeros. let bytes_mut = std::slice::from_raw_parts_mut(buf as *mut u8, bytes.len()); bytes_mut.iter_mut().for_each(|b| if *b == 0 { *b = b'_' }); *((buf as *mut u8).add(bytes.len())) = 0; // Terminate. buf as *mut c_char } } /// Copies a byte slice to a buffer. pub fn bytes_to_rnp_buffer>(b: B) -> *mut u8 { let bytes = b.as_ref(); unsafe { let buf = malloc(bytes.len()); copy_nonoverlapping(bytes.as_ptr(), buf as *mut _, bytes.len()); buf as *mut _ } } #[no_mangle] pub unsafe extern "C" fn rnp_buffer_destroy(ptr: *mut c_void) { free(ptr); } sequoia-octopus-librnp-1.11.1/src/conversions.rs000064400000000000000000000267751046102023000200570ustar 00000000000000use sequoia_openpgp as openpgp; use openpgp::{ armor, KeyID, Fingerprint, KeyHandle, packet::{ UserID, }, policy::AsymmetricAlgorithm, types::{ AEADAlgorithm, Curve, HashAlgorithm, SymmetricAlgorithm, PublicKeyAlgorithm, ReasonForRevocation, KeyFlags, } }; use crate::{ Keygrip, error::*, }; pub trait ToRnpId { fn to_rnp_id(&self) -> &str; } pub trait FromRnpId { fn from_rnp_id(id: &str) -> Result where Self: Sized; } impl ToRnpId for SymmetricAlgorithm { fn to_rnp_id(&self) -> &str { use SymmetricAlgorithm::*; #[allow(deprecated)] match self { Unencrypted => "PLAINTEXT", IDEA => "IDEA", TripleDES => "TRIPLEDES", CAST5 => "CAST5", Blowfish => "BLOWFISH", AES128 => "AES128", AES192 => "AES192", AES256 => "AES256", Twofish => "TWOFISH", Camellia128 => "CAMELLIA128", Camellia192 => "CAMELLIA192", Camellia256 => "CAMELLIA256", _ => "unknown", } } } impl FromRnpId for SymmetricAlgorithm { fn from_rnp_id(id: &str) -> Result { use SymmetricAlgorithm::*; #[allow(deprecated)] match id.to_uppercase().as_str() { "IDEA" => Ok(IDEA), "TRIPLEDES" => Ok(TripleDES), "CAST5" => Ok(CAST5), "BLOWFISH" => Ok(Blowfish), "AES128" => Ok(AES128), "AES192" => Ok(AES192), "AES256" => Ok(AES256), "TWOFISH" => Ok(Twofish), "CAMELLIA128" => Ok(Camellia128), "CAMELLIA192" => Ok(Camellia192), "CAMELLIA256" => Ok(Camellia256), "SM4" => Err(RNP_ERROR_NOT_SUPPORTED), _ => { global_warn!("unknown symmetric algorithm: {:?}", id); Err(RNP_ERROR_BAD_PARAMETERS) }, } } } impl FromRnpId for Option { fn from_rnp_id(id: &str) -> Result { use AEADAlgorithm::*; match id.to_uppercase().as_str() { "NONE" => Ok(None), "EAX" => Ok(Some(EAX)), "OCB" => Ok(Some(OCB)), _ => { global_warn!("unknown AEAD algorithm: {:?}", id); Err(RNP_ERROR_BAD_PARAMETERS) }, } } } impl ToRnpId for PublicKeyAlgorithm { fn to_rnp_id(&self) -> &str { use PublicKeyAlgorithm::*; #[allow(deprecated)] match self { RSAEncryptSign => "RSA", RSAEncrypt => "RSA", RSASign => "RSA", ElGamalEncrypt => "ELGAMAL", DSA => "DSA", ECDH => "ECDH", ECDSA => "ECDSA", ElGamalEncryptSign => "ELGAMAL", EdDSA => "EDDSA", _ => "unknown", } } } impl FromRnpId for PublicKeyAlgorithm { fn from_rnp_id(id: &str) -> Result { use PublicKeyAlgorithm::*; #[allow(deprecated)] match id.to_uppercase().as_str() { "RSA" => Ok(RSAEncryptSign), "DSA" => Ok(DSA), "ELGAMAL" => Ok(ElGamalEncrypt), "ECDSA" => Ok(ECDSA), "ECDH" => Ok(ECDH), "EDDSA" => Ok(EdDSA), "SM2" => Err(RNP_ERROR_NOT_SUPPORTED), _ => { global_warn!("unknown public key algorithm: {:?}", id); Err(RNP_ERROR_BAD_PARAMETERS) }, } } } impl ToRnpId for Curve { fn to_rnp_id(&self) -> &str { use Curve::*; match self { NistP256 => "NIST P-256", NistP384 => "NIST P-384", NistP521 => "NIST P-521", BrainpoolP256 => "brainpoolP256r1", BrainpoolP512 => "brainpoolP512r1", Ed25519 => "Ed25519", Cv25519 => "Curve25519", _ => "unknown", } } } impl FromRnpId for Curve { fn from_rnp_id(id: &str) -> Result { use Curve::*; match id.to_uppercase().as_str() { "NIST P-256" => Ok(NistP256), "NIST P-384" => Ok(NistP384), "NIST P-521" => Ok(NistP521), "BRAINPOOLP256R1" => Ok(BrainpoolP256), "BRAINPOOLP512R1" => Ok(BrainpoolP512), "ED25519" => Ok(Ed25519), "CURVE25519" => Ok(Cv25519), "SECP256K1" => Err(RNP_ERROR_NOT_SUPPORTED), "SM2" => Err(RNP_ERROR_NOT_SUPPORTED), _ => { global_warn!("unknown curve: {:?}", id); Err(RNP_ERROR_BAD_PARAMETERS) }, } } } impl ToRnpId for HashAlgorithm { fn to_rnp_id(&self) -> &str { use HashAlgorithm::*; match self { MD5 => "MD5", SHA1 => "SHA1", RipeMD => "RIPEMD160", SHA256 => "SHA256", SHA384 => "SHA384", SHA512 => "SHA512", SHA224 => "SHA224", _ => "unknown", } } } impl FromRnpId for HashAlgorithm { fn from_rnp_id(id: &str) -> Result { use HashAlgorithm::*; match id.to_uppercase().as_str() { "MD5" => Ok(MD5), "SHA1" => Ok(SHA1), "RIPEMD160" => Ok(RipeMD), "SHA256" => Ok(SHA256), "SHA384" => Ok(SHA384), "SHA512" => Ok(SHA512), "SHA224" => Ok(SHA224), "SM3" => Err(RNP_ERROR_NOT_SUPPORTED), _ => { global_warn!("unknown hash algorithm: {:?}", id); Err(RNP_ERROR_BAD_PARAMETERS) }, } } } /// Extensions for AsymmetricAlgorihm. pub trait AsymmetricAlgorithmExt { /// Returns all AsymmetricAlgorithms matching the given RNP name. /// /// AsymmetricAlgorithm is more fine-grained as it includes the /// key length. To compensate, we return all RSA values here, /// with the most appropriate representative as the first value. fn all_from_rnp_id(id: &str) -> Result> { use AsymmetricAlgorithm::*; match id.to_uppercase().as_str() { "RSA" => Ok(vec![RSA2048, RSA1024, RSA3072, RSA4096]), "ELGAMAL" => Ok(vec![ElGamal2048, ElGamal1024, ElGamal3072, ElGamal4096]), "DSA" => Ok(vec![DSA2048, DSA1024, DSA3072, DSA4096]), "SM2" => Ok(vec![]), // XXX: Add more algorithms as more RNP_ALGNAME_* symbols // are added. _ => { global_warn!("unknown symmetric algorithm: {:?}", id); Err(RNP_ERROR_BAD_PARAMETERS) }, } } } impl AsymmetricAlgorithmExt for AsymmetricAlgorithm {} impl FromRnpId for AsymmetricAlgorithm { fn from_rnp_id(id: &str) -> Result { Self::all_from_rnp_id(id)?.get(0).cloned() .ok_or(RNP_ERROR_NOT_SUPPORTED) } } impl ToRnpId for armor::Kind { fn to_rnp_id(&self) -> &str { use armor::Kind::*; match self { Message => "message", PublicKey => "public key", SecretKey => "secret key", Signature => "signature", _ => "unknown", } } } impl FromRnpId for armor::Kind { fn from_rnp_id(id: &str) -> Result { use armor::Kind::*; match id.to_uppercase().as_str() { "MESSAGE" => Ok(Message), "PUBLIC KEY" => Ok(PublicKey), "SECRET KEY" => Ok(SecretKey), "SIGNATURE" => Ok(Signature), _ => { global_warn!("unknown symmetric algorithm: {:?}", id); Err(RNP_ERROR_BAD_PARAMETERS) }, } } } impl ToRnpId for ReasonForRevocation { fn to_rnp_id(&self) -> &str { use ReasonForRevocation::*; match self { Unspecified => "no", KeySuperseded => "superseded", KeyCompromised => "compromised", KeyRetired => "retired", UIDRetired => "uid retired", // XXX: RNP doesn't support that afaics _ => "unknown", } } } impl FromRnpId for ReasonForRevocation { fn from_rnp_id(id: &str) -> Result { use ReasonForRevocation::*; match id.to_uppercase().as_str() { "NO" => Ok(Unspecified), "SUPERSEDED" => Ok(KeySuperseded), "COMPROMISED" => Ok(KeyCompromised), "RETIRED" => Ok(KeyRetired), _ => { global_warn!("unknown reason for revocation: {:?}", id); Err(RNP_ERROR_BAD_PARAMETERS) }, } } } #[derive(Debug)] pub enum RnpIdentifierType { UserID, KeyID, Fingerprint, Keygrip, } impl FromRnpId for RnpIdentifierType { fn from_rnp_id(id: &str) -> Result { match id { "userid" => Ok(RnpIdentifierType::UserID), "keyid" => Ok(RnpIdentifierType::KeyID), "fingerprint" => Ok(RnpIdentifierType::Fingerprint), "grip" => Ok(RnpIdentifierType::Keygrip), _ => { global_warn!("unknown iterator typ: {:?}", id); Err(RNP_ERROR_BAD_PARAMETERS) }, } } } impl RnpIdentifierType { pub fn with_identifier(&self, id: &str) -> Result { Ok(match self { RnpIdentifierType::UserID => RnpIdentifier::UserID(id.into()), RnpIdentifierType::KeyID => RnpIdentifier::KeyID( global_rnp_try_or!(id.parse(), Err(RNP_ERROR_BAD_PARAMETERS))), RnpIdentifierType::Fingerprint => RnpIdentifier::Fingerprint( global_rnp_try_or!(id.parse(), Err(RNP_ERROR_BAD_PARAMETERS))), RnpIdentifierType::Keygrip => RnpIdentifier::Keygrip( global_rnp_try_or!(id.parse(), Err(RNP_ERROR_BAD_PARAMETERS))), }) } } #[derive(Debug)] pub enum RnpIdentifier { UserID(UserID), KeyID(KeyID), Fingerprint(Fingerprint), Keygrip(Keygrip), } impl From for RnpIdentifier { fn from(h: KeyHandle) -> Self { match h { KeyHandle::KeyID(i) => RnpIdentifier::KeyID(i), KeyHandle::Fingerprint(i) => RnpIdentifier::Fingerprint(i), } } } impl From<&KeyHandle> for RnpIdentifier { fn from(h: &KeyHandle) -> Self { match h { KeyHandle::KeyID(i) => RnpIdentifier::KeyID(i.clone()), KeyHandle::Fingerprint(i) => RnpIdentifier::Fingerprint(i.clone()), } } } #[derive(Debug)] pub enum RnpKeyUsage { Sign, Certify, Encrypt, Authenticate, } impl FromRnpId for RnpKeyUsage { fn from_rnp_id(id: &str) -> Result { match id { "sign" => Ok(RnpKeyUsage::Sign), "certify" => Ok(RnpKeyUsage::Certify), "encrypt" => Ok(RnpKeyUsage::Encrypt), "authenticate" => Ok(RnpKeyUsage::Authenticate), _ => { global_warn!("unknown usage: {:?}", id); Err(RNP_ERROR_BAD_PARAMETERS) }, } } } impl RnpKeyUsage { pub fn to_keyflags(&self) -> KeyFlags { use RnpKeyUsage::*; match self { Sign => KeyFlags::empty().set_signing(), Certify => KeyFlags::empty().set_certification(), Encrypt => KeyFlags::empty().set_transport_encryption() .set_storage_encryption(), Authenticate => KeyFlags::empty().set_authentication(), } } } sequoia-octopus-librnp-1.11.1/src/dump_packets/dump.rs000064400000000000000000001400541046102023000211160ustar 00000000000000use std::io::{self, Read}; use sequoia_openpgp as openpgp; use openpgp::armor::ReaderMode; use self::openpgp::types::SymmetricAlgorithm; use self::openpgp::fmt::hex; use self::openpgp::crypto::mpi; use self::openpgp::{Packet, Result}; use self::openpgp::packet; use self::openpgp::packet::prelude::*; use self::openpgp::packet::header::CTB; use self::openpgp::packet::{Header, header::BodyLength, Signature}; use self::openpgp::packet::signature::subpacket::{Subpacket, SubpacketValue}; use self::openpgp::crypto::S2K; use self::openpgp::parse::{ Dearmor, Parse, PacketParserBuilder, PacketParserResult, map::Map, }; /// Converts sequoia_openpgp types for rendering. pub trait Convert { /// Performs the conversion. fn convert(self) -> T; } impl Convert for std::time::Duration { fn convert(self) -> humantime::FormattedDuration { humantime::format_duration(self) } } impl Convert for openpgp::types::Duration { fn convert(self) -> humantime::FormattedDuration { humantime::format_duration(self.into()) } } impl Convert> for std::time::SystemTime { fn convert(self) -> chrono::DateTime { chrono::DateTime::::from(self) } } impl Convert> for openpgp::types::Timestamp { fn convert(self) -> chrono::DateTime { std::time::SystemTime::from(self).convert() } } /// Holds a session key as parsed from the command line, with an optional /// algorithm specifier. /// /// This struct does not implement [`Display`] to prevent accidental leaking /// of key material. If you are sure you want to print a session key, use /// [`display_sensitive`]. /// /// [`Display`]: std::fmt::Display /// [`display_sensitive`]: SessionKey::display_sensitive #[derive(Debug, Clone)] pub struct SessionKey { pub session_key: openpgp::crypto::SessionKey, pub symmetric_algo: Option, } impl SessionKey { /// Returns an object that implements Display for explicitly opting into /// printing a `SessionKey`. pub fn display_sensitive(&self) -> SessionKeyDisplay { SessionKeyDisplay { csk: self } } } /// Helper struct for intentionally printing session keys with format! and {}. /// /// This struct implements the `Display` trait to print the session key. This /// construct requires the user to explicitly call /// [`SessionKey::display_sensitive`]. By requiring the user to opt-in, this /// will hopefully reduce that the chance that the session key is inadvertently /// leaked, e.g., in a log that may be publicly posted. pub struct SessionKeyDisplay<'a> { csk: &'a SessionKey, } /// Print the session key without prefix in hexadecimal representation. impl<'a> std::fmt::Display for SessionKeyDisplay<'a> { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let sk = self.csk; write!(f, "{}", hex::encode(&sk.session_key)) } } #[derive(Debug)] pub enum Kind { Message { encrypted: bool, }, Keyring, Cert, Unknown, } #[allow(clippy::redundant_pattern_matching)] pub fn dump(input: &mut (dyn io::Read + Sync + Send), output: &mut dyn io::Write, max_decompressed_literal_data: Option, mpis: bool, hex: bool, sk: Option<&SessionKey>, width: W) -> Result where W: Into> { rnp_function!(dump, crate::TRACE); // If no limit is supplied, stop after 100 MB. let max_decompressed_literal_data = max_decompressed_literal_data.unwrap_or(100 * 1024 * 1024); let mut saw_decompression_packet = false; let mut ppr = self::openpgp::parse::PacketParserBuilder::from_reader(input)?; // To produce hex dumps, we need to enable mapping, but also turn // on buffering. This makes sure that the map contains the whole // packet content, even if it has not been parsed (such as when // encountering unknown or junk pseudo packets). if hex { ppr = ppr.map(true).buffer_unread_content(); } let mut ppr = ppr.build()?; let width = width.into().unwrap_or(80); let mut first_armor_block = true; let mut is_keyring = true; loop { let mut dumper = PacketDumper::new(width, mpis); let mut message_encrypted = false; let mut pkesks = vec![]; let mut skesks = vec![]; while let PacketParserResult::Some(mut pp) = ppr { let additional_fields = match pp.packet { Packet::PKESK(ref p) => { pkesks.push(p.clone()); vec![] }, Packet::SKESK(ref p) => { skesks.push(p.clone()); vec![] }, Packet::CompressedData(_) => { t!("Encountered compressed data packet. \ Activating zip bomb protection."); saw_decompression_packet = true; Vec::new() } Packet::Literal(_) => { let mut prefix = vec![0; 40]; let n = pp.read(&mut prefix)?; let summary = vec![ format!("Content: {:?}{}", String::from_utf8_lossy(&prefix[..n]), if n == prefix.len() { "..." } else { "" }), ]; if saw_decompression_packet { // Protect against a possible zip bomb. t!("Zip bomb protection activated. Will abort after \ reading more than {} bytes of literal data.", max_decompressed_literal_data); const BUFFER_SIZE: usize = 1024 * 1024; let mut buffer = vec![0; BUFFER_SIZE]; let mut literal_data_read = prefix.len(); while literal_data_read <= max_decompressed_literal_data { let remaining = max_decompressed_literal_data - literal_data_read + 1; let read = pp.read( &mut buffer[..remaining.min(BUFFER_SIZE)])?; if read == 0 { // EOF. break; } literal_data_read += read; } t!("Read {} bytes of literal data", literal_data_read); if literal_data_read > max_decompressed_literal_data { t!("Zip bomb detected"); return Err(crate::Error::BadParameters.into()); } else { t!("No zip bomb detected"); } } summary }, Packet::SEIP(ref s) => { let version = s.version(); message_encrypted = true; if let Some(sk) = &sk { let decrypted_with = match s { SEIP::V2(s) => { // No need for guessing. let algo = s.symmetric_algo(); pp.decrypt(algo, &sk.session_key).ok().map(|_| algo) }, _ => { // We don't know which algorithm to use, // try to find one that decrypts the message. (1u8..=19) .map(SymmetricAlgorithm::from) .find(|algo| pp.decrypt(*algo, &sk.session_key).is_ok()) }, }; let mut fields = Vec::new(); fields.push(format!("Session key: {}", hex::encode(&sk.session_key))); if let Some(algo) = decrypted_with { if version == 1 { // For v2, the packet already contains that // information. fields.push(format!("Symmetric algo: {}", algo)); } fields.push("Decryption successful".into()); } else { fields.push("Decryption failed".into()); } fields } else { vec!["No session key supplied".into()] } }, _ => Vec::new(), }; let header = pp.header().clone(); let map = pp.take_map(); let recursion_depth = pp.recursion_depth(); let packet = pp.packet.clone(); dumper.packet(output, recursion_depth as usize, header, packet, map, additional_fields)?; let (_, ppr_) = match pp.recurse() { Ok(v) => Ok(v), Err(e) => { let _ = dumper.flush(output); Err(e) }, }?; ppr = ppr_; } dumper.flush(output)?; if let PacketParserResult::EOF(eof) = ppr { let is_message = eof.is_message().is_ok() && first_armor_block; let is_cert = eof.is_cert().is_ok() && first_armor_block; is_keyring &= eof.is_keyring().is_ok(); first_armor_block = false; // Now, the parser is exhausted, but we may find another // armored blob. Note that this can only happen if the first // set of packets was also armored. match PacketParserBuilder::from_buffered_reader(eof.into_reader()) .and_then( |builder| builder .dearmor(Dearmor::Enabled( ReaderMode::Tolerant(None))) .build()) { Ok(ppr_) => { writeln!(output, "Note: There is another block of armored \ OpenPGP data.")?; if is_message { writeln!(output, "Note: Data concatenated to a message is \ likely an error.")?; } else if is_cert || is_keyring { writeln!(output, "Note: This is a non-standard extension \ to OpenPGP.")?; } writeln!(output)?; ppr = ppr_; continue; }, Err(_) => break if is_message { Ok(Kind::Message { encrypted: message_encrypted, }) } else if is_cert { Ok(Kind::Cert) } else if is_keyring { Ok(Kind::Keyring) } else { Ok(Kind::Unknown) } } } else { unreachable!() } } } struct Node { header: Header, packet: Packet, map: Option, additional_fields: Vec, children: Vec, } impl Node { fn new(header: Header, packet: Packet, map: Option, additional_fields: Vec) -> Self { Node { header, packet, map, additional_fields, children: Vec::new(), } } fn append(&mut self, depth: usize, node: Node) { if depth == 0 { self.children.push(node); } else { self.children.iter_mut().last().unwrap().append(depth - 1, node); } } } pub struct PacketDumper { width: usize, mpis: bool, root: Option, } impl PacketDumper { pub fn new(width: usize, mpis: bool) -> Self { PacketDumper { width, mpis, root: None, } } pub fn packet(&mut self, output: &mut dyn io::Write, depth: usize, header: Header, p: Packet, map: Option, additional_fields: Vec) -> Result<()> { let node = Node::new(header, p, map, additional_fields); if self.root.is_none() { assert_eq!(depth, 0); self.root = Some(node); } else if depth == 0 { let root = self.root.take().unwrap(); self.dump_tree(output, "", &root)?; self.root = Some(node); } else { self.root.as_mut().unwrap().append(depth - 1, node); } Ok(()) } pub fn flush(&self, output: &mut dyn io::Write) -> Result<()> { if let Some(root) = self.root.as_ref() { self.dump_tree(output, "", root)?; } Ok(()) } fn dump_tree(&self, output: &mut dyn io::Write, indent: &str, node: &Node) -> Result<()> { let indent_node = format!("{}{} ", indent, if node.children.is_empty() { " " } else { "│" }); self.dump_packet(output, &indent_node, Some(&node.header), &node.packet, node.map.as_ref(), &node.additional_fields)?; if node.children.is_empty() { return Ok(()); } let last = node.children.len() - 1; for (i, child) in node.children.iter().enumerate() { let is_last = i == last; write!(output, "{}{}── ", indent, if is_last { "└" } else { "├" })?; let indent_child = format!("{}{} ", indent, if is_last { " " } else { "│" }); self.dump_tree(output, &indent_child, child)?; } Ok(()) } fn dump_packet(&self, mut output: &mut dyn io::Write, i: &str, header: Option<&Header>, p: &Packet, map: Option<&Map>, additional_fields: &Vec) -> Result<()> { use self::openpgp::Packet::*; if let Some(tag) = p.kind() { write!(output, "{}", tag)?; } else { write!(output, "Unknown or Unsupported Packet")?; } if let Some(h) = header { write!(output, ", {} CTB, {}{}", if let CTB::Old(_) = h.ctb() { "old" } else { "new" }, if let Some(map) = map { format!("{} header bytes + ", map.iter().take(2).map(|f| f.as_bytes().len()) .sum::()) } else { // XXX: Mapping is disabled. No can do for // now. Once we save the header in // packet::Common, we can use this instead of // relying on the map. "".into() }, match h.length() { BodyLength::Full(n) => format!("{} bytes", n), BodyLength::Partial(n) => format!("partial length, {} bytes in first chunk", n), BodyLength::Indeterminate => "indeterminate length".into(), })?; } writeln!(output)?; #[allow(deprecated)] match p { Unknown(ref u) => { writeln!(output, "{} Tag: {}", i, u.tag())?; writeln!(output, "{} Error: {}", i, u.error())?; }, PublicKey(ref k) => self.dump_key(output, i, k)?, PublicSubkey(ref k) => self.dump_key(output, i, k)?, SecretKey(ref k) => self.dump_key(output, i, k)?, SecretSubkey(ref k) => self.dump_key(output, i, k)?, Signature(s) => self.dump_signature(output, i, s)?, OnePassSig(ref o) => { writeln!(output, "{} Version: {}", i, o.version())?; writeln!(output, "{} Type: {}", i, o.typ())?; writeln!(output, "{} Pk algo: {}", i, o.pk_algo())?; writeln!(output, "{} Hash algo: {}", i, o.hash_algo())?; writeln!(output, "{} Issuer: {}", i, o.issuer())?; if let packet::OnePassSig::V6(o) = o { writeln!(output, "{} Salt: {}", i, hex::encode(o.salt()))?; } writeln!(output, "{} Last: {}", i, o.last())?; }, Trust(ref p) => { writeln!(output, "{} Value:", i)?; let mut hd = hex::Dumper::new( &mut output, self.indentation_for_hexdump(&format!("{} ", i), 16)); hd.write_ascii(p.value())?; }, UserID(ref u) => { writeln!(output, "{} Value: {}", i, String::from_utf8_lossy(u.value()))?; }, UserAttribute(ref u) => { use self::openpgp::packet::user_attribute::{Subpacket, Image}; use openpgp::serialize::MarshalInto; for subpacket in u.subpackets() { match subpacket { Ok(Subpacket::Image(image)) => match image { Image::JPEG(data) => writeln!(output, "{} JPEG: {} bytes", i, data.len())?, Image::Private(n, data) => writeln!(output, "{} Private image({}): {} bytes", i, n, data.len())?, Image::Unknown(n, data) => writeln!(output, "{} Unknown image({}): {} bytes", i, n, data.len())?, _ => writeln!(output, "{} Unknown image: {} bytes", i, image.serialized_len())?, }, Ok(Subpacket::Unknown(n, data)) => writeln!(output, "{} Unknown subpacket({}): {} bytes", i, n, data.len())?, Ok(u) => writeln!(output, "{} Unknown subpacket: {} bytes", i, u.serialized_len())?, Err(e) => writeln!(output, "{} Invalid subpacket encoding: {}", i, e)?, } } }, Marker(_) => { }, Literal(ref l) => { writeln!(output, "{} Format: {}", i, l.format())?; if let Some(filename) = l.filename() { writeln!(output, "{} Filename: {:?}", i, String::from_utf8_lossy(filename))?; } if let Some(timestamp) = l.date() { writeln!(output, "{} Timestamp: {}", i, timestamp.convert())?; } }, CompressedData(ref c) => { writeln!(output, "{} Algorithm: {}", i, c.algo())?; }, PKESK(ref p) => { writeln!(output, "{} Version: {}", i, p.version())?; writeln!(output, "{} Recipient: {}", i, p.recipient().as_ref().map(ToString::to_string) .unwrap_or_else(|| "".into()))?; writeln!(output, "{} Pk algo: {}", i, p.pk_algo())?; if self.mpis { writeln!(output, "{}", i)?; writeln!(output, "{} Encrypted session key:", i)?; let ii = format!("{} ", i); match p.esk() { mpi::Ciphertext::X25519 { e, key } => self.dump_mpis(output, &ii, &[&e[..], key], &["e", "key"])?, mpi::Ciphertext::X448 { e, key } => self.dump_mpis(output, &ii, &[&e[..], key], &["e", "key"])?, mpi::Ciphertext::RSA { c } => self.dump_mpis(output, &ii, &[c.value()], &["c"])?, mpi::Ciphertext::ElGamal { e, c } => self.dump_mpis(output, &ii, &[e.value(), c.value()], &["e", "c"])?, mpi::Ciphertext::ECDH { e, key } => self.dump_mpis(output, &ii, &[e.value(), key], &["e", "key"])?, mpi::Ciphertext::Unknown { mpis, rest } => { let keys: Vec = (0..mpis.len()).map( |i| format!("mpi{}", i)).collect(); self.dump_mpis( output, &ii, &mpis.iter().map(|m| { m.value().iter().as_slice() }).collect::>()[..], &keys.iter().map(|k| k.as_str()) .collect::>()[..], )?; self.dump_mpis(output, &ii, &[rest], &["rest"])?; }, // crypto::mpi::Ciphertext is non-exhaustive. u => writeln!(output, "{}Unknown variant: {:?}", ii, u)?, } } }, SKESK(ref s) => { writeln!(output, "{} Version: {}", i, s.version())?; match s { self::openpgp::packet::SKESK::V4(ref s) => { writeln!(output, "{} Symmetric algo: {}", i, s.symmetric_algo())?; write!(output, "{} S2K: ", i)?; self.dump_s2k(output, i, s.s2k())?; if let Ok(Some(esk)) = s.esk() { writeln!(output, "{} ESK: {}", i, hex::encode(esk))?; } }, self::openpgp::packet::SKESK::V6(ref s) => { writeln!(output, "{} Symmetric algo: {}", i, s.symmetric_algo())?; writeln!(output, "{} AEAD: {}", i, s.aead_algo())?; write!(output, "{} S2K: ", i)?; self.dump_s2k(output, i, s.s2k())?; writeln!(output, "{} IV: {}", i, hex::encode(s.aead_iv()))?; writeln!(output, "{} ESK: {}", i, hex::encode(s.esk()))?; }, // SKESK is non-exhaustive. u => writeln!(output, "{} Unknown variant: {:?}", i, u)?, } }, SEIP(ref s) => { writeln!(output, "{} Version: {}", i, s.version())?; match s { packet::SEIP::V1(_) => (), packet::SEIP::V2(s) => { writeln!(output, "{} Symmetric algo: {}", i, s.symmetric_algo())?; writeln!(output, "{} AEAD algo: {}", i, s.aead())?; writeln!(output, "{} Chunk size: {}", i, s.chunk_size())?; writeln!(output, "{} Salt: {}", i, hex::encode(s.salt()))?; }, _ => (), } }, MDC(ref m) => { writeln!(output, "{} Digest: {}", i, hex::encode(m.digest()))?; writeln!(output, "{} Computed digest: {}", i, hex::encode(m.computed_digest()))?; writeln!(output, "{} Valid: {}", i, m.valid())?; }, Padding(_) => { // Nothing to do. } // openpgp::Packet is non-exhaustive. u => writeln!(output, "{} Unknown variant: {:?}", i, u)?, } for field in additional_fields { writeln!(output, "{} {}", i, field)?; } writeln!(output, "{}", i)?; if let Some(map) = map { if map.iter().next().is_none() { // There is no data, we cannot dump anything. return Ok(()); } let mut hd = hex::Dumper::new(output, self.indentation_for_hexdump( i, map.iter() .map(|f| if f.name() == "body" { 16 } else { f.name().len() }) .max() .expect("we checked that there is one entry"))); for field in map.iter() { if field.name() == "body" { hd.write_ascii(field.as_bytes())?; } else { hd.write(field.as_bytes(), field.name())?; } } let output = hd.into_inner(); writeln!(output, "{}", i)?; } Ok(()) } /// Dumps the given key packet. fn dump_key(&self, output: &mut dyn io::Write, i: &str, k: &Key) -> Result<()> where P: key::KeyParts, R: key::KeyRole, { self.dump_key_internal( output, i, k.parts_as_unspecified().role_as_unspecified()) } /// Dumps the given key packet, prevents monomorphization of this /// big function. fn dump_key_internal(&self, output: &mut dyn io::Write, i: &str, k: &Key) -> Result<()> { writeln!(output, "{} Version: {}", i, k.version())?; writeln!(output, "{} Creation time: {}", i, k.creation_time().convert())?; writeln!(output, "{} Pk algo: {}", i, k.pk_algo())?; if let Some(bits) = k.mpis().bits() { writeln!(output, "{} Pk size: {} bits", i, bits)?; } writeln!(output, "{} Fingerprint: {}", i, k.fingerprint())?; writeln!(output, "{} KeyID: {}", i, k.keyid())?; if self.mpis { writeln!(output, "{}", i)?; writeln!(output, "{} Public Key:", i)?; let ii = format!("{} ", i); match k.mpis() { mpi::PublicKey::X25519 { u } => self.dump_mpis(output, &ii, &[u], &["u"])?, mpi::PublicKey::X448 { u } => self.dump_mpis(output, &ii, &[&u[..]], &["u"])?, mpi::PublicKey::Ed25519 { a } => self.dump_mpis(output, &ii, &[a], &["a"])?, mpi::PublicKey::Ed448 { a } => self.dump_mpis(output, &ii, &[&a[..]], &["a"])?, mpi::PublicKey::RSA { e, n } => self.dump_mpis(output, &ii, &[e.value(), n.value()], &["e", "n"])?, mpi::PublicKey::DSA { p, q, g, y } => self.dump_mpis(output, &ii, &[p.value(), q.value(), g.value(), y.value()], &["p", "q", "g", "y"])?, mpi::PublicKey::ElGamal { p, g, y } => self.dump_mpis(output, &ii, &[p.value(), g.value(), y.value()], &["p", "g", "y"])?, mpi::PublicKey::EdDSA { curve, q } => { writeln!(output, "{} Curve: {}", ii, curve)?; self.dump_mpis(output, &ii, &[q.value()], &["q"])?; }, mpi::PublicKey::ECDSA { curve, q } => { writeln!(output, "{} Curve: {}", ii, curve)?; self.dump_mpis(output, &ii, &[q.value()], &["q"])?; }, mpi::PublicKey::ECDH { curve, q, hash, sym } => { writeln!(output, "{} Curve: {}", ii, curve)?; writeln!(output, "{} KDF hash algo: {}", ii, hash)?; writeln!(output, "{} KEK symmetric algo: {}", ii, sym)?; self.dump_mpis(output, &ii, &[q.value()], &["q"])?; }, mpi::PublicKey::Unknown { mpis, rest } => { let keys: Vec = (0..mpis.len()).map( |i| format!("mpi{}", i)).collect(); self.dump_mpis( output, &ii, &mpis.iter().map(|m| { m.value().iter().as_slice() }).collect::>()[..], &keys.iter().map(|k| k.as_str()) .collect::>()[..], )?; self.dump_mpis(output, &ii, &[&rest[..]], &["rest"])?; }, // crypto::mpi:Publickey is non-exhaustive u => writeln!(output, "{}Unknown variant: {:?}", ii, u)?, } } if let Some(secrets) = k.optional_secret() { writeln!(output, "{}", i)?; writeln!(output, "{} Secret Key:", i)?; let ii = format!("{} ", i); match secrets { SecretKeyMaterial::Unencrypted(ref u) => { writeln!(output, "{}", i)?; writeln!(output, "{} Unencrypted", ii)?; if self.mpis { u.map(|mpis| -> Result<()> { match mpis { mpi::SecretKeyMaterial::X25519 { x } => self.dump_mpis(output, &ii, &[x], &["x"])?, mpi::SecretKeyMaterial::X448 { x } => self.dump_mpis(output, &ii, &[&x[..]], &["x"])?, mpi::SecretKeyMaterial::Ed25519 { x } => self.dump_mpis(output, &ii, &[x], &["x"])?, mpi::SecretKeyMaterial::Ed448 { x } => self.dump_mpis(output, &ii, &[&x[..]], &["x"])?, mpi::SecretKeyMaterial::RSA { d, p, q, u } => self.dump_mpis(output, &ii, &[d.value(), p.value(), q.value(), u.value()], &["d", "p", "q", "u"])?, mpi::SecretKeyMaterial::DSA { x } => self.dump_mpis(output, &ii, &[x.value()], &["x"])?, mpi::SecretKeyMaterial::ElGamal { x } => self.dump_mpis(output, &ii, &[x.value()], &["x"])?, mpi::SecretKeyMaterial::EdDSA { scalar } => self.dump_mpis(output, &ii, &[scalar.value()], &["scalar"])?, mpi::SecretKeyMaterial::ECDSA { scalar } => self.dump_mpis(output, &ii, &[scalar.value()], &["scalar"])?, mpi::SecretKeyMaterial::ECDH { scalar } => self.dump_mpis(output, &ii, &[scalar.value()], &["scalar"])?, mpi::SecretKeyMaterial::Unknown { mpis, rest } => { let keys: Vec = (0..mpis.len()).map( |i| format!("mpi{}", i)).collect(); self.dump_mpis( output, &ii, &mpis.iter().map(|m| { m.value().iter().as_slice() }).collect::>()[..], &keys.iter().map(|k| k.as_str()) .collect::>()[..], )?; self.dump_mpis(output, &ii, &[rest], &["rest"])?; }, // crypto::mpi::SecretKeyMaterial is non-exhaustive. u => writeln!(output, "{}Unknown variant: {:?}", ii, u)?, } Ok(()) })?; } } SecretKeyMaterial::Encrypted(ref e) => { writeln!(output, "{}", i)?; writeln!(output, "{} Encrypted", ii)?; write!(output, "{} S2K: ", ii)?; self.dump_s2k(output, &ii, e.s2k())?; writeln!(output, "{} Sym. algo: {}", ii, e.algo())?; if self.mpis { if let Ok(ciphertext) = e.ciphertext() { self.dump_mpis(output, &ii, &[ciphertext], &["ciphertext"])?; } } }, } } Ok(()) } pub fn dump_signature(&self, output: &mut dyn io::Write, i: &str, s: &Signature) -> Result<()> { writeln!(output, "{} Version: {}", i, s.version())?; writeln!(output, "{} Type: {}", i, s.typ())?; writeln!(output, "{} Pk algo: {}", i, s.pk_algo())?; writeln!(output, "{} Hash algo: {}", i, s.hash_algo())?; if s.hashed_area().iter().count() > 0 { writeln!(output, "{} Hashed area:", i)?; for pkt in s.hashed_area().iter() { self.dump_subpacket(output, i, pkt, s)?; } } if s.unhashed_area().iter().count() > 0 { writeln!(output, "{} Unhashed area:", i)?; for pkt in s.unhashed_area().iter() { self.dump_subpacket(output, i, pkt, s)?; } } writeln!(output, "{} Digest prefix: {}", i, hex::encode(s.digest_prefix()))?; if let packet::Signature::V6(s) = s { writeln!(output, "{} Salt: {}", i, hex::encode(s.salt()))?; } write!(output, "{} Level: {} ", i, s.level())?; match s.level() { 0 => writeln!(output, "(signature over data)")?, 1 => writeln!(output, "(notarization over signatures \ level 0 and data)")?, n => writeln!(output, "(notarization over signatures \ level <= {} and data)", n - 1)?, } if self.mpis { writeln!(output, "{}", i)?; writeln!(output, "{} Signature:", i)?; let ii = format!("{} ", i); match s.mpis() { mpi::Signature::Ed25519 { s } => self.dump_mpis(output, &ii, &[&s[..]], &["s"])?, mpi::Signature::Ed448 { s } => self.dump_mpis(output, &ii, &[&s[..]], &["s"])?, mpi::Signature::RSA { s } => self.dump_mpis(output, &ii, &[s.value()], &["s"])?, mpi::Signature::DSA { r, s } => self.dump_mpis(output, &ii, &[r.value(), s.value()], &["r", "s"])?, mpi::Signature::ElGamal { r, s } => self.dump_mpis(output, &ii, &[r.value(), s.value()], &["r", "s"])?, mpi::Signature::EdDSA { r, s } => self.dump_mpis(output, &ii, &[r.value(), s.value()], &["r", "s"])?, mpi::Signature::ECDSA { r, s } => self.dump_mpis(output, &ii, &[r.value(), s.value()], &["r", "s"])?, mpi::Signature::Unknown { mpis, rest } => { let keys: Vec = (0..mpis.len()).map( |i| format!("mpi{}", i)).collect(); self.dump_mpis( output, &ii, &mpis.iter().map(|m| { m.value().iter().as_slice() }).collect::>()[..], &keys.iter().map(|k| k.as_str()) .collect::>()[..], )?; self.dump_mpis(output, &ii, &[&rest[..]], &["rest"])?; }, // crypto::mpi::Signature is non-exhaustive. u => writeln!(output, "{}Unknown variant: {:?}", ii, u)?, } } Ok(()) } fn dump_subpacket(&self, output: &mut dyn io::Write, i: &str, s: &Subpacket, sig: &Signature) -> Result<()> { use self::SubpacketValue::*; let hexdump_unknown = |output: &mut dyn io::Write, buf| -> Result<()> { let mut hd = hex::Dumper::new(output, self.indentation_for_hexdump( &format!("{} ", i), 0)); hd.write_labeled(buf, |_, _| None)?; Ok(()) }; #[allow(deprecated)] match s.value() { Unknown { body, .. } => { writeln!(output, "{} {:?}{}:", i, s.tag(), if s.critical() { " (critical)" } else { "" })?; hexdump_unknown(output, body.as_slice())?; }, SignatureCreationTime(t) => write!(output, "{} Signature creation time: {}", i, (*t).convert())?, SignatureExpirationTime(t) => write!(output, "{} Signature expiration time: {} ({})", i, t.convert(), if let Some(creation) = sig.signature_creation_time() { (creation + (*t).into()).convert().to_string() } else { " (no Signature Creation Time subpacket)".into() })?, ExportableCertification(e) => write!(output, "{} Exportable certification: {}", i, e)?, TrustSignature{level, trust} => write!(output, "{} Trust signature: level {} trust {}", i, level, trust)?, RegularExpression(ref r) => write!(output, "{} Regular expression: {}", i, String::from_utf8_lossy(r))?, Revocable(r) => write!(output, "{} Revocable: {}", i, r)?, KeyExpirationTime(t) => write!(output, "{} Key expiration time: {}", i, t.convert())?, PreferredSymmetricAlgorithms(ref c) => write!(output, "{} Symmetric algo preferences: {}", i, c.iter().map(|c| format!("{:?}", c)) .collect::>().join(", "))?, RevocationKey(rk) => { let (pk_algo, fp) = rk.revoker(); write!(output, "{} Revocation key: {}/{}", i, fp, pk_algo)?; if rk.sensitive() { write!(output, ", sensitive")?; } }, Issuer(ref is) => write!(output, "{} Issuer: {}", i, is)?, NotationData(n) => if n.flags().human_readable() { write!(output, "{} Notation: {}", i, n)?; if s.critical() { write!(output, " (critical)")?; } writeln!(output)?; } else { write!(output, "{} Notation: {}", i, n.name())?; let flags = format!("{:?}", n.flags()); if ! flags.is_empty() { write!(output, "{}", flags)?; } if s.critical() { write!(output, " (critical)")?; } writeln!(output)?; hexdump_unknown(output, n.value())?; }, PreferredHashAlgorithms(ref h) => write!(output, "{} Hash preferences: {}", i, h.iter().map(|h| format!("{:?}", h)) .collect::>().join(", "))?, PreferredCompressionAlgorithms(ref c) => write!(output, "{} Compression preferences: {}", i, c.iter().map(|c| format!("{:?}", c)) .collect::>().join(", "))?, KeyServerPreferences(ref p) => write!(output, "{} Keyserver preferences: {:?}", i, p)?, PreferredKeyServer(ref k) => write!(output, "{} Preferred keyserver: {}", i, String::from_utf8_lossy(k))?, PrimaryUserID(p) => write!(output, "{} Primary User ID: {}", i, p)?, PolicyURI(ref p) => write!(output, "{} Policy URI: {}", i, String::from_utf8_lossy(p))?, KeyFlags(ref k) => write!(output, "{} Key flags: {:?}", i, k)?, SignersUserID(ref u) => write!(output, "{} Signer's User ID: {}", i, String::from_utf8_lossy(u))?, ReasonForRevocation{code, ref reason} => { write!(output, "{} Reason for revocation: {}{}{}", i, code, if reason.len() > 0 { ", " } else { "" }, String::from_utf8_lossy(reason))? } Features(ref f) => write!(output, "{} Features: {:?}", i, f)?, SignatureTarget{pk_algo, hash_algo, ref digest} => write!(output, "{} Signature target: {}, {}, {}", i, pk_algo, hash_algo, hex::encode(digest))?, EmbeddedSignature(_) => // Embedded signature is dumped below. write!(output, "{} Embedded signature: ", i)?, IssuerFingerprint(ref fp) => write!(output, "{} Issuer Fingerprint: {}", i, fp)?, IntendedRecipient(ref fp) => write!(output, "{} Intended Recipient: {}", i, fp)?, ApprovedCertifications(digests) => { write!(output, "{} Approved Certifications:", i)?; if digests.is_empty() { writeln!(output, " None")?; } else { writeln!(output)?; for d in digests { writeln!(output, "{} {}", i, hex::encode(d))?; } } }, PreferredAEADCiphersuites(p) => write!(output, "{} AEAD preferences: {}", i, p.iter().map(|(symm, aead)| format!("{:?}+{:?}", symm, aead)) .collect::>().join(", "))?, // SubpacketValue is non-exhaustive. u => writeln!(output, "{} Unknown variant: {:?}", i, u)?, } match s.value() { Unknown { .. } => (), NotationData { .. } => (), EmbeddedSignature(ref sig) => { if s.critical() { write!(output, " (critical)")?; } writeln!(output)?; let indent = format!("{} ", i); write!(output, "{}", indent)?; self.dump_signature(output, &indent, sig)?; }, _ => { if s.critical() { write!(output, " (critical)")?; } writeln!(output)?; } } Ok(()) } fn dump_s2k(&self, output: &mut dyn io::Write, i: &str, s2k: &S2K) -> Result<()> { use self::S2K::*; #[allow(deprecated)] match s2k { Implicit => { writeln!(output, "Implicit")?; }, Simple { hash } => { writeln!(output, "Simple")?; writeln!(output, "{} Hash: {}", i, hash)?; }, Salted { hash, ref salt } => { writeln!(output, "Salted")?; writeln!(output, "{} Hash: {}", i, hash)?; writeln!(output, "{} Salt: {}", i, hex::encode(salt))?; }, Iterated { hash, ref salt, hash_bytes } => { writeln!(output, "Iterated")?; writeln!(output, "{} Hash: {}", i, hash)?; writeln!(output, "{} Salt: {}", i, hex::encode(salt))?; writeln!(output, "{} Hash bytes: {}", i, hash_bytes)?; }, Argon2 { salt, t, p, m } => { writeln!(output, "Argon2")?; writeln!(output, "{} Salt: {}", i, hex::encode(salt))?; writeln!(output, "{} Passes: {}", i, t)?; writeln!(output, "{} Parallelism: {}", i, p)?; writeln!(output, "{} Memory: {} ({} KiB)", i, m, 2usize.pow((*m).into()))?; }, Private { tag, parameters } => { writeln!(output, "Private")?; writeln!(output, "{} Tag: {}", i, tag)?; if let Some(p) = parameters.as_ref() { writeln!(output, "{} Parameters: {:?}", i, p)?; } }, Unknown { tag, parameters } => { writeln!(output, "Unknown")?; writeln!(output, "{} Tag: {}", i, tag)?; if let Some(p) = parameters.as_ref() { writeln!(output, "{} Parameters: {:?}", i, p)?; } }, // S2K is non-exhaustive u => writeln!(output, "{} Unknown variant: {:?}", i, u)?, } Ok(()) } fn dump_mpis(&self, output: &mut dyn io::Write, i: &str, chunks: &[&[u8]], keys: &[&str]) -> Result<()> { assert_eq!(chunks.len(), keys.len()); if chunks.is_empty() { return Ok(()); } let max_key_len = keys.iter().map(|k| k.len()).max().unwrap(); for (chunk, key) in chunks.iter().zip(keys.iter()) { writeln!(output, "{}", i)?; let mut hd = hex::Dumper::new( Vec::new(), self.indentation_for_hexdump(i, max_key_len)); hd.write(*chunk, *key)?; output.write_all(&hd.into_inner())?; } Ok(()) } /// Returns indentation for hex dumps. /// /// Returns a prefix of `i` so that a hexdump with labels no /// longer than `max_label_len` will fit into the target width. fn indentation_for_hexdump(&self, i: &str, max_label_len: usize) -> String { let amount = ::std::cmp::max( 0, ::std::cmp::min( self.width as isize - 63 // Length of address, hex digits, and whitespace. - max_label_len as isize, i.len() as isize), ) as usize; format!("{} ", &i.chars().take(amount).collect::()) } } sequoia-octopus-librnp-1.11.1/src/dump_packets.rs000064400000000000000000000023011046102023000201410ustar 00000000000000//! Implements rnp_dump_packets_to_output. use crate::{ RnpInput, RnpOutput, RnpResult, error::*, }; // XXX: Copied from sequoia-sq. pub mod dump; const RNP_DUMP_MPI: u32 = 1 << 0; const RNP_DUMP_RAW: u32 = 1 << 1; const RNP_DUMP_GRIP: u32 = 1 << 2; #[no_mangle] pub unsafe extern "C" fn rnp_dump_packets_to_output(input: *mut RnpInput, output: *mut RnpOutput, flags: u32) -> RnpResult { rnp_function!(rnp_dump_packets_to_output, crate::TRACE); let input = assert_ptr_mut!(input); let output = assert_ptr_mut!(output); arg!(flags); let dump_mpis = flags & RNP_DUMP_MPI > 0; let dump_hex = flags & RNP_DUMP_RAW > 0; // Key grips are a proprietary GnuPG extension. No. let _dump_grip = flags & RNP_DUMP_GRIP > 0; let max_decompressed_literal_data = if let RnpOutput::Buf((_buf, Some(max))) = output { Some(*max) } else { None }; rnp_try_or!(dump::dump(input, output, max_decompressed_literal_data, dump_mpis, dump_hex, None, None), RNP_ERROR_GENERIC); rnp_success!() } sequoia-octopus-librnp-1.11.1/src/error.rs000064400000000000000000000541271046102023000166300ustar 00000000000000//! Error handling. use std::{ fmt, sync::OnceLock, }; // Like eprintln! macro_rules! log { ($dst:expr $(,)?) => ( $crate::error::log_internal($dst) ); ($dst:expr, $($arg:tt)*) => ( $crate::error::log_internal(std::format!($dst, $($arg)*)) ); } /// Native RNP result type. /// /// This is what the RNP functions return. pub type RnpResult = u32; /// A wrapped RnpResult. /// /// This is the type of our status code constants. By using a type /// distinct from [`RnpResult`], we force a conversion through /// [`RnpStatus::epilogue`] (or [`RnpStatus::quiet_epilogue`]). #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct RnpStatus(RnpResult); impl fmt::UpperHex for RnpStatus { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::UpperHex::fmt(&self.0, f) // Forward. } } impl fmt::Display for RnpStatus { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match *self { RNP_SUCCESS => f.write_str("RNP_SUCCESS"), RNP_ERROR_GENERIC => f.write_str("RNP_ERROR_GENERIC"), RNP_ERROR_BAD_FORMAT => f.write_str("RNP_ERROR_BAD_FORMAT"), RNP_ERROR_BAD_PARAMETERS => f.write_str("RNP_ERROR_BAD_PARAMETERS"), RNP_ERROR_NOT_IMPLEMENTED => f.write_str("RNP_ERROR_NOT_IMPLEMENTED"), RNP_ERROR_NOT_SUPPORTED => f.write_str("RNP_ERROR_NOT_SUPPORTED"), RNP_ERROR_OUT_OF_MEMORY => f.write_str("RNP_ERROR_OUT_OF_MEMORY"), RNP_ERROR_SHORT_BUFFER => f.write_str("RNP_ERROR_SHORT_BUFFER"), RNP_ERROR_NULL_POINTER => f.write_str("RNP_ERROR_NULL_POINTER"), RNP_ERROR_ACCESS => f.write_str("RNP_ERROR_ACCESS"), RNP_ERROR_READ => f.write_str("RNP_ERROR_READ"), RNP_ERROR_WRITE => f.write_str("RNP_ERROR_WRITE"), RNP_ERROR_BAD_STATE => f.write_str("RNP_ERROR_BAD_STATE"), RNP_ERROR_MAC_INVALID => f.write_str("RNP_ERROR_MAC_INVALID"), RNP_ERROR_SIGNATURE_INVALID => f.write_str("RNP_ERROR_SIGNATURE_INVALID"), RNP_ERROR_KEY_GENERATION => f.write_str("RNP_ERROR_KEY_GENERATION"), RNP_ERROR_BAD_PASSWORD => f.write_str("RNP_ERROR_BAD_PASSWORD"), RNP_ERROR_KEY_NOT_FOUND => f.write_str("RNP_ERROR_KEY_NOT_FOUND"), RNP_ERROR_NO_SUITABLE_KEY => f.write_str("RNP_ERROR_NO_SUITABLE_KEY"), RNP_ERROR_DECRYPT_FAILED => f.write_str("RNP_ERROR_DECRYPT_FAILED"), RNP_ERROR_RNG => f.write_str("RNP_ERROR_RNG"), RNP_ERROR_SIGNING_FAILED => f.write_str("RNP_ERROR_SIGNING_FAILED"), RNP_ERROR_NO_SIGNATURES_FOUND => f.write_str("RNP_ERROR_NO_SIGNATURES_FOUND"), RNP_ERROR_SIGNATURE_EXPIRED => f.write_str("RNP_ERROR_SIGNATURE_EXPIRED"), RNP_ERROR_NOT_ENOUGH_DATA => f.write_str("RNP_ERROR_NOT_ENOUGH_DATA"), RNP_ERROR_UNKNOWN_TAG => f.write_str("RNP_ERROR_UNKNOWN_TAG"), RNP_ERROR_PACKET_NOT_CONSUMED => f.write_str("RNP_ERROR_PACKET_NOT_CONSUMED"), RNP_ERROR_NO_USERID => f.write_str("RNP_ERROR_NO_USERID"), RNP_ERROR_EOF => f.write_str("RNP_ERROR_EOF"), n => write!(f, "RNP_ERROR_{:08X}", n), } } } impl RnpStatus { /// Does this status denote success? pub fn success(&self) -> bool { self == &RNP_SUCCESS } /// On failure, prints a trace message in debug builds. /// /// Returns the [`RnpResult`] suitable as return value for RNP /// functions. pub fn epilogue(&self, name: &str, args: Vec) -> RnpResult { if ! self.success() || call_tracing() || full_tracing() { if cfg!(debug_assertions) { log!("sequoia-octopus: TRACE: {}({}) => {}", name, args.join(", "), self) } } self.quiet_epilogue() } /// Does not print a trace message. /// /// Returns the [`RnpResult`] suitable as return value for RNP /// functions. pub fn quiet_epilogue(&self) -> RnpResult { self.0 } #[cfg(test)] pub const fn as_u32(&self) -> u32 { self.0 } } pub const RNP_SUCCESS: RnpStatus = RnpStatus(0x00000000); // Common error codes pub const RNP_ERROR_GENERIC: RnpStatus = RnpStatus(0x10000000); pub const RNP_ERROR_BAD_FORMAT: RnpStatus = RnpStatus(0x10000001); pub const RNP_ERROR_BAD_PARAMETERS: RnpStatus = RnpStatus(0x10000002); pub const RNP_ERROR_NOT_IMPLEMENTED: RnpStatus = RnpStatus(0x10000003); pub const RNP_ERROR_NOT_SUPPORTED: RnpStatus = RnpStatus(0x10000004); pub const RNP_ERROR_OUT_OF_MEMORY: RnpStatus = RnpStatus(0x10000005); pub const RNP_ERROR_SHORT_BUFFER: RnpStatus = RnpStatus(0x10000006); pub const RNP_ERROR_NULL_POINTER: RnpStatus = RnpStatus(0x10000007); // Storage pub const RNP_ERROR_ACCESS: RnpStatus = RnpStatus(0x11000000); pub const RNP_ERROR_READ: RnpStatus = RnpStatus(0x11000001); pub const RNP_ERROR_WRITE: RnpStatus = RnpStatus(0x11000002); // Crypto pub const RNP_ERROR_BAD_STATE: RnpStatus = RnpStatus(0x12000000); pub const RNP_ERROR_MAC_INVALID: RnpStatus = RnpStatus(0x12000001); pub const RNP_ERROR_SIGNATURE_INVALID: RnpStatus = RnpStatus(0x12000002); pub const RNP_ERROR_KEY_GENERATION: RnpStatus = RnpStatus(0x12000003); pub const RNP_ERROR_BAD_PASSWORD: RnpStatus = RnpStatus(0x12000004); pub const RNP_ERROR_KEY_NOT_FOUND: RnpStatus = RnpStatus(0x12000005); pub const RNP_ERROR_NO_SUITABLE_KEY: RnpStatus = RnpStatus(0x12000006); pub const RNP_ERROR_DECRYPT_FAILED: RnpStatus = RnpStatus(0x12000007); pub const RNP_ERROR_RNG: RnpStatus = RnpStatus(0x12000008); pub const RNP_ERROR_SIGNING_FAILED: RnpStatus = RnpStatus(0x12000009); pub const RNP_ERROR_NO_SIGNATURES_FOUND: RnpStatus = RnpStatus(0x1200000a); pub const RNP_ERROR_SIGNATURE_EXPIRED: RnpStatus = RnpStatus(0x1200000b); // Parsing pub const RNP_ERROR_NOT_ENOUGH_DATA: RnpStatus = RnpStatus(0x13000000); pub const RNP_ERROR_UNKNOWN_TAG: RnpStatus = RnpStatus(0x13000001); pub const RNP_ERROR_PACKET_NOT_CONSUMED: RnpStatus = RnpStatus(0x13000002); pub const RNP_ERROR_NO_USERID: RnpStatus = RnpStatus(0x13000003); pub const RNP_ERROR_EOF: RnpStatus = RnpStatus(0x13000004); /// Rustic-errors resembling the native RNP errors. /// /// These errors can be used in functions returning standard errors to /// return a specific native RNP error. /// /// # Examples /// /// ```rust,no-compile /// #[no_mangle] pub unsafe extern "C" /// fn rnp_something(rnp_key: *mut RnpKey) -> RnpResult { /// rnp_function!(rnp_key_protect, crate::TRACE); /// /// let f = || -> openpgp::Result<()> { /// Err(Error::NotImplemented) /// }; /// /// rnp_return!(f()) /// } /// ``` #[derive(thiserror::Error, Debug, Clone)] pub enum Error { #[error("Generic")] Generic, #[error("BadFormat")] BadFormat, #[error("BadParameters")] BadParameters, #[error("NotImplemented")] NotImplemented, #[error("NotSupported")] NotSupported, #[error("OutOfMemory")] OutOfMemory, #[error("ShortBuffer")] ShortBuffer, #[error("NullPointer")] NullPointer, #[error("Access")] Access, #[error("Read")] Read, #[error("Write")] Write, #[error("BadState")] BadState, #[error("MacInvalid")] MacInvalid, #[error("SignatureInvalid")] SignatureInvalid, #[error("KeyGeneration")] KeyGeneration, #[error("BadPassword")] BadPassword, #[error("KeyNotFound")] KeyNotFound, #[error("NoSuitableKey")] NoSuitableKey, #[error("DecryptFailed")] DecryptFailed, #[error("RNG")] RNG, #[error("SigningFailed")] SigningFailed, #[error("NoSignaturesFound")] NoSignaturesFound, #[error("SignatureExpired")] SignatureExpired, #[error("NotEnoughData")] NotEnoughData, #[error("UnknownTag")] UnknownTag, #[error("PacketNotConsumed")] PacketNotConsumed, #[error("NoUserID")] NoUserID, #[error("EOF")] EOF, } impl From for RnpStatus { fn from(e: Error) -> RnpStatus { use Error::*; match e { Generic => RNP_ERROR_GENERIC, BadFormat => RNP_ERROR_BAD_FORMAT, BadParameters => RNP_ERROR_BAD_PARAMETERS, NotImplemented => RNP_ERROR_NOT_IMPLEMENTED, NotSupported => RNP_ERROR_NOT_SUPPORTED, OutOfMemory => RNP_ERROR_OUT_OF_MEMORY, ShortBuffer => RNP_ERROR_SHORT_BUFFER, NullPointer => RNP_ERROR_NULL_POINTER, Access => RNP_ERROR_ACCESS, Read => RNP_ERROR_READ, Write => RNP_ERROR_WRITE, BadState => RNP_ERROR_BAD_STATE, MacInvalid => RNP_ERROR_MAC_INVALID, SignatureInvalid => RNP_ERROR_SIGNATURE_INVALID, KeyGeneration => RNP_ERROR_KEY_GENERATION, BadPassword => RNP_ERROR_BAD_PASSWORD, KeyNotFound => RNP_ERROR_KEY_NOT_FOUND, NoSuitableKey => RNP_ERROR_NO_SUITABLE_KEY, DecryptFailed => RNP_ERROR_DECRYPT_FAILED, RNG => RNP_ERROR_RNG, SigningFailed => RNP_ERROR_SIGNING_FAILED, NoSignaturesFound => RNP_ERROR_NO_SIGNATURES_FOUND, SignatureExpired => RNP_ERROR_SIGNATURE_EXPIRED, NotEnoughData => RNP_ERROR_NOT_ENOUGH_DATA, UnknownTag => RNP_ERROR_UNKNOWN_TAG, PacketNotConsumed => RNP_ERROR_PACKET_NOT_CONSUMED, NoUserID => RNP_ERROR_NO_USERID, EOF => RNP_ERROR_EOF, } } } // Used by helper functions. pub type Result = std::result::Result; //#[cfg(windows)] pub fn log_internal>(text: T) { let text = format!("{}: {}", chrono::offset::Utc::now().format("%T"), text.as_ref()); if cfg!(windows) { // Save messages to a log file in the current profile's // directory (.../.thunderbird/$PROFILE/octopus.log). // // This is a bit hairy, because the code needs to be // reentrant: to initialize the logger's file description, we // need the location of the current profile, but finding that // location also uses the logging functionality. // // To break this cycle, if the logger is locked, rather than // wait for the lock, we simply enqueue the message in a // channel. Then when we actually have the lock, we first // print any messages queued in the channel and then print out // our own message. use std::fs::File; use std::io::Write; use std::ops::DerefMut; use std::sync::Mutex; use std::sync::mpsc::channel; use std::sync::mpsc::Sender; use std::sync::mpsc::Receiver; use crate::tbprofile::TBProfile; struct State { sender: Mutex>, // If None, the file has not yet been opened. output: Mutex, Option)>>, } static LOGGER: OnceLock = OnceLock::new(); let logger = LOGGER.get_or_init( || { let (sender, receiver) = channel(); State { sender: Mutex::new(sender), output: Mutex::new(Some((receiver, None))), } }); let mut logged = false; if let Ok(mut guard) = logger.output.try_lock() { // We got the lock. // If initialization fails, we set output to None. But // since it is borrowed, we need to delay it. let mut kill = false; if let Some((receiver, ref mut ofd)) = guard.deref_mut() { if ofd.is_none() { // We need to initialize the file descriptor. if let Some(tbpath) = TBProfile::path() { // We found a TB profile. Let's try to open the // log file. let path = tbpath.join("octopus.log"); if let Ok(fd) = File::create(&path) { *ofd = Some(fd); eprintln!("Logging to {:?}", path); } else { // We failed to open the file :/ kill = true; } } else { // We failed to find the TBProfile :/ kill = true; } } if let Some(fd) = ofd { // First, drain the message queue. while let Ok(text) = receiver.try_recv() { let _ = writeln!(fd, "{}", text); } // Then print our own message. let _ = writeln!(fd, "{}", text); let _ = fd.flush(); logged = true; } } if kill { *guard = None; } } else { // Locked. Enqueue the message for later. If we can't // send, it means initialization failed so just ignore. if let Ok(_) = logger.sender.lock().unwrap().send(text.clone()) { logged = true; } } if ! logged { // Something went wrong. Just send it to stderr, which // probably won't do anything on Windows, but if we are // debugging on another platform, that will be useful. eprintln!("{}", text); } } else { // Just write to stderr. eprintln!("{}", text); } } pub fn full_tracing() -> bool { static FULL_TRACING: OnceLock = OnceLock::new(); *FULL_TRACING.get_or_init( || std::env::var("SEQUOIA_OCTOPUS_TRACING") .map(|t| t == "full") .unwrap_or(false)) } pub fn call_tracing() -> bool { static FULL_TRACING: OnceLock = OnceLock::new(); *FULL_TRACING.get_or_init( || std::env::var("SEQUOIA_OCTOPUS_TRACING") .map(|t| t == "call") .unwrap_or(false)) } macro_rules! rnp_function { ( $fn_name: path, $TRACE: expr ) => { #[allow(dead_code, unused_mut, unused_variables)] let mut args: Vec = Vec::new(); #[allow(unused_macros)] macro_rules! arg { ($arg: expr) => { args.push(format!("{:?}", $arg)) }; } #[allow(unused_macros)] macro_rules! rnp_return_status { ($status: expr) => { return $status.epilogue(stringify!($fn_name), args) }; } #[allow(unused_macros)] macro_rules! rnp_success { () => { rnp_return_status!(RNP_SUCCESS) }; } #[allow(unused_macros)] macro_rules! _trace { ( $msg: expr ) => { if $TRACE && crate::error::full_tracing() { log!("sequoia-octopus: TRACE: {}: {}", stringify!($fn_name), $msg); } }; } // Currently, Rust doesn't support $( ... ) in a nested // macro's definition. See: // https://users.rust-lang.org/t/nested-macros-issue/8348/2 #[allow(unused_macros)] macro_rules! t { ( $fmt:expr ) => { _trace!( $fmt) }; ( $fmt:expr, $a:expr ) => { _trace!( format!($fmt, $a)) }; ( $fmt:expr, $a:expr, $b:expr ) => { _trace!( format!($fmt, $a, $b)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr ) => { _trace!( format!($fmt, $a, $b, $c)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr ) => { _trace!( format!($fmt, $a, $b, $c, $d)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr ) => { _trace!( format!($fmt, $a, $b, $c, $d, $e)) }; ( $fmt:expr, $a:expr, $b:expr, $c:expr, $d:expr, $e:expr, $f:expr ) => { _trace!( 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!( 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!( 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!( 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!( 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!( format!($fmt, $a, $b, $c, $d, $e, $f, $g, $h, $i, $j, $k)) }; } #[allow(unused_macros)] macro_rules! warn { // Currently, Rust doesn't support $( ... ) in a nested // macro's definition. See: // https://users.rust-lang.org/t/nested-macros-issue/8348/2 // //( $fmt: expr $(, $a: expr )* ) => { // eprintln!(concat!("sequoia-octopus: ", // stringify!($fn_name), // ": ", $fmt) // $(, $a )*); //}; ( $fmt: expr ) => { log!(concat!("sequoia-octopus: ", stringify!($fn_name), ": ", $fmt)); }; ( $fmt: expr, $a: expr ) => { log!(concat!("sequoia-octopus: ", stringify!($fn_name), ": ", $fmt), $a); }; ( $fmt: expr, $a: expr, $b: expr ) => { log!(concat!("sequoia-octopus: ", stringify!($fn_name), ": ", $fmt), $a, $b); }; ( $fmt: expr, $a: expr, $b: expr, $c: expr ) => { log!(concat!("sequoia-octopus: ", stringify!($fn_name), ": ", $fmt), $a, $b, $c); }; } #[allow(unused_macros)] macro_rules! assert_ptr_int { ( $param: expr ) => { if $param.is_null() { warn!("parameter {:?} is NULL", stringify!($param)); rnp_return_status!(crate::error::RNP_ERROR_NULL_POINTER); } }; } #[allow(unused_macros)] macro_rules! assert_ptr { ( $param: expr ) => { arg!($param); assert_ptr_int!($param); }; } #[allow(unused_macros)] macro_rules! assert_ptr_ref { ( $param: expr ) => {{ assert_ptr!($param); &*$param }}; } #[allow(unused_macros)] macro_rules! assert_ptr_mut { ( $param: expr ) => {{ assert_ptr!($param); &mut *$param }}; } #[allow(unused_macros)] macro_rules! assert_str { ( $param: expr ) => { assert_str!($param, false) }; ( confidential => $param: expr ) => { assert_str!($param, true) }; ( $param: expr, $confidential: expr ) => {{ assert_ptr_int!($param); match std::ffi::CStr::from_ptr($param).to_str() { Ok(s) => { if $confidential { arg!("") } else { arg!(s); } s }, Err(e) => { warn!("parameter {:?} is not UTF8: {}", stringify!($param), e); rnp_return_status!(crate::error::RNP_ERROR_BAD_PARAMETERS); } } }}; } #[allow(unused_macros)] macro_rules! rnp_try { ( $result: expr ) => { match $result { Ok(v) => v, Err(e) => rnp_return_status!(e), } }; } #[allow(unused_macros)] macro_rules! rnp_try_or { ( $result: expr, $fail_with: expr ) => { match $result { Ok(v) => v, Err(_) => { let s: crate::error::RnpStatus = $fail_with; rnp_return_status!(s); }, } }; } #[allow(unused_macros)] macro_rules! rnp_return { ( $expr: expr ) => { rnp_return_status!(if let Err(e) = $expr { warn!("{}", e); if let Ok(e) = e.downcast::() { e.into() } else { crate::error::RNP_ERROR_GENERIC } } else { t!("Leaving function: success"); crate::error::RNP_SUCCESS }) }; } #[allow(unused_macros)] macro_rules! thunderbird_workaround { () => {{ if crate::THUNDERBIRD_WORKAROUND { t!("Thunderbird-specific workaround in {}:{}", file!(), line!()); } crate::THUNDERBIRD_WORKAROUND }}; } if crate::error::full_tracing() { t!("Entering function"); } }; } macro_rules! global_warn { ( $fmt: expr $(, $a: expr )* ) => { log!(concat!("sequoia-octopus: ", $fmt) $(, $a )*) }; } macro_rules! global_rnp_try_or { ( $result: expr, $fail_with: expr ) => { match $result { Ok(v) => v, Err(e) => { global_warn!("{}", e); return $fail_with; }, } }; } sequoia-octopus-librnp-1.11.1/src/flags.rs000064400000000000000000000063021046102023000165630ustar 00000000000000// Key export flags. pub type RnpKeyExportFlags = u32; pub const RNP_KEY_EXPORT_ARMORED: RnpKeyExportFlags = 1 << 0; pub const RNP_KEY_EXPORT_PUBLIC: RnpKeyExportFlags = 1 << 1; pub const RNP_KEY_EXPORT_SECRET: RnpKeyExportFlags = 1 << 2; pub const RNP_KEY_EXPORT_SUBKEYS: RnpKeyExportFlags = 1 << 3; // Key remove flags. pub type RnpKeyRemoveFlags = u32; pub const RNP_KEY_REMOVE_PUBLIC: RnpKeyRemoveFlags = 1 << 0; pub const RNP_KEY_REMOVE_SECRET: RnpKeyRemoveFlags = 1 << 1; pub const RNP_KEY_REMOVE_SUBKEYS: RnpKeyRemoveFlags = 1 << 2; // Key unload flags. pub type RnpKeyUnloadFlags = u32; pub const RNP_KEY_UNLOAD_PUBLIC: RnpKeyUnloadFlags = 1 << 0; pub const RNP_KEY_UNLOAD_SECRET: RnpKeyUnloadFlags = 1 << 1; // Flags for optional details to include in JSON. pub type RnpJsonFlags = u32; pub const RNP_JSON_PUBLIC_MPIS: RnpJsonFlags = 1 << 0; pub const RNP_JSON_SECRET_MPIS: RnpJsonFlags = 1 << 1; pub const RNP_JSON_SIGNATURES: RnpJsonFlags = 1 << 2; pub const RNP_JSON_SIGNATURE_MPIS: RnpJsonFlags = 1 << 3; // Flags to include additional data in packet dumping. pub type RnpJsonDumpFlags = u32; pub const RNP_JSON_DUMP_MPI: RnpJsonDumpFlags = 1 << 0; pub const RNP_JSON_DUMP_RAW: RnpJsonDumpFlags = 1 << 1; pub const RNP_JSON_DUMP_GRIP: RnpJsonDumpFlags = 1 << 2; pub type RnpDumpFlags = u32; pub const RNP_DUMP_MPI: RnpDumpFlags = 1 << 0; pub const RNP_DUMP_RAW: RnpDumpFlags = 1 << 1; pub const RNP_DUMP_GRIP: RnpDumpFlags = 1 << 2; // Flags for the key loading/saving functions. pub type RnpLoadSaveFlags = u32; pub const RNP_LOAD_SAVE_PUBLIC_KEYS: RnpLoadSaveFlags = 1 << 0; pub const RNP_LOAD_SAVE_SECRET_KEYS: RnpLoadSaveFlags = 1 << 1; pub const RNP_LOAD_SAVE_PERMISSIVE: RnpLoadSaveFlags = 1 << 8; pub const RNP_LOAD_SAVE_SINGLE: RnpLoadSaveFlags = 1 << 9; // Flags for output structure creation. pub type RnpOutputFlags = u32; pub const RNP_OUTPUT_FILE_OVERWRITE: RnpOutputFlags = 1 << 0; pub const RNP_OUTPUT_FILE_RANDOM: RnpOutputFlags = 1 << 1; // User id type. pub type RnpUserIDType = u32; pub const RNP_USER_ID: RnpUserIDType = 1; pub const RNP_USER_ATTR: RnpUserIDType = 2; // Flags for rnp_key_remove_signatures. pub type RnpKeyRemoveSignatureFlags = u32; pub const RNP_KEY_SIGNATURE_INVALID: RnpKeyRemoveSignatureFlags = 1; pub const RNP_KEY_SIGNATURE_UNKNOWN_KEY: RnpKeyRemoveSignatureFlags = 2; pub const RNP_KEY_SIGNATURE_NON_SELF_SIG: RnpKeyRemoveSignatureFlags = 4; pub type RnpKeyRemoveSignatureAction = u32; pub const RNP_KEY_SIGNATURE_KEEP: RnpKeyRemoveSignatureAction = 0; pub const RNP_KEY_SIGNATURE_REMOVE: RnpKeyRemoveSignatureAction = 1; // Flags for rnp_op_encrypt_set_flags. pub type RnpEncryptFlags = u32; pub const RNP_ENCRYPT_NOWRAP: RnpEncryptFlags = 1 << 0; // Flags for rnp_*_security_rule pub type RnpSecurityFlags = u32; pub const RNP_SECURITY_OVERRIDE: RnpSecurityFlags = 1 << 0; pub const RNP_SECURITY_VERIFY_KEY: RnpSecurityFlags = 1 << 1; pub const RNP_SECURITY_VERIFY_DATA: RnpSecurityFlags = 1 << 2; pub const RNP_SECURITY_REMOVE_ALL: RnpSecurityFlags = 1 << 16; pub type RnpSecurityLevel = u32; pub const RNP_SECURITY_PROHIBITED: RnpSecurityLevel = 0; pub const RNP_SECURITY_INSECURE: RnpSecurityLevel = 1; pub const RNP_SECURITY_DEFAULT: RnpSecurityLevel = 2; sequoia-octopus-librnp-1.11.1/src/gpg.rs000064400000000000000000001050301046102023000162420ustar 00000000000000// Copyright 2019-2020 Heiko Schaefer // // This file is part of OpenPGP CA // https://gitlab.com/openpgp-ca/openpgp-ca // // SPDX-FileCopyrightText: 2019-2020 Heiko Schaefer , 2019-2021 pep Foundation // SPDX-License-Identifier: LGPL-2.0-or-later use std::collections::{BTreeMap, HashMap}; use std::fmt; use std::io::Write; use std::path::{Path, PathBuf}; use std::process::Command; use std::process::Stdio; use std::time::SystemTime; use anyhow::{Context, Result}; use csv::StringRecord; use sequoia_openpgp as openpgp; use openpgp::{ Cert, Fingerprint, packet::{Key, key::PublicParts, key::UnspecifiedRole}, policy::Policy, types::HashAlgorithm, }; use sequoia_gpg_agent::gnupg; /// Controls tracing in this module. const TRACE: bool = crate::TRACE; // Setting this to false will cause the creation of a context to fail. const ENABLE_GPG: bool = true; /// Errors used in this module. /// /// Note: This enum cannot be exhaustively matched to allow future /// extensions. #[non_exhaustive] #[derive(thiserror::Error, Debug, Clone)] pub enum Error { /// The output is unchanged. /// /// Returns the new cache tag. Use it to update the stored tag. /// This way, if a file is modified but the output is the same, /// you will store the new modification time. #[error("Output unchanged")] Unchanged(CacheTag), } #[derive(Debug, Clone)] pub struct CacheTag { /// Best effort size and modification time of the keyrings and /// trust databases. metadata: Vec<(&'static str, u64, SystemTime)>, /// Hash digest of the gpg output. hash: Vec, } /// SHA512 is almost twice as fast as SHA256 on 64-bit architectures /// because it operates on 64-bit words. #[cfg(target_pointer_width = "64")] const OUTPUT_HASH_ALGO: HashAlgorithm = HashAlgorithm::SHA512; /// But, on 32-bit architectures, pick the hash that fits the word /// size as it will be slightly more efficient. #[cfg(target_pointer_width = "32")] const OUTPUT_HASH_ALGO: HashAlgorithm = HashAlgorithm::SHA256; impl CacheTag { /// Creates a new CacheTag for the given data. fn new(ctx: &Ctx) -> Result { let homedir: PathBuf = ctx.directory("homedir")?.into(); let metadata = [ "pubring.gpg", "pubring.kbx", "private-keys-v1.d", "trustdb.gpg", "tofu.db", ].iter() .filter_map(|name| { std::fs::metadata(homedir.join(name)).ok().map(|m| (name, m)) }) .filter_map(|(name, m)| { m.modified().ok().map(|mtime| (*name, m.len(), mtime)) }) .collect(); Ok(CacheTag { metadata, hash: vec![], }) } /// Hashes the output. fn hash_output(mut self, output: &[u8]) -> Result { let mut output_hasher = OUTPUT_HASH_ALGO.context()?.for_digest(); output_hasher.update(&output); self.hash = output_hasher.into_digest()?; Ok(self) } /// Steals the output hash from `other`. /// /// Use this to copy the hash digest from the stored tag when /// short-circuiting on the modification time. fn hash_steal(mut self, other: &Self) -> Self { self.hash = other.hash.clone(); self } /// Compares the two sets of file metadata. /// /// If either metadata is not available, this returns false. fn metadata_eq(&self, other: &CacheTag) -> bool { self.metadata.len() > 0 && other.metadata.len() > 0 && self.metadata == other.metadata } /// Compares the two hashes. /// /// If either digest is not available, this returns false. fn hash_eq(&self, other: &CacheTag) -> bool { self.hash.len() > 0 && other.hash.len() > 0 && self.hash == other.hash } } pub fn make_context() -> Result { let ctx = Ctx::ephemeral().context( "SKIP: Failed to create GnuPG context. Is GnuPG installed?", )?; ctx.start("gpg-agent").context( "SKIP: Failed to to start gpg-agent. Is the GnuPG agent installed?", )?; Ok(ctx) } /// Creates a KeyPair and makes it prompt for a password using the /// context provided by the given cert. pub fn agent_keypair(policy: &dyn Policy, cert: &Option, key: &Key) -> openpgp::Result { let agent = gnupg::Context::new()?; let mut s = sequoia_gpg_agent::KeyPair::new_for_gnupg_context(&agent, key)?; if let Some(cert) = cert.as_ref() { if let Ok(vcert) = cert.with_policy(policy, None) { s = s.with_cert(&vcert); } } Ok(s) } /// A GnuPG context. #[derive(Debug)] pub struct Ctx { homedir: Option, components: BTreeMap, directories: BTreeMap, sockets: BTreeMap, #[allow(dead_code)] // We keep it around for the cleanup. ephemeral: Option, } impl Ctx { /// Creates a new context for the default GnuPG home directory. pub fn new() -> Result { Self::make(None, None) } /// Creates a new context for the given GnuPG home directory. pub fn with_homedir

(homedir: P) -> Result where P: AsRef, { Self::make(Some(homedir.as_ref()), None) } /// Creates a new ephemeral context. /// /// The created home directory will be deleted once this object is /// dropped. pub fn ephemeral() -> Result { Self::make(None, Some(tempfile::tempdir()?)) } /// don't delete home directory. /// this is intended for manually debugging data that was created in a /// test-run. pub fn leak_tempdir(&mut self) -> Option { if self.ephemeral.is_some() { let _ = self.stop_all(); let _ = self.remove_socket_dir(); } self.ephemeral.take().map(tempfile::TempDir::into_path) } fn make( homedir: Option<&Path>, ephemeral: Option, ) -> Result { if ! ENABLE_GPG { return Err(anyhow::anyhow!("gpg disabled at compile time")); } let mut components: BTreeMap = Default::default(); let mut directories: BTreeMap = Default::default(); let mut sockets: BTreeMap = Default::default(); let homedir: Option = ephemeral .as_ref() .map(|tmp| tmp.path()) .or(homedir) .map(|p| p.into()); for fields in Self::gpgconf(&homedir, &["--list-components"], 3)?.into_iter() { components.insert( String::from_utf8(fields[0].clone())?, String::from_utf8(fields[2].clone())?.into(), ); } for fields in Self::gpgconf(&homedir, &["--list-dirs"], 2)?.into_iter() { let (mut key, value) = (fields[0].clone(), fields[1].clone()); if key.ends_with(b"-socket") { let l = key.len(); key.truncate(l - b"-socket".len()); sockets.insert( String::from_utf8(key)?, String::from_utf8(value)?.into(), ); } else { directories.insert( String::from_utf8(key)?, String::from_utf8(value)?.into(), ); } } Ok(Ctx { homedir, components, directories, sockets, ephemeral, }) } fn gpgconf( homedir: &Option, arguments: &[&str], nfields: usize, ) -> Result>>> { let nl = |&c: &u8| c as char == '\n'; let colon = |&c: &u8| c as char == ':'; let mut gpgconf = new_background_command("gpgconf"); if let Some(homedir) = homedir { gpgconf.arg("--homedir").arg(homedir); // https://dev.gnupg.org/T4496 gpgconf.env("GNUPGHOME", homedir); } for argument in arguments { gpgconf.arg(argument); } let output = gpgconf.output().map_err(|e| -> anyhow::Error { GnupgError::GPGConf(e.to_string()).into() })?; if output.status.success() { let mut result = Vec::new(); for line in output.stdout.split(nl) { if line.is_empty() { // EOF. break; } let fields = line .splitn(nfields, colon) .map(|f| f.to_vec()) .collect::>(); if fields.len() != nfields { return Err(GnupgError::GPGConf(format!( "Malformed response, expected {} fields, \ on line: {:?}", nfields, line )) .into()); } result.push(fields); } Ok(result) } else { Err(GnupgError::GPGConf( String::from_utf8_lossy(&output.stderr).into_owned(), ) .into()) } } /// Returns the path to a GnuPG component. pub fn component(&self, component: C) -> Result<&Path> where C: AsRef, { self.components .get(component.as_ref()) .map(|p| p.as_path()) .ok_or_else(|| { GnupgError::GPGConf(format!( "No such component {:?}", component.as_ref() )) .into() }) } /// Returns the path to a GnuPG directory. pub fn directory(&self, directory: C) -> Result<&Path> where C: AsRef, { self.directories .get(directory.as_ref()) .map(|p| p.as_path()) .ok_or_else(|| { GnupgError::GPGConf(format!( "No such directory {:?}", directory.as_ref() )) .into() }) } /// Returns the path to a GnuPG socket. pub fn socket(&self, socket: C) -> Result<&Path> where C: AsRef, { self.sockets .get(socket.as_ref()) .map(|p| p.as_path()) .ok_or_else(|| { GnupgError::GPGConf(format!( "No such socket {:?}", socket.as_ref() )) .into() }) } /// Creates directories for RPC communication. pub fn create_socket_dir(&self) -> Result<()> { Self::gpgconf(&self.homedir, &["--create-socketdir"], 1)?; Ok(()) } /// Removes directories for RPC communication. /// /// Note: This will stop all servers once they note that their /// socket is gone. pub fn remove_socket_dir(&self) -> Result<()> { Self::gpgconf(&self.homedir, &["--remove-socketdir"], 1)?; Ok(()) } /// Starts a GnuPG component. pub fn start(&self, component: &str) -> Result<()> { self.create_socket_dir()?; Self::gpgconf(&self.homedir, &["--launch", component], 1)?; Ok(()) } /// Stops a GnuPG component. pub fn stop(&self, component: &str) -> Result<()> { Self::gpgconf(&self.homedir, &["--kill", component], 1)?; Ok(()) } /// Stops all GnuPG components. pub fn stop_all(&self) -> Result<()> { self.stop("all") } } impl Drop for Ctx { fn drop(&mut self) { if self.ephemeral.is_some() { let _ = self.stop_all(); let _ = self.remove_socket_dir(); } } } impl std::error::Error for GnupgError {} impl fmt::Display for GnupgError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { GnupgError::GPGConf(s) => write!(f, "gpgconf: {}", s), GnupgError::OperationFailed(s) => { write!(f, "Operation failed: {}", s) } GnupgError::ProtocolError(s) => { write!(f, "Protocol violation: {}", s) } } } } #[derive(Debug)] /// Errors used in this module. pub enum GnupgError { /// Errors related to `gpgconf`. GPGConf(String), /// The remote operation failed. OperationFailed(String), /// The remote party violated the protocol. ProtocolError(String), } pub fn import(ctx: &Ctx, what: &[u8]) -> Result<()> { let doit = || -> Result<()> { let mut gpg = new_background_command("gpg") .stdin(Stdio::piped()) .stdout(Stdio::null()) .stderr(Stdio::null()) .arg("--homedir") .arg(ctx.directory("homedir")?) .arg("--import") .spawn()?; gpg.stdin.as_mut().expect("attached").write_all(what)?; gpg.wait()?; Ok(()) }; doit().context("Running gpg --import")?; Ok(()) } pub fn export(ctx: &Ctx, search: Option<&str>, if_changed: Option<&CacheTag>) -> Result<(Vec, CacheTag)> { let doit = || -> Result<(Vec, CacheTag)> { let tag = CacheTag::new(ctx)?; if let Some(if_changed) = if_changed { if tag.metadata_eq(if_changed) { // Result is unchanged. return Err(Error::Unchanged(tag.hash_steal(if_changed)).into()); } } let mut gpg = new_background_command("gpg"); gpg .stdin(Stdio::null()) .stdout(Stdio::piped()) .stderr(Stdio::null()) .arg("--homedir") .arg(ctx.directory("homedir")?) .arg("--export-options") .arg("export-local-sigs") .arg("--export"); if let Some(search) = search { gpg.arg(search); } let gpg = gpg.output()?; let tag = tag.hash_output(&gpg.stdout)?; if let Some(if_changed) = if_changed { if tag.hash_eq(if_changed) { // Result is unchanged. return Err(Error::Unchanged(tag).into()); } } Ok((gpg.stdout, tag)) }; Ok(doit()?) } // Execute gpg --export-ownertrust and parse its output. It looks // like this: // // # List of assigned trustvalues, created Thu 16 Dec 2021 09:33:48 AM CET // # (Use "gpg --import-ownertrust" to restore them) // B58FC4B77C26A52287E10F0DD70D5F58603CD078:4: // 141198894E27D44F7084F098C0A4CBB987978569:4: // // The values are the fingerprint's ownertrust. This is a private API // (https://lists.gnupg.org/pipermail/gnupg-users/2016-April/055722.html), // but according to the source code (gnupg/g10/trustdb.h): // // #define TRUST_UNKNOWN 0 /* o: not yet calculated/assigned */ // #define TRUST_EXPIRED 1 /* e: calculation may be invalid */ // #define TRUST_UNDEFINED 2 /* q: not enough information for calculation */ // #define TRUST_NEVER 3 /* n: never trust this pubkey */ // #define TRUST_MARGINAL 4 /* m: marginally trusted */ // #define TRUST_FULLY 5 /* f: fully trusted */ // #define TRUST_ULTIMATE 6 /* u: ultimately trusted */ #[enumber::convert] #[derive(Debug, PartialEq, Eq)] pub enum OwnerTrust { Unknown = 0, Expired = 1, Undefined = 2, Never = 3, Marginal = 4, Fully = 5, Ultimate = 6, Other(usize), } pub fn export_ownertrust() -> Result> { // t!("export_ownertrust"); let mut command = Command::new("gpg"); #[cfg(windows)] let mut command = { use std::os::windows::process::CommandExt; // see https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags const CREATE_NO_WINDOW: u32 = 0x08000000; command.creation_flags(CREATE_NO_WINDOW); command }; command .stdin(Stdio::null()) .stdout(Stdio::piped()) .stderr(Stdio::null()) .arg("--export-ownertrust"); let result = command.output()?; // The output should be ASCII characters. So this conversion // should work. If not, abort. let output = String::from_utf8(result.stdout)?; let mut db = HashMap::new(); for line in output.split('\n') { if line == "" { continue; } if let Some('#') = line.chars().next() { // Ignore comments. // t!("Ignoring comment: {:?}", line); continue; } let mut fields = line.split(':'); let fingerprint = if let Some(fingerprint) = fields.next() { fingerprint } else { // t!("Invalid line: {:?}", line); continue; }; let value = if let Some(value) = fields.next() { value } else { // t!("Invalid line: {:?}", line); continue; }; match (fingerprint.parse::(), value.parse::()) { (Ok(fpr), Ok(field)) => { let ownertrust: OwnerTrust = field.into(); // t!("{} => {:?}", fpr, ownertrust); db.insert(fpr, ownertrust); } (Err(_err), _) => { // t!("Failed to parse fingerprint: {}", err); } (_, Err(_err)) => { // t!("Failed to parse ownertrust: {}", err); } } } Ok(db) } fn new_background_command(program: S) -> Command where S: AsRef, { let command = Command::new(program); #[cfg(windows)] let command = { use std::os::windows::process::CommandExt; // see https://docs.microsoft.com/en-us/windows/win32/procthread/process-creation-flags const CREATE_NO_WINDOW: u32 = 0x08000000; let mut command = command; command.creation_flags(CREATE_NO_WINDOW); command }; command } // If if_changed is not None, this hashes the output before parsing. // If the output's hash (as returned by a previous call to this // function) matches if_changed, this function returns an error. fn list_keys_raw(ctx: &Ctx, secret: bool, filter: Option, if_changed: Option<&CacheTag>) -> Result<(Vec, CacheTag)> { rnp_function!(gpg::list_keys_raw, false); t!("filter: {:?}", filter); t!("if_chnaged: {:?}", if_changed); let doit = || -> Result<_> { let tag = CacheTag::new(ctx)?; if let Some(if_changed) = if_changed { if tag.metadata_eq(if_changed) { // Result is unchanged. return Err(Error::Unchanged(tag.hash_steal(if_changed)).into()); } } let mut gpg = new_background_command("gpg"); gpg .stdin(Stdio::null()) .stdout(Stdio::piped()) .stderr(Stdio::null()) .arg("--homedir") .arg(ctx.directory("homedir")?) .arg("--with-colons"); if secret { gpg.arg("--list-secret-keys"); // Skip expensive trust models, we don't use the // information anyways. gpg.arg("--always-trust"); } else { gpg.arg("--list-keys"); } if let Some(filter) = filter { gpg.arg(filter); } t!("gpg command: {:?}", gpg); let gpg = gpg.output()?; let tag = tag.hash_output(&gpg.stdout)?; if let Some(if_changed) = if_changed { if tag.hash_eq(if_changed) { // Result is unchanged. return Err(Error::Unchanged(tag).into()); } } let r = list_keys_raw_parse(&gpg.stdout)?; Ok((r, tag)) }; Ok(doit().with_context( || format!( "Running gpg {} --with-colons", if secret { "--list-secret-keys" } else { "--list-keys" }))?) } fn list_keys_raw_parse(stdout: &[u8]) -> Result> { // Although csv takes a byte string, it expects valid UTF-8. // Doing a lossy conversion might break a few User IDs, but // everyone uses UTF-8 these days. let output = String::from_utf8_lossy(stdout); let output = output.as_bytes(); let mut rdr = csv::ReaderBuilder::new() .has_headers(false) .delimiter(b':') .flexible(true) .from_reader(std::io::Cursor::new(output)); // We don't expect gpg to return invalid records. But, let's // ignore any just in case. let r = rdr.records().filter_map(|rec| { match rec { Ok(rec) => Some(rec), Err(err) => { global_warn!("Parsing gpg --list-keys --with-colons:\n {}", err); None } } }).collect(); Ok(r) } #[derive(Debug, PartialEq, Eq)] pub enum Validity { Marginal, Fully, Ultimately, } pub fn list_validity(ctx: &Ctx, filter: Option, if_changed: Option<&CacheTag>) -> Result<(Vec<(Fingerprint, Vec<(String, Validity)>)>, CacheTag)> { rnp_function!(gpg::list_validity, TRACE); let (res, tag) = list_keys_raw(&ctx, false, filter, if_changed)?; let mut validity: Vec<(Fingerprint, Vec<(String, Validity)>)> = Vec::new(); let mut key_validity: Vec<(String, Validity)> = Vec::new(); let mut fingerprint: Option = None; for line in res.into_iter() { match line.get(0) { Some("pub") => { if ! key_validity.is_empty() { validity.push((fingerprint.expect("set"), key_validity)); key_validity = Vec::new(); } t!("Start of new certificate"); fingerprint = None; } // The first fpr is for the primary key; ignore subkeys. Some("fpr") => if fingerprint.is_none() { fingerprint = if let Some(fingerprint) = line.get(9) { t!(" Considering {}", fingerprint); fingerprint.parse().ok() } else { continue; } } Some("uid") if fingerprint.is_some() => { let uid: String = if let Some(uid) = line.get(9) { // XXX: Unquote it: // // The value is quoted like a C string to avoid // control characters (the colon is quoted // =\x3a=). t!(" Considering {}", uid); uid.into() } else { continue; }; match line.get(1).unwrap_or("") { // Marginal. "m" => { t!("{}: {} is marginally trusted.", fingerprint.as_ref().expect("set"), uid); key_validity.push((uid, Validity::Marginal)); } "f" => { t!("{}: {} is fully trusted.", fingerprint.as_ref().expect("set"), uid); key_validity.push((uid, Validity::Fully)); } "u" => { t!("{}: {} is ultimately trusted.", fingerprint.as_ref().expect("set"), uid); key_validity.push((uid, Validity::Ultimately)); } _ => (), } } _ => { continue; } } } if ! key_validity.is_empty() { validity.push((fingerprint.expect("set"), key_validity)); } Ok((validity, tag)) } pub fn list_secret_keys(ctx: &Ctx, filter: Option, if_changed: Option<&CacheTag>) -> Result<(Vec<(Fingerprint, Fingerprint)>, CacheTag)> { rnp_function!(gpg::list_secret_keys, TRACE); // Note: when listing secret keys, we don't evaluate the trust // model. let (res, tag) = list_keys_raw(&ctx, true, filter, if_changed)?; let secret_keys = list_secret_keys_parse(res)?; Ok((secret_keys, tag)) } pub fn list_secret_keys_parse(res: Vec) -> Result> { rnp_function!(gpg::list_secret_keys_parse, TRACE); let mut secret_keys: Vec<(Fingerprint, Fingerprint)> = Vec::new(); let mut primary_fingerprint: Option = None; let mut key_has_secret = false; let mut keys = 0; // The output looks like: // // $ gpg --with-colons -K neal@walfield | grep -E '^(pub|sec|fpr|ssb|sub)' // sec:r:1024:17:3BF609C68BAFCDBD:991801296:::u:::sca:::+:::::0: // fpr:::::::::11C294DF1D6C9698FEFE231D3BF609C68BAFCDBD: // ssb:r:1024:16:3DAD5ECF8191AFAE:991801300::::::e:::+:::: // fpr:::::::::BAE80223CD24C35F8A56518E3DAD5ECF8191AFAE: // sec:u:3744:1:AACB3243630052D9:1428396777:1618145210::u:::scESCA:::#:::::0: // fpr:::::::::8F17777118A33DDA9BA48E62AACB3243630052D9: // ssb:u:2048:1:7223B56678E02528:1428399766:1618145244:::::s:::D27... // fpr:::::::::C03FA6411B03AE12576461187223B56678E02528: // ssb:u:2048:1:C2B819056C652598:1428399800:1618145277:::::e:::D27... // fpr:::::::::50E6D924308DBF223CFB510AC2B819056C652598: // ssb:u:2048:1:A3506AFB820ABD08:1428399828:1618145266:::::a:::D27... // fpr:::::::::2DC50AB55BE2F3B04C2D2CF8A3506AFB820ABD08: // // In short, we have: key and subkey records, which don't include // the fingerprint, and we have 'fpr' records, which include the // fingerprint of the preceding key. for line in res.into_iter() { match line.get(0) { Some("pub") => { t!("Start of new certificate"); primary_fingerprint = None; key_has_secret = false; keys = 0; } Some("sec") => { t!("Start of new key"); primary_fingerprint = None; key_has_secret = true; keys = 0; } Some("sub") => { t!("New subkey"); key_has_secret = false; keys += 1; } Some("ssb") => { t!("New secret subkey"); key_has_secret = true; keys += 1; } // The first fpr is for the primary key. The rest are for // subkeys. Some("fpr") => { let fpr: Fingerprint = if let Some(fpr) = line.get(9) { t!(" Considering {}", fpr); match fpr.parse() { Ok(fpr) => fpr, Err(err) => { t!("Parsing {:?} as fingerprint: {}", fpr, err); continue; } } } else { continue; }; if keys == 0 { // Primary. if key_has_secret { secret_keys.push((fpr.clone(), fpr.clone())); } primary_fingerprint = Some(fpr); } else if let Some(ref primary_fingerprint) = primary_fingerprint { // Subkey. if key_has_secret { secret_keys.push((primary_fingerprint.clone(), fpr.clone())); } } } _ => { continue; } } } Ok(secret_keys) } #[cfg(test)] mod tests { use super::*; #[test] fn list_secret_keys() -> Result<()> { // $ gpg --with-colons -K neal@walfield.org let raw = r#"sec:r:1024:17:3BF609C68BAFCDBD:991801296:::u:::sca:::+:::::0: fpr:::::::::11C294DF1D6C9698FEFE231D3BF609C68BAFCDBD: grp:::::::::05962113260A2C8EA6A109FF29BDFB0135046EDB: uid:r::::1279109191::1B1B7482B7F9ECDBAF1240DE9DCD998175520447::Neal H. Walfield ::::::::::0: uid:r::::1005763171::EE96475411968ECFDFA4119223DAE7EC591E3DFF::Neal H Walfield ::::::::::0: uid:r::::991801296::9AD6D7918766A121EF89B575DBBB55F50A219D5E::Neal H Walfield ::::::::::0: uid:r::::991801340::296A2626DFCCD551020324659B64A82F1C213358::Neal H Walfield ::::::::::0: uid:r::::1279108858::EDDE4C7DDAD6767B658E6AA40460D9335D3FD6B1::Neal H. Walfield ::::::::::0: uid:r::::1279109115::DA1FB102143127EFC07BF8FF3B33F14D71A9F4D6::Neal H. Walfield ::::::::::0: uid:r::::1279109142::B0ABC32AD3E5DDA33FA25A55EEDDC9B9DFBFC51F::Neal H. Walfield ::::::::::0: uid:r::::991801401::A6F224DB69F3D02EADAB0D8DCE5C70C1E67F7A49::Neal H Walfield ::::::::::0: ssb:r:1024:16:3DAD5ECF8191AFAE:991801300::::::e:::+:::: fpr:::::::::BAE80223CD24C35F8A56518E3DAD5ECF8191AFAE: grp:::::::::E10FC1BFDDBAA7E7E5D9ED5EF7426F8FEFDDEAC5: sec:u:3744:1:AACB3243630052D9:1428396777:1618145210::u:::scESCA:::#:::::0: fpr:::::::::8F17777118A33DDA9BA48E62AACB3243630052D9: grp:::::::::C45986381F54F967C2F6B104521C8634090F326A: uid:u::::1555073210::1B1B7482B7F9ECDBAF1240DE9DCD998175520447::Neal H. Walfield ::::::::::0: uid:u::::1555073229::59A9F6DB32F4942939989604985D0D623843E722::Neal H. Walfield ::::::::::0: uid:r::::::5737B70A0641BECFB13CA64599114B0D0BE9A824::Neal H. Walfield ::::::::::0: uid:u::::1555073229::29DE54352C7620F4C908DB6E860BD77EFA13B709::Neal H. Walfield ::::::::::0: uid:u::::1555073229::56E1B5E6FB1E1FA0F1EFC398C4638563C873DF5B::Neal H. Walfield ::::::::::0: uid:u::::1555073229::168858B59F321107B0057756E417110B167C1DF4::Neal H. Walfield ::::::::::0: ssb:u:2048:1:7223B56678E02528:1428399766:1618145244:::::s:::D2760001240102000006030166360000:::23: fpr:::::::::C03FA6411B03AE12576461187223B56678E02528: grp:::::::::BE2FE8C8793141322AC30E3EAFD1E4F9D8DACCC4: ssb:u:2048:1:C2B819056C652598:1428399800:1618145277:::::e:::D2760001240102000006030166360000:::23: fpr:::::::::50E6D924308DBF223CFB510AC2B819056C652598: grp:::::::::9873FD355DE470DDC151CD9919AC9785C3C2FDDE: ssb:u:2048:1:A3506AFB820ABD08:1428399828:1618145266:::::a:::D2760001240102000006030166360000:::23: fpr:::::::::2DC50AB55BE2F3B04C2D2CF8A3506AFB820ABD08: grp:::::::::9483454871CC1239D4C2A1416F2742D39A14DB14: "#; let r = list_keys_raw_parse(raw.as_bytes())?; let r = list_secret_keys_parse(r)?; assert_eq!( r, vec![ ("11C294DF1D6C9698FEFE231D3BF609C68BAFCDBD".parse().unwrap(), "11C294DF1D6C9698FEFE231D3BF609C68BAFCDBD".parse().unwrap()), ("11C294DF1D6C9698FEFE231D3BF609C68BAFCDBD".parse().unwrap(), "BAE80223CD24C35F8A56518E3DAD5ECF8191AFAE".parse().unwrap()), ("8F17777118A33DDA9BA48E62AACB3243630052D9".parse().unwrap(), "8F17777118A33DDA9BA48E62AACB3243630052D9".parse().unwrap()), ("8F17777118A33DDA9BA48E62AACB3243630052D9".parse().unwrap(), "C03FA6411B03AE12576461187223B56678E02528".parse().unwrap()), ("8F17777118A33DDA9BA48E62AACB3243630052D9".parse().unwrap(), "50E6D924308DBF223CFB510AC2B819056C652598".parse().unwrap()), ("8F17777118A33DDA9BA48E62AACB3243630052D9".parse().unwrap(), "2DC50AB55BE2F3B04C2D2CF8A3506AFB820ABD08".parse().unwrap()), ]); // $ gpg --with-colons -k neal@walfield.org let raw = r#"tru::1:1617140210:1618145210:3:1:5 pub:r:1024:17:3BF609C68BAFCDBD:991801296:::u:::sca::::::::0: fpr:::::::::11C294DF1D6C9698FEFE231D3BF609C68BAFCDBD: uid:r::::1279109191::1B1B7482B7F9ECDBAF1240DE9DCD998175520447::Neal H. Walfield ::::::::::0: uid:r::::1005763171::EE96475411968ECFDFA4119223DAE7EC591E3DFF::Neal H Walfield ::::::::::0: uid:r::::991801296::9AD6D7918766A121EF89B575DBBB55F50A219D5E::Neal H Walfield ::::::::::0: uid:r::::991801340::296A2626DFCCD551020324659B64A82F1C213358::Neal H Walfield ::::::::::0: uid:r::::1279108858::EDDE4C7DDAD6767B658E6AA40460D9335D3FD6B1::Neal H. Walfield ::::::::::0: uid:r::::1279109115::DA1FB102143127EFC07BF8FF3B33F14D71A9F4D6::Neal H. Walfield ::::::::::0: uid:r::::1279109142::B0ABC32AD3E5DDA33FA25A55EEDDC9B9DFBFC51F::Neal H. Walfield ::::::::::0: uid:r::::991801401::A6F224DB69F3D02EADAB0D8DCE5C70C1E67F7A49::Neal H Walfield ::::::::::0: sub:r:1024:16:3DAD5ECF8191AFAE:991801300::::::e::::::: fpr:::::::::BAE80223CD24C35F8A56518E3DAD5ECF8191AFAE: pub:r:1024:17:87234295786B0BAD:976762276:::-:::sca::::::::0: fpr:::::::::520054A53C19CBB2E7F5639687234295786B0BAD: uid:r::::979410749::A6F224DB69F3D02EADAB0D8DCE5C70C1E67F7A49::Neal H Walfield ::::::::::0: uid:r::::976762276::9AD6D7918766A121EF89B575DBBB55F50A219D5E::Neal H Walfield ::::::::::0: uid:r::::976769586::296A2626DFCCD551020324659B64A82F1C213358::Neal H Walfield ::::::::::0: sub:r:1024:16:BCD1834A0CB6D455:976762281::::::e::::::: fpr:::::::::9C6961D19B435818A3B26E08BCD1834A0CB6D455: pub:u:3744:1:AACB3243630052D9:1428396777:1618145210::u:::scESCA::::::::0: fpr:::::::::8F17777118A33DDA9BA48E62AACB3243630052D9: uid:u::::1555073210::1B1B7482B7F9ECDBAF1240DE9DCD998175520447::Neal H. Walfield ::::::::::0: uid:u::::1555073229::59A9F6DB32F4942939989604985D0D623843E722::Neal H. Walfield ::::::::::0: uid:r::::::5737B70A0641BECFB13CA64599114B0D0BE9A824::Neal H. Walfield ::::::::::0: uid:u::::1555073229::29DE54352C7620F4C908DB6E860BD77EFA13B709::Neal H. Walfield ::::::::::0: uid:u::::1555073229::56E1B5E6FB1E1FA0F1EFC398C4638563C873DF5B::Neal H. Walfield ::::::::::0: uid:u::::1555073229::168858B59F321107B0057756E417110B167C1DF4::Neal H. Walfield ::::::::::0: sub:u:2048:1:7223B56678E02528:1428399766:1618145244:::::s::::::23: fpr:::::::::C03FA6411B03AE12576461187223B56678E02528: sub:u:2048:1:C2B819056C652598:1428399800:1618145277:::::e::::::23: fpr:::::::::50E6D924308DBF223CFB510AC2B819056C652598: sub:u:2048:1:A3506AFB820ABD08:1428399828:1618145266:::::a::::::23: fpr:::::::::2DC50AB55BE2F3B04C2D2CF8A3506AFB820ABD08: "#; let r = list_keys_raw_parse(raw.as_bytes())?; let r = list_secret_keys_parse(r)?; assert_eq!(r, vec![]); Ok(()) } } sequoia-octopus-librnp-1.11.1/src/import.rs000064400000000000000000000310041046102023000167760ustar 00000000000000use libc::{ c_char, }; use sequoia_openpgp as openpgp; use crate::{ RnpResult, RnpContext, RnpInput, str_to_rnp_buffer, flags::*, error::*, }; #[derive(serde::Serialize, Debug, Default)] struct KeyImportResults { keys: Vec, } #[derive(serde::Serialize, Debug)] struct KeyImportResult { public: String, secret: String, fingerprint: String, } impl Default for KeyImportResult { fn default() -> Self { KeyImportResult { public: RnpKeyImportStatus::Unknown.into(), secret: RnpKeyImportStatus::Unknown.into(), fingerprint: Default::default(), } } } enum RnpKeyImportStatus { Unknown, Unchanged, Updated, New, } impl From for String { fn from(s: RnpKeyImportStatus) -> Self { use RnpKeyImportStatus::*; match s { Unknown => "unknown".into(), Unchanged => "unchanged".into(), Updated => "updated".into(), New => "new".into(), } } } #[derive(serde::Serialize, Debug, Default)] struct SigImportResults { sigs: Vec, } #[derive(serde::Serialize, Debug)] struct SigImportResult { public: String, secret: String, #[serde(rename = "signer fingerprint")] signer_fingerprint: String, } impl Default for SigImportResult { fn default() -> Self { SigImportResult { public: RnpSigImportStatus::Unknown.into(), secret: RnpSigImportStatus::Unknown.into(), signer_fingerprint: Default::default(), } } } #[derive(PartialEq)] enum RnpSigImportStatus { Unknown, UnknownKey, Unchanged, New, } impl From for String { fn from(s: RnpSigImportStatus) -> Self { use RnpSigImportStatus::*; match s { Unknown => "unknown".into(), UnknownKey => "unknown key".into(), Unchanged => "unchanged".into(), New => "new".into(), } } } /// XXX: RNP reports per subkey results, we don't, and it is not clear /// that we want that (or that any caller may want that). Currently, /// TB doesn't use this information at all, but is planning to do so /// in the future (again, not clear if they care about subkeys). #[no_mangle] pub unsafe extern "C" fn rnp_import_keys(ctx: *mut RnpContext, input: *mut RnpInput, flags: u32, results: *mut *mut c_char) -> RnpResult { rnp_function!(rnp_import_keys, crate::TRACE); let ctx = assert_ptr_mut!(ctx); let input = assert_ptr_mut!(input); arg!(flags); arg!(results); let public = flags & RNP_LOAD_SAVE_PUBLIC_KEYS > 0; let secret = flags & RNP_LOAD_SAVE_SECRET_KEYS > 0; let keep_going = flags & RNP_LOAD_SAVE_PERMISSIVE > 0; let single = flags & RNP_LOAD_SAVE_SINGLE > 0; t!("public = {}, secret = {}, keep_going = {}, single = {}", public, secret, keep_going, single); let mut import_results = KeyImportResults::default(); let f = || -> openpgp::Result<()> { // First, load into an accumulator. If any cert is bad, and // not keep_going, then the whole operation should fail. let certs = sequoia_openpgp_mt::keyring::parse(input)?; let mut acc = Vec::new(); for cert in certs { t!("Considering cert {:?}", cert.as_ref().map(|c| c.fingerprint())); match cert { Err(_) if keep_going => continue, Err(e) => { warn!("bad key and !keep_going, aborting: {}", e); return Err(e); }, Ok(v) => acc.push(v), } if single { // Just load the first key. break; } } for cert in acc { use RnpKeyImportStatus::*; let mut r = KeyImportResult::default(); let fp = cert.fingerprint(); r.fingerprint = fp.to_hex(); let status; if let Some(known) = ctx.certs.read().by_primary_fp(&fp) { if known.as_tsk() == cert.as_tsk() { status = Unchanged; } else { // XXX: This is not quite correct. False // positive if cert has less information than // known. Then, it is not really an update. status = Updated; } } else { status = New; // ctx is still mutably borrowed. Do the // insertion in a second step to appease the // borrow checker. } let imported_secret = secret && cert.is_tsk(); if let Unchanged = status { // Nothing to do. } else { // Appease the borrow checker. if secret { ctx.insert_key(cert); } else { ctx.insert_cert(cert); } } r.public = status.into(); if imported_secret { r.secret = r.public.clone(); } t!("Imported {:?}", r); import_results.keys.push(r); } if ! results.is_null() { *results = str_to_rnp_buffer( serde_json::to_string_pretty(&import_results)?); } Ok(()) }; rnp_return_status!(if let Err(e) = f() { warn!("rnp_import_keys: {}", e); RNP_ERROR_GENERIC } else { RNP_SUCCESS }) } /// Import detached signatures by adding them to the cert or key that issued them. /// /// Warning: We assume that signatures are first-party signatures (see sequoia#692)! /// WARNING: We do not handle third-party revocations! /// /// Does not import anything in case of any faulty input. #[no_mangle] pub unsafe extern "C" fn rnp_import_signatures( ctx: *mut RnpContext, input: *mut RnpInput, _flags: u32, results: *mut *mut c_char, ) -> RnpResult { use openpgp::packet::{Packet, Signature}; rnp_function!(rnp_import_signatures, crate::TRACE); let ctx = assert_ptr_mut!(ctx); let input = assert_ptr_mut!(input); let mut import_results = SigImportResults::default(); let mut f = || -> openpgp::Result<()> { fn parse_input(input: &mut RnpInput) -> openpgp::Result> { use openpgp::parse::{PacketParser, PacketParserResult, Parse}; let mut ppr: PacketParserResult = PacketParser::from_reader(input)?; let mut signatures: Vec = Vec::new(); while let PacketParserResult::Some(pp) = ppr { let (packet, next_ppr) = pp.next()?; ppr = next_ppr; match packet { Packet::Signature(sig) => { signatures.push(sig); } Packet::Marker(_) => { // ignore Marker packets } Packet::Unknown(unknown) => { if unknown.tag() == openpgp::packet::Tag::Signature { t!("Found unknown signature packet {:?}", unknown); } else { Err(anyhow::anyhow!( "Found unknown packet: {}", unknown.tag() ))? } } p => Err(anyhow::anyhow!( "Found unexpected packet: {}", p.tag() ))?, }; } Ok(signatures) } // First, parse the input and stop if there is an error. // Then, try to import each signature, collect outcome in import_results. let signatures = parse_input(input)?; for sig in signatures.into_iter() { t!("Considering signature by {:?}, {}", sig.get_issuers(), sig.typ()); let issuers = &sig.get_issuers(); let mut issuers = issuers.iter(); let mut merged = false; // Iterate over the issuer hints, merge signature for the fist issuer // found in one of the keystores. while let Some(issuer) = issuers.next() { if merged { break } t!("Considering issuer {}", issuer); // XXX: We assume that signatures are first-party // signatures (see sequoia#692)! // We do not handle third-party revocations! let mut inner = |issuer: &openpgp::KeyHandle| -> openpgp::Result { fn try_merge( sig: Signature, cert: openpgp::Cert, ) -> openpgp::Result { let old_count = cert.bad_signatures().count(); let cert_new = cert .insert_packets(Packet::Signature(sig.clone()))?.0; if old_count < cert_new.bad_signatures().count() { Err(anyhow::anyhow!("signature does not match")) } else { Ok(cert_new) } } // Find key and/or cert which issued the signature. let cert = match issuer { openpgp::KeyHandle::Fingerprint(fp) => { ctx.certs.read().by_primary_fp(&fp).map(|c| c.clone()) } openpgp::KeyHandle::KeyID(id) => { ctx.certs.read().by_primary_id(&id).next().map(|c| c.clone()) } }; let cert = cert.ok_or_else(|| anyhow::anyhow!("no cert found"))?; t!("Found cert: {:?}", cert.fingerprint()); let cert_merged = try_merge(sig.clone(), cert.clone())?; let mut r = SigImportResult::default(); r.signer_fingerprint = cert.fingerprint().to_hex(); let status = if cert.eq(&cert_merged) { RnpSigImportStatus::Unchanged } else { RnpSigImportStatus::New }; let has_secret = cert_merged.is_tsk(); if has_secret { if status == RnpSigImportStatus::New { ctx.insert_key(cert_merged) }; } else { if status == RnpSigImportStatus::New { ctx.insert_cert(cert_merged); }; } if has_secret { r.public = status.into(); r.secret = r.public.clone(); } else { r.public = status.into(); r.secret = RnpSigImportStatus::UnknownKey.into(); } Ok(r) }; let r = inner(issuer); if let Ok(r) = r { t!("Imported {:?}", r); import_results.sigs.push(r); // The sig was merged for this issuer, stop. merged = true; } if ! merged { t!("No cert or key found"); // TODO: rnp returns this result if it ends up not finding the // issuer in either keyring. // It's quite unhelpful, but maybe consumers expect a result entry // for each sig? let r = SigImportResult { public: RnpSigImportStatus::UnknownKey.into(), secret: RnpSigImportStatus::UnknownKey.into(), signer_fingerprint: Default::default(), }; import_results.sigs.push(r); } } } if ! results.is_null() { *results = str_to_rnp_buffer( serde_json::to_string_pretty(&import_results)?); } Ok(()) }; rnp_return_status!(if let Err(e) = f() { warn!("rnp_import_signatures: {}", e); RNP_ERROR_GENERIC } else { RNP_SUCCESS }) } sequoia-octopus-librnp-1.11.1/src/io.rs000064400000000000000000000253241046102023000161030ustar 00000000000000use std::{ io, fmt, fs::File, path::PathBuf, }; use sequoia_openpgp as openpgp; use openpgp::{ armor, }; use super::*; pub enum RnpInput { Ref(io::Cursor<&'static [u8]>), Buf(io::Cursor>), File(PathBuf, File), } impl fmt::Debug for RnpInput { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { RnpInput::Ref(c) => { f.debug_struct("RnpInput") .field("Ref", &c.get_ref().len()) .finish() } RnpInput::Buf(c) => { f.debug_struct("RnpInput") .field("Vec", &c.get_ref().len()) .finish() } RnpInput::File(p, _) => { f.debug_struct("RnpInput") .field("File", p) .finish() } } } } pub enum RnpOutput<'a> { Buf((Vec, Option)), File(File), Armorer(Option>>) } impl RnpInput { /// Returns the input's file size, if it can be determined. pub fn size(&self) -> openpgp::Result { match self { RnpInput::Ref(c) => Ok(c.get_ref().len() as u64), RnpInput::Buf(c) => Ok(c.get_ref().len() as u64), RnpInput::File(_, fp) => { Ok(fp.metadata()?.len()) } } } /// Tries to clone this input. /// /// This may fail if the input refers to a file and reopening the /// file fails. /// /// The input stream is rewound for consistency with the reopened /// file. pub fn try_clone(&self) -> io::Result { use io::Seek; match self { RnpInput::Ref(c) => { let mut c = c.clone(); c.seek(io::SeekFrom::Start(0))?; Ok(RnpInput::Ref(c)) }, RnpInput::Buf(c) => { let mut c = c.clone(); c.seek(io::SeekFrom::Start(0))?; Ok(RnpInput::Buf(c)) }, RnpInput::File(p, _) => Ok(RnpInput::File(p.clone(), File::open(p)?)), } } /// Copies referenced data, if necessary. pub fn to_owned(self) -> Self { if let RnpInput::Ref(c) = self { let p = c.position(); let mut owned = io::Cursor::new(c.into_inner().to_vec()); owned.set_position(p); RnpInput::Buf(owned) } else { self } } } #[no_mangle] pub unsafe extern "C" fn rnp_input_from_path(input: *mut *mut RnpInput, path: *const c_char) -> RnpResult { rnp_function!(rnp_input_from_path, crate::TRACE); assert_ptr!(input); let path = assert_str!(path).into(); rnp_return_status!(if let Ok(f) = File::open(&path) { *input = Box::into_raw(Box::new( RnpInput::File(path, f) )); RNP_SUCCESS } else { RNP_ERROR_ACCESS }) } #[no_mangle] pub unsafe extern "C" fn rnp_input_from_memory(input: *mut *mut RnpInput, buf: *const u8, len: size_t, do_copy: bool) -> RnpResult { rnp_function!(rnp_input_from_memory, crate::TRACE); assert_ptr!(input); assert_ptr!(buf); arg!(len); arg!(do_copy); let data = std::slice::from_raw_parts(buf, len); *input = Box::into_raw(Box::new(if do_copy { RnpInput::Buf(io::Cursor::new(data.to_vec())) } else { RnpInput::Ref(io::Cursor::new(data)) })); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_input_destroy(input: *mut RnpInput) -> RnpResult { rnp_function!(rnp_input_destroy, crate::TRACE); arg!(input); if ! input.is_null() { drop(Box::from_raw(input)); } rnp_success!() } impl io::Read for RnpInput { fn read(&mut self, buf: &mut [u8]) -> io::Result { match self { RnpInput::Ref(c) => c.read(buf), RnpInput::Buf(c) => c.read(buf), RnpInput::File(_, f) => f.read(buf), } } } #[no_mangle] pub unsafe extern "C" fn rnp_output_to_path(output: *mut *mut RnpOutput, path: *const c_char) -> RnpResult { rnp_function!(rnp_output_to_path, crate::TRACE); let path = rnp_try!(cstr_to_pathbuf(path)); let f = match File::create(&path) { Ok(f) => f, Err(e) => { log!("sequoia-octopus: failed to create {:?}: {}", path, e); rnp_return_status!(RNP_ERROR_ACCESS); }, }; *output = Box::into_raw(Box::new( RnpOutput::File(f) )); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_output_to_memory(output: *mut *mut RnpOutput, max_alloc: size_t) -> RnpResult { rnp_function!(rnp_output_to_memory, crate::TRACE); assert_ptr!(output); arg!(max_alloc); *output = Box::into_raw(Box::new( RnpOutput::Buf((Vec::new(), if max_alloc == 0 { None } else { Some(max_alloc) })) )); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_output_memory_get_buf(output: *const RnpOutput, buf: *mut *mut u8, len: *mut size_t, do_copy: bool) -> RnpResult { rnp_function!(rnp_output_memory_get_buf, crate::TRACE); let output = assert_ptr_ref!(output); let buf = assert_ptr_mut!(buf); let len = assert_ptr_mut!(len); arg!(do_copy); rnp_return_status!(if let RnpOutput::Buf((bytes, _)) = output { if do_copy { *buf = bytes_to_rnp_buffer(bytes); *len = bytes.len(); } else { *buf = bytes.as_ptr() as *mut _; *len = bytes.len(); } RNP_SUCCESS } else { RNP_ERROR_GENERIC }) } #[no_mangle] pub unsafe extern "C" fn rnp_output_to_armor<'a>(sink: *mut RnpOutput<'a>, output: *mut *mut RnpOutput<'a>, kind: *const c_char) -> RnpResult { rnp_function!(rnp_output_to_armor, crate::TRACE); let sink = assert_ptr_mut!(sink); let output = assert_ptr_mut!(output); if kind.is_null() { arg!(kind); warn!("rnp_output_to_armor: type detection not implemented"); rnp_return_status!(RNP_ERROR_NOT_IMPLEMENTED); // XXX } let kind = assert_str!(kind); let kind = rnp_try!(armor::Kind::from_rnp_id(kind)); *output = match armor::Writer::new(&mut *sink, kind) { Ok(v) => Box::into_raw(Box::new(RnpOutput::Armorer(Some(v)))), Err(e) => { warn!("rnp_output_to_armor: {}", e); rnp_return_status!(RNP_ERROR_WRITE); }, }; rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_output_armor_set_line_length<'a>(output: *mut RnpOutput<'a>, llen: size_t) -> RnpResult { rnp_function!(rnp_output_armor_set_line_length, crate::TRACE); assert_ptr!(output); arg!(llen); if llen == 64 { // This is Sequoia's default line length, there is nothing to // do. Another job well done. } else { // Currently, there is no API to change the line length, nor // do I think there is a use case for changing the line // length. Emit a warning and move on. warn!("rnp_output_armor_set_line_length: setting length to {} ignored", llen); } rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_output_destroy(output: *mut RnpOutput) -> RnpResult { rnp_function!(rnp_output_destroy, crate::TRACE); arg!(output); if ! output.is_null() { drop(Box::from_raw(output)); } rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_output_finish(output: *mut RnpOutput) -> RnpResult { rnp_function!(rnp_output_finish, crate::TRACE); let output = assert_ptr_mut!(output); match output { RnpOutput::Buf(_) => (), RnpOutput::File(_) => (), RnpOutput::Armorer(w) => if let Some(w) = w.take() { if let Err(e) = w.finalize() { warn!("rnp_output_finish: {}", e); rnp_return_status!(RNP_ERROR_WRITE); } } else { rnp_return_status!(RNP_ERROR_WRITE); }, // XXX: If we ever implement RNP_OUTPUT_FILE_RANDOM, do the // rename here. } rnp_success!() } impl<'a> io::Write for RnpOutput<'a> { fn write(&mut self, buf: &[u8]) -> io::Result { match self { RnpOutput::Buf((c, max_alloc)) => { if let Some(max) = max_alloc { let left = *max - c.len(); c.write(&buf[..buf.len().min(left)]) } else { c.write(buf) } }, RnpOutput::File(f) => f.write(buf), RnpOutput::Armorer(w) => if let Some(w) = w { w.write(buf) } else { Err(io::Error::new(io::ErrorKind::BrokenPipe, "rnp_output_finished called")) }, } } fn flush(&mut self) -> io::Result<()> { match self { RnpOutput::Buf(_) => Ok(()), RnpOutput::File(f) => f.flush(), RnpOutput::Armorer(w) => if let Some(w) = w { w.flush() } else { Err(io::Error::new(io::ErrorKind::BrokenPipe, "rnp_output_finished called")) }, } } } #[no_mangle] pub unsafe extern "C" fn rnp_enarmor(input: *mut RnpInput, output: *mut RnpOutput, kind: *const c_char) -> RnpResult { rnp_function!(rnp_enarmor, crate::TRACE); assert_ptr!(input); assert_ptr!(output); if kind.is_null() { arg!(kind); warn!("rnp_enarmor: type detection not implemented"); rnp_return_status!(RNP_ERROR_NOT_IMPLEMENTED); // XXX } let kind = assert_str!(kind); let kind = rnp_try!(armor::Kind::from_rnp_id(kind)); fn f(input: &mut RnpInput, output: &mut RnpOutput, kind: armor::Kind) -> openpgp::Result<()> { let mut w = armor::Writer::new(output, kind)?; io::copy(input, &mut w)?; w.finalize()?; Ok(()) } rnp_return_status!(if let Err(e) = f(&mut *input, &mut *output, kind) { warn!("rnp_enarmor failed: {}", e); RNP_ERROR_GENERIC } else { RNP_SUCCESS }) } sequoia-octopus-librnp-1.11.1/src/iter.rs000064400000000000000000000122771046102023000164420ustar 00000000000000use libc::{ c_char, }; use crate::{ NP, RnpContext, RnpResult, str_to_rnp_buffer, error::*, conversions::*, }; pub struct RnpIdentifierIterator { typ: RnpIdentifierType, iter: std::vec::IntoIter, } // XXX: Do we want to return subkeys? I guess RNP does, but TB // filters out subkeys anyway. #[no_mangle] pub unsafe extern "C" fn rnp_identifier_iterator_create(ctx: *mut RnpContext, iter: *mut *mut RnpIdentifierIterator, typ: *const c_char) -> RnpResult { rnp_function!(rnp_identifier_iterator_create, crate::TRACE); let ctx = assert_ptr_mut!(ctx); let iter = assert_ptr_mut!(iter); let typ = assert_str!(typ); let typ = rnp_try!(RnpIdentifierType::from_rnp_id(typ)); t!("type = {:?}", typ); // We load the keyrings in the background. But, once TB tries to // do a scan, we have to wait: it creates an index, which it never // updates on its own, and we want it to include all known // certificates. let _ = ctx.certs.block_on_load(); // Thunderbird is doing a scan. Now is a good time to check for // WoT updates. However, we cannot stall our caller, hence we // schedule a background job and wait for a little while. rnp_try_or!( (*ctx).certs.update_wot_in_background((*ctx).policy.clone()), Error::Generic.into()); // There are a few good reasons why we eagerly evaluate the // iterator: // // - We'd have to keep the keystore lock for the life of the // iterator. That could be a very long time. In particular, // if the caller forgets to destroy it we deadlock. It's // probably better to just leak the iterator. Also, it would // not be possible to modify the keystore, which is // inconvenient. // // - Because we don't want to return duplicates, we'd have to // keep a list of certificates that we've already seen. At // that point, we use O(n) memory anyway! let ks = ctx.certs.read(); let results: Vec = match typ { RnpIdentifierType::UserID => { let mut userids: Vec = Vec::with_capacity(2 * ks.count()); for cert in ks.iter() { if let Ok(vcert) = cert.with_policy(&*ctx.policy(), None) { userids.extend(vcert.userids() .filter_map(|u| { String::from_utf8( u.userid().value().to_vec()).ok() })) } else if let Ok(vcert) = cert.with_policy(NP, None) { // cert is not valid under the standard policy. // Thus, it is safe to show User IDs that are // valid under the Null Policy: they will never be // used in a security sensitive context, which // requires a cert that *is* valid under the // standard policy. userids.extend(vcert.userids() .filter_map(|u| { String::from_utf8( u.userid().value().to_vec()).ok() })) } } // We need to dedup. userids.sort(); userids.dedup(); userids } RnpIdentifierType::KeyID => { // by_primary_id is by definition deduped. ks.keyids() .map(|keyid| format!("{:X}", keyid)) .collect() } RnpIdentifierType::Fingerprint => { // by_primary_fp is by definition deduped. ks.fingerprints() .map(|fpr| format!("{:X}", fpr)) .collect() } RnpIdentifierType::Keygrip => { // by_primary_grip is by definition deduped. ks.keygrips() .map(|grip| grip.to_string()) .collect() } }; *iter = Box::into_raw(Box::new(RnpIdentifierIterator { typ, iter: results.into_iter(), })); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_identifier_iterator_destroy(iter: *mut RnpIdentifierIterator) -> RnpResult { rnp_function!(rnp_identifier_iterator_destroy, crate::TRACE); arg!(iter); if ! iter.is_null() { drop(Box::from_raw(iter)); } rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_identifier_iterator_next(iter: *mut RnpIdentifierIterator, item: *mut *const c_char) -> RnpResult { rnp_function!(rnp_identifier_iterator_next, crate::TRACE); let iter = assert_ptr_mut!(iter); t!("type = {:?}", iter.typ); assert_ptr!(item); if let Some(id) = iter.iter.next() { t!("Yielding {:?} = {}", iter.typ, id); *item = str_to_rnp_buffer(&id); } else { // Exhausted the iterator. t!("Iterator exhausted."); *item = std::ptr::null(); } rnp_success!() } sequoia-octopus-librnp-1.11.1/src/key.rs000064400000000000000000002354441046102023000162720ustar 00000000000000use std::{ cmp::min, fmt, io, sync::{ Arc, RwLock, RwLockReadGuard, RwLockWriteGuard, }, time::{ Duration, UNIX_EPOCH, }, }; use libc::{ c_char, c_void, size_t, }; use sequoia_openpgp as openpgp; use openpgp::{ cert::{ Cert, CertRevocationBuilder, SubkeyRevocationBuilder, amalgamation::ValidAmalgamation, }, packet::{ Packet, UserID, Key, key::{ UnspecifiedParts, UnspecifiedRole, }, }, policy::Policy, serialize::Serialize, types::{ HashAlgorithm, ReasonForRevocation, RevocationStatus, }, }; use crate::{ RnpResult, RnpContext, RnpOutput, Keygrip, RnpSignature, RnpUserID, RnpPasswordFor, str_to_rnp_buffer, c_str_to_rnp_buffer, c_str_to_rnp_buffer_lossy, cstr_to_str, flags::*, conversions::*, error::*, signature::ca_signatures, tbprofile::TBProfile, }; pub struct RnpKey { pub(crate) ctx: *mut RnpContext, key: Key, cert: Option>>, } impl fmt::Debug for RnpKey { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("RnpKey") .field("ctx", &self.ctx) .field("key", &self.key.fingerprint()) .field("cert", { &if let Some(c) = self.cert.as_ref() { if let Ok(c) = c.read() { c.fingerprint().to_hex() } else { "[LOCKED]".into() } } else { "Unknown".into() } }) .finish() } } impl RnpKey { /// Creates a new RnpKey without reference to the containing certificate. /// /// If at all possible you should use the `new` variant. This is /// because when the certificate is needed and it is not /// available, it is looked up in the keystore. This introduces /// two failure modes: the key is not present, there are multiple /// keys that have the same fingerprint. In the latter case, we /// return the best match, which should usually be good enough. pub fn without_cert(ctx: *mut RnpContext, key: Key) -> Self { let mut key = RnpKey { ctx, key, cert: None, }; key.find_cert(); key } pub fn new(ctx: *mut RnpContext, key: Key, cert: &Cert) -> Self { rnp_function!(RnpKey::new, crate::TRACE); let ctx = unsafe { &mut *ctx }; let cert_cell = if let Some(cert_cell) = ctx.certs.read().cert_cell_by_primary_fp(&cert.fingerprint()) { Arc::clone(&cert_cell) } else { // We've been asked to create a key handle to a key that // is not in our keystore. That's not good. There are a // few things we can do: // // 1. Crash (this function is infallible). // 2. Supply None (this will cause problems later). // 3. Reference the passed certificate. // 4. Insert the passed certificate into the keystore. // // For now we opt for #3. #4 might be better since we // don't treat the keyring as a curated keyring anyway. warn!("Attempt to create key handle for key ({}, {}) \ that is not in the keystore", cert.fingerprint(), key.fingerprint()); Arc::new(RwLock::new(cert.clone())) }; RnpKey { ctx, key, cert: Some(cert_cell), } } fn find_cert(&mut self) { rnp_function!(RnpKey::find_cert, crate::TRACE); if self.cert.is_none() { // There may be multiple certificates with the same key. // Prefer an exact match (i.e., fingerprint *and* secret // key material match), if one is available. let ctx = unsafe { &*self.ctx }; let fp = self.key.fingerprint(); let mut candidates = Vec::new(); for cell in ctx.certs.read().cert_cell_by_fp(&fp) { let cert = cell.read().unwrap(); for k in cert.keys().key_handle(&fp) { // Eq compares the secret parts when parts is // unspecified. candidates.push( (if &self.key == k.key() .role_as_unspecified() .parts_as_unspecified() { 0 } else { 1 }, cert.fingerprint(), Arc::clone(&cell))) } } if candidates.len() == 0 { warn!("No certificate in key store has the key {:X}", fp); return; } candidates.sort_by_key(|a| { (a.0, a.1.clone()) }); self.cert = candidates.into_iter().next().map(|(exact, _, cell)| { if exact == 1 { warn!("No certificate exactly contains the key {}", fp); } cell }); } } fn cert(&mut self) -> Option> { self.find_cert(); self.try_cert() } pub(crate) fn try_cert(&self) -> Option> { self.cert.as_ref().map(|c| c.read().unwrap()) } fn cert_mut(&mut self) -> Option> { self.find_cert(); self.try_cert_mut() } fn try_cert_mut(&self) -> Option> { let ctx = unsafe { &*self.ctx }; ctx.certs.read().mark_updated(); self.cert.as_ref().map(|c| c.write().unwrap()) } } impl std::ops::Deref for RnpKey { type Target = Key; fn deref(&self) -> &Self::Target { &self.key } } impl From for Key { fn from(k: RnpKey) -> Self { k.key } } #[no_mangle] pub unsafe extern "C" fn rnp_locate_key(ctx: *mut RnpContext, identifier_type: *const c_char, identifier: *const c_char, key: *mut *mut RnpKey) -> RnpResult { rnp_function!(rnp_locate_key, crate::TRACE); let ctx = assert_ptr_mut!(ctx); let identifier_type = assert_str!(identifier_type); let identifier = assert_str!(identifier); let key = assert_ptr_mut!(key); let id = rnp_try!( rnp_try!(RnpIdentifierType::from_rnp_id(identifier_type)) .with_identifier(identifier)); t!("Locating key by {:?}", id); // Thunderbird is looking up an identifier. Now is a good time to // check for WoT updates. However, we cannot stall our caller, // hence we schedule a background job and wait for a little while. rnp_try_or!( (*ctx).certs.update_wot_in_background((*ctx).policy.clone()), Error::Generic.into()); // (*ctx).cert also takes a read lock. But we need to keep the // key store locked for when we look up the matching certificate's // cell. let ks = (&*ctx).certs.read(); if let Some(cert) = (*ctx).cert(&id) { use RnpIdentifier::*; let k = match id { UserID(_) => cert.primary_key().key().clone() .role_into_unspecified(), KeyID(id) => cert.keys().key_handle(id) .nth(0).expect("must exist in cert").key().clone(), Fingerprint(fp) => cert.keys().key_handle(fp) .nth(0).expect("must exist in cert").key().clone(), Keygrip(grip) => cert.keys().filter(|k| { crate::Keygrip::of(k.key().mpis()).map(|g| g == grip).unwrap_or(false) }) .nth(0).expect("must exist in cert").key().clone(), }.parts_into_unspecified(); let cert = Some(ks.cert_cell_by_primary_fp(&cert.fingerprint()) .expect("just looked it up")); drop(ks); *key = Box::into_raw(Box::new(RnpKey { ctx, key: k, // XXX: (*ctx).cert() already found the cell. If that // function returned the cell instead, then we could avoid // this lock and look up. cert, })); } else { *key = std::ptr::null_mut(); } rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_key_handle_destroy(key: *mut RnpKey) -> RnpResult { rnp_function!(rnp_key_handle_destroy, crate::TRACE); arg!(key); if ! key.is_null() { drop(Box::from_raw(key)); } rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_key_get_keyid(key: *const RnpKey, keyid: *mut *mut c_char) -> RnpResult { rnp_function!(rnp_key_get_keyid, crate::TRACE); let key = assert_ptr_ref!(key); let keyid = assert_ptr_mut!(keyid); *keyid = str_to_rnp_buffer(format!("{:X}", (*key).keyid())); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_key_get_fprint(key: *const RnpKey, fprint: *mut *mut c_char) -> RnpResult { rnp_function!(rnp_key_get_fprint, crate::TRACE); let key = assert_ptr_ref!(key); let fprint = assert_ptr_mut!(fprint); *fprint = str_to_rnp_buffer(format!("{:X}", (*key).fingerprint())); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_key_get_alg(key: *const RnpKey, alg: *mut *mut c_char) -> RnpResult { rnp_function!(rnp_key_get_alg, crate::TRACE); let key = assert_ptr_ref!(key); let alg = assert_ptr_mut!(alg); *alg = str_to_rnp_buffer((*key).pk_algo().to_rnp_id()); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_key_get_curve(key: *mut RnpKey, curve_out: *mut *mut c_char) -> RnpResult { rnp_function!(rnp_key_get_curve, crate::TRACE); let key = assert_ptr_ref!(key); let curve_out = assert_ptr_mut!(curve_out); use openpgp::crypto::mpi::PublicKey; match key.key.mpis() { | PublicKey::EdDSA { curve, .. } | PublicKey::ECDSA { curve, .. } | PublicKey::ECDH { curve, .. } => { use openpgp::types::Curve::*; // We want to return an error on unknown curves. match curve { | NistP256 | NistP384 | NistP521 | BrainpoolP256 | BrainpoolP512 | Ed25519 | Cv25519 => (), _ => rnp_return_status!(RNP_ERROR_BAD_PARAMETERS), }; *curve_out = str_to_rnp_buffer(curve.to_rnp_id()); rnp_success!() }, // Not an ECC algorithm. _ => rnp_return_status!(RNP_ERROR_BAD_PARAMETERS), } } impl RnpKey { fn is_primary(&mut self) -> Result { let key_fpr = self.fingerprint(); if let Some(cert) = self.cert() { Ok(cert.fingerprint() == key_fpr) } else { Err(RNP_ERROR_NO_SUITABLE_KEY) } } } // XXX: A key can be both primary and subkey at the same time in // different certs, and what this function returns is not clearly // defined. The result depends on the order the keys are returned. #[no_mangle] pub unsafe extern "C" fn rnp_key_is_primary(key: *mut RnpKey, result: *mut bool) -> RnpResult { rnp_function!(rnp_key_is_primary, crate::TRACE); let key = assert_ptr_mut!(key); let result = assert_ptr_mut!(result); *result = rnp_try!((*key).is_primary()); rnp_success!() } // XXX: A key can be both primary and subkey at the same time in // different certs, and what this function returns is not clearly // defined. The result depends on the order the keys are returned. #[no_mangle] pub unsafe extern "C" fn rnp_key_is_sub(key: *mut RnpKey, result: *mut bool) -> RnpResult { rnp_function!(rnp_key_is_sub, crate::TRACE); let key = assert_ptr_mut!(key); let result = assert_ptr_mut!(result); *result = ! rnp_try!((*key).is_primary()); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_key_get_grip(key: *const RnpKey, grip: *mut *mut c_char) -> RnpResult { rnp_function!(rnp_key_get_grip, crate::TRACE); let key = assert_ptr_ref!(key); let grip = assert_ptr_mut!(grip); rnp_return_status!(if let Ok(keygrip) = Keygrip::of((*key).mpis()) { *grip = str_to_rnp_buffer(keygrip.to_string()); RNP_SUCCESS } else { RNP_ERROR_GENERIC }) } #[no_mangle] pub unsafe extern "C" fn rnp_key_get_primary_grip(key: *mut RnpKey, grip: *mut *mut c_char) -> RnpResult { rnp_function!(rnp_key_get_primary_grip, crate::TRACE); let key = assert_ptr_mut!(key); let grip = assert_ptr_mut!(grip); rnp_return_status!(if let Some(cert) = (*key).cert() { if let Ok(keygrip) = Keygrip::of(cert.primary_key().key().mpis()) { *grip = str_to_rnp_buffer(keygrip.to_string()); RNP_SUCCESS } else { RNP_ERROR_GENERIC } } else { RNP_ERROR_NO_SUITABLE_KEY }) } /// XXX: This is a really odd function. How can we not have the /// public key? #[no_mangle] pub unsafe extern "C" fn rnp_key_have_public(key: *const RnpKey, result: *mut bool) -> RnpResult { rnp_function!(rnp_key_have_public, crate::TRACE); let _ = assert_ptr_ref!(key); let result = assert_ptr_mut!(result); *result = true; rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_key_have_secret(key: *const RnpKey, result: *mut bool) -> RnpResult { rnp_function!(rnp_key_have_secret, crate::TRACE); let key = assert_ptr_ref!(key); let result = assert_ptr_mut!(result); let mut has_secret = (*key).has_secret(); // If there is no secret in the Thunderbird keyring, we are // looking at the gpg-agent store. However, we have to be careful // to only report secret keys after Thunderbird has created their // encrypted passphrase file, otherwise Thunderbird will complain // about the OpenPGP state being corrupted, and disable OpenPGP // support. if ! has_secret && TBProfile::has_encrypted_passphrase_txt() { has_secret = (*(*key).ctx).certs.key_on_agent(&(*key).fingerprint()); } t!("returning {:?}", has_secret); *result = has_secret; rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_key_get_bits(key: *const RnpKey, bits: *mut u32) -> RnpResult { rnp_function!(rnp_key_get_bits, crate::TRACE); let key = assert_ptr_ref!(key); let bits = assert_ptr_mut!(bits); rnp_return_status!(if let Some(b) = (*key).mpis().bits() { *bits = b as u32; RNP_SUCCESS } else { RNP_ERROR_NO_SUITABLE_KEY }) } #[no_mangle] pub unsafe extern "C" fn rnp_key_get_creation(key: *const RnpKey, creation: *mut u32) -> RnpResult { rnp_function!(rnp_key_get_creation, crate::TRACE); let key = assert_ptr_ref!(key); let creation = assert_ptr_mut!(creation); *creation = (*key).creation_time().duration_since(UNIX_EPOCH) .expect("creation time is representable as epoch") .as_secs() as u32; rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_key_get_expiration(key: *mut RnpKey, expiration: *mut u32) -> RnpResult { rnp_function!(rnp_key_get_expiration, crate::TRACE); let key = assert_ptr_mut!(key); let expiration = assert_ptr_mut!(expiration); let fp = key.fingerprint(); let policy = (*(key.ctx)).policy().clone(); if let Some(cert) = key.cert().as_ref() .and_then(|c| c.with_policy(&policy, None).ok()) { if let Some(vka) = cert.keys().key_handle(fp.clone()).nth(0) { *expiration = vka.key_validity_period().map(|d| d.as_secs() as u32) .unwrap_or(0); // 0 is special and means does not expire. } else { // Not valid under the policy. Treat as expired. t!("The (sub)key {} is not valid under the standard policy, \ treating as expired.", &fp); // Expired immediately. 0 is special and means does not expire. *expiration = 1; } } else { // Not valid under the policy. Treat as expired. t!("The (sub)key {} is not valid under the standard policy, \ treating as expired.", fp); // Expired immediately. 0 is special and means does not expire. *expiration = 1; } if *expiration > 0 { t!("{} expires {} seconds after its creation", fp, *expiration); } else { t!("{} does not expire", fp); } rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_key_get_primary_uid(key: *mut RnpKey, uid: *mut *mut c_char) -> RnpResult { rnp_function!(rnp_key_get_primary_uid, crate::TRACE); let key = assert_ptr_mut!(key); let uid = assert_ptr_mut!(uid); let policy = (*(key.ctx)).policy().clone(); // If we fail to find a primary userid using the standard policy, // we fall back to using a null policy. This value is only used // for displaying purposes, and falling back seems reasonable to // me. rnp_return_status!(if let Some(cert) = (*key).cert().as_ref() .and_then(|c| c.with_policy(&policy, None).ok() .or_else(|| c.with_policy(crate::NP, None).ok())) { if let Ok(u) = cert.primary_userid() { *uid = c_str_to_rnp_buffer_lossy(u.userid().value()); RNP_SUCCESS } else { RNP_ERROR_GENERIC } } else { RNP_ERROR_NO_SUITABLE_KEY }) } // XXX: should we only consider valid userids, or all of them? #[no_mangle] pub unsafe extern "C" fn rnp_key_get_uid_at(key: *mut RnpKey, idx: size_t, uid: *mut *mut c_char) -> RnpResult { rnp_function!(rnp_key_get_uid_at, crate::TRACE); let key = assert_ptr_mut!(key); arg!(idx); let uid = assert_ptr_mut!(uid); rnp_return_status!(if let Some(cert) = (*key).cert() { if let Some(u) = cert.userids().nth(idx) { if let Some(ptr) = c_str_to_rnp_buffer(u.userid().value()) { *uid = ptr; RNP_SUCCESS } else { RNP_ERROR_GENERIC } } else { RNP_ERROR_BAD_PARAMETERS } } else { RNP_ERROR_NO_SUITABLE_KEY }) } // XXX: should we only consider valid userids, or all of them? #[no_mangle] pub unsafe extern "C" fn rnp_key_get_uid_handle_at(key: *mut RnpKey, idx: size_t, uid: *mut *mut RnpUserID) -> RnpResult { rnp_function!(rnp_key_get_uid_handle_at, crate::TRACE); let key = assert_ptr_mut!(key); let uid = assert_ptr_mut!(uid); let ctx = (*key).ctx; rnp_return_status!(if let Some(cert) = (*key).cert() { if let Some(u) = cert.userids().nth(idx) { if let Some(u) = RnpUserID::new(ctx, u.userid().clone(), cert.clone()) { *uid = Box::into_raw(Box::new(u)); RNP_SUCCESS } else { RNP_ERROR_GENERIC } } else { RNP_ERROR_BAD_PARAMETERS } } else { RNP_ERROR_NO_SUITABLE_KEY }) } // XXX: should we only consider valid userids, or all of them? #[no_mangle] pub unsafe extern "C" fn rnp_key_get_uid_count(key: *mut RnpKey, count: *mut size_t) -> RnpResult { rnp_function!(rnp_key_get_uid_count, crate::TRACE); let key = assert_ptr_mut!(key); let count = assert_ptr_mut!(count); rnp_return_status!(if let Some(cert) = (*key).cert() { *count = cert.userids().count(); RNP_SUCCESS } else { RNP_ERROR_NO_SUITABLE_KEY }) } // XXX: should we only consider valid subkeys, or all of them? #[no_mangle] pub unsafe extern "C" fn rnp_key_get_subkey_at(key: *mut RnpKey, idx: size_t, subkey: *mut *mut RnpKey) -> RnpResult { rnp_function!(rnp_key_get_subkey_at, crate::TRACE); let key = assert_ptr_mut!(key); let subkey = assert_ptr_mut!(subkey); rnp_return_status!(if let Some(cell) = (*key).cert.as_ref() { let cert = cell.read().unwrap(); if let Some(k) = cert.keys().subkeys().nth(idx) { *subkey = Box::into_raw(Box::new(RnpKey { ctx: (*key).ctx, key: k.key().clone() .parts_into_unspecified() .role_into_unspecified(), cert: Some(Arc::clone(cell)), })); RNP_SUCCESS } else { RNP_ERROR_BAD_PARAMETERS } } else { RNP_ERROR_NO_SUITABLE_KEY }) } // XXX: should we only consider valid subkeys, or all of them? #[no_mangle] pub unsafe extern "C" fn rnp_key_get_subkey_count(key: *mut RnpKey, count: *mut size_t) -> RnpResult { rnp_function!(rnp_key_get_subkey_count, crate::TRACE); let key = assert_ptr_mut!(key); let count = assert_ptr_mut!(count); rnp_return_status!(if let Some(cert) = (*key).cert() { *count = cert.keys().subkeys().count(); RNP_SUCCESS } else { RNP_ERROR_NO_SUITABLE_KEY }) } #[no_mangle] pub unsafe extern "C" fn rnp_key_get_signature_at(key: *mut RnpKey, idx: size_t, sig: *mut *mut RnpSignature) -> RnpResult { rnp_function!(rnp_key_get_signature_at, crate::TRACE); let key = assert_ptr_mut!(key); arg!(idx); let sig = assert_ptr_mut!(sig); rnp_return_status!(if let Some(cert) = key.try_cert() { if let Some(subkey) = cert.keys().key_handle(key.fingerprint()).next() { if let Some((s, valid)) = ca_signatures(subkey.signatures()).nth(idx) { *sig = Box::into_raw(Box::new( RnpSignature::new(key.ctx, s.clone(), valid))); RNP_SUCCESS } else { RNP_ERROR_BAD_PARAMETERS } } else { RNP_ERROR_NO_SUITABLE_KEY } } else { RNP_ERROR_NO_SUITABLE_KEY }) } #[no_mangle] pub unsafe extern "C" fn rnp_key_get_signature_count(key: *mut RnpKey, count: *mut size_t) -> RnpResult { rnp_function!(rnp_key_get_signature_count, crate::TRACE); let key = assert_ptr_mut!(key); let count = assert_ptr_mut!(count); rnp_return_status!(if let Some(cert) = key.try_cert() { if let Some(subkey) = cert.keys().key_handle(key.fingerprint()).next() { *count = ca_signatures(subkey.signatures()).count(); RNP_SUCCESS } else { RNP_ERROR_NO_SUITABLE_KEY } } else { RNP_ERROR_NO_SUITABLE_KEY }) } #[no_mangle] pub unsafe extern "C" fn rnp_key_allows_usage(key: *mut RnpKey, usage: *const c_char, result: *mut bool) -> RnpResult { rnp_function!(rnp_key_allows_usage, crate::TRACE); let key = assert_ptr_mut!(key); let usage = assert_str!(usage); let result = assert_ptr_mut!(result); let policy = (*((*key).ctx)).policy().clone(); let usage = rnp_try!(RnpKeyUsage::from_rnp_id(usage)); let fp = key.fingerprint(); if let Some(cert) = (*key).cert().as_ref() .and_then(|c| c.with_policy(&policy, None).ok()) { if let Some(vka) = cert .keys().key_handle(fp) .nth(0) { *result = vka.has_any_key_flag(usage.to_keyflags()); } else { // Not valid under the policy. Don't allow use of key. *result = false; } } else { // Not valid under the policy. Don't allow use of key. *result = false; } rnp_success!() } // XXX: Revocation is a lot less binary than RNP would like it to be. // How should we handle third-party revocations here? #[no_mangle] pub unsafe extern "C" fn rnp_key_is_revoked(key: *mut RnpKey, result: *mut bool) -> RnpResult { rnp_function!(rnp_key_is_revoked, crate::TRACE); let key = assert_ptr_mut!(key); let result = assert_ptr_mut!(result); let policy = (*((*key).ctx)).policy().clone(); let fp = key.fingerprint(); if let Some(cert) = (*key).cert().as_ref() .and_then(|c| c.with_policy(&policy, None).ok()) { if let Some(vka) = cert .keys().key_handle(fp) .nth(0) { use openpgp::types::RevocationStatus::*; *result = match vka.revocation_status() { Revoked(_) => true, CouldBe(_) => false, // XXX NotAsFarAsWeKnow => false, }; } else { // Not valid under the policy. Treat as revoked. *result = true; } } else { // Not valid under the policy. Treat as revoked. *result = true; } rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_key_export_autocrypt(key: *mut RnpKey, subkey: *mut RnpKey, uid: *const c_char, output: *mut RnpOutput, flags: u32) -> RnpResult { rnp_function!(rnp_key_export_autocrypt, crate::TRACE); let key = assert_ptr_mut!(key); arg!(subkey); let uid = assert_str!(uid); // XXX: Could be NULL. let output = assert_ptr_mut!(output); arg!(flags); let policy = (*((*key).ctx)).policy().clone(); fn f(policy: &dyn Policy, cert: RwLockReadGuard, uid: &str, output: &mut dyn io::Write) -> openpgp::Result<()> { // Attempt to parse the given user id into only the email. let parsed_uid = UserID::from(uid.to_string()); let addr = parsed_uid.email().ok().and_then(|e| e) // But fall back to whatever we got. .unwrap_or(uid); // Instead of implementing what RNP does, we use // sequoia-autocrypt for the normalization, and ignore subkey. use sequoia_autocrypt::AutocryptHeader; let header = AutocryptHeader::new_sender(policy, &cert, addr, None)?; let cert = header.key.expect("on success, the cleaned cert is there"); cert.serialize(output)?; Ok(()) } rnp_return_status!(if let Some(cert) = (*key).cert() { if let Err(e) = f(&policy, cert, uid, &mut *output) { log!("sequoia-octopus: failed to export for autocrypt: {}", e); RNP_ERROR_GENERIC } else { RNP_SUCCESS } } else { RNP_ERROR_NO_SUITABLE_KEY }) } /// XXX: This API seems dangerous. #[no_mangle] pub unsafe extern "C" fn rnp_key_remove(key: *mut RnpKey, flags: RnpKeyRemoveFlags) -> RnpResult { rnp_function!(rnp_key_remove, crate::TRACE); let key = assert_ptr_mut!(key); arg!(flags); let ctx = &mut *key.ctx; let is_subkey = ! rnp_try!((*key).is_primary()); let remove_public = flags & RNP_KEY_REMOVE_PUBLIC > 0; let remove_secret = flags & RNP_KEY_REMOVE_SECRET > 0; let remove_subkeys = flags & RNP_KEY_REMOVE_SUBKEYS > 0; match (is_subkey, remove_subkeys) { (false, false) => { // XXX RNP allows one to delete a primary key without // deleting the subkeys. That is so odd. We don't // support that, because: // // - We store whole certificates, and without the // primary key, we have no place to store the subkey. // - The usecase for doing that seems dodgy. rnp_return_status!(RNP_ERROR_NOT_SUPPORTED); }, (false, true) => (), // OK. (true, false) => (), // OK. (true, true) => rnp_return_status!(RNP_ERROR_BAD_PARAMETERS), } let fp = key.fingerprint(); if remove_public { if ! ctx.certs.write().remove_all(&fp) { rnp_return_status!(RNP_ERROR_KEY_NOT_FOUND); } } else if remove_secret { if ! ctx.certs.write().remove_secret_key_material(&fp) { rnp_return_status!(RNP_ERROR_KEY_NOT_FOUND); } } rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_key_export(key: *mut RnpKey, output: *mut RnpOutput, flags: RnpKeyExportFlags) -> RnpResult { rnp_function!(rnp_key_export, crate::TRACE); let key = assert_ptr_mut!(key); let output = assert_ptr_mut!(output); arg!(flags); let key = &mut *key; let is_subkey = ! rnp_try!(key.is_primary()); let armored = flags & RNP_KEY_EXPORT_ARMORED > 0; // XXX: This flag is so odd. How can you not export the public // key? let _export_public = flags & RNP_KEY_EXPORT_PUBLIC > 0; let export_secret = flags & RNP_KEY_EXPORT_SECRET > 0; let export_subkeys = flags & RNP_KEY_EXPORT_SUBKEYS > 0; match (is_subkey, export_subkeys) { (false, false) => { // XXX RNP allows one to export a primary key without // exporting the subkeys. That is so odd. () // Grudgingly... }, (false, true) => (), // OK. (true, false) => (), // OK. (true, true) => rnp_return_status!(RNP_ERROR_BAD_PARAMETERS), } let mut cert = key.cert() .expect("worked for is_primary() above").clone(); // Cut down cert. if ! export_subkeys { cert = cert.retain_subkeys(|_| false); } else if is_subkey { let subkey = key.fingerprint(); cert = cert.retain_subkeys(|ska| ska.key().fingerprint() == subkey); } fn f(cert: Cert, armored: bool, export_secret: bool, sink: &mut RnpOutput) -> openpgp::Result<()> { match (armored, export_secret) { (false, false) => cert.export(sink)?, (false, true) => cert.as_tsk().export(sink)?, ( true, false) => cert.armored().export(sink)?, ( true, true) => cert.as_tsk().armored().export(sink)?, } Ok(()) } rnp_return_status!(if let Err(e) = f(cert, armored, export_secret, output) { warn!("failed to export key: {}", e); RNP_ERROR_WRITE } else { RNP_SUCCESS }) } #[no_mangle] pub unsafe extern "C" fn rnp_key_is_locked(key: *mut RnpKey, result: *mut bool) -> RnpResult { rnp_function!(rnp_key_is_locked, crate::TRACE); let key = assert_ptr_mut!(key); let result = assert_ptr_mut!(result); rnp_return_status!(if let Some(k) = key.key.parts_as_secret().ok() { *result = (*key.ctx).key_is_locked(k); RNP_SUCCESS } else { if thunderbird_workaround!() { // Tell Thunderbird that keys without secret key material // are unlocked. This will make transparent encryption // using gpg-agent work. *result = false; RNP_SUCCESS } else { // Stock RNP will return an error if invoked on keys // without secret key material. RNP_ERROR_NO_SUITABLE_KEY } }) } #[no_mangle] pub unsafe extern "C" fn rnp_key_lock(key: *mut RnpKey) -> RnpResult { rnp_function!(rnp_key_lock, crate::TRACE); let key = assert_ptr_mut!(key); rnp_return_status!(if let Some(k) = key.key.parts_as_secret().ok() { (*key.ctx).key_lock(k); RNP_SUCCESS } else { RNP_ERROR_NO_SUITABLE_KEY }) } #[no_mangle] pub unsafe extern "C" fn rnp_key_unlock(key: *mut RnpKey, password: *const c_char) -> RnpResult { rnp_function!(rnp_key_unlock, crate::TRACE); let key = assert_ptr_mut!(key); let password = if password.is_null() { arg!(password); None } else { arg!(""); Some(rnp_try!(cstr_to_str(password)).to_string().into()) }; rnp_return_status!(if let Some(k) = key.key.parts_as_secret().ok() { if let Err(e) = (*key.ctx).key_unlock(k.clone(), password) { warn!("rnp_key_unlock: {}", e); RNP_ERROR_GENERIC } else { RNP_SUCCESS } } else { RNP_ERROR_NO_SUITABLE_KEY }) } #[no_mangle] pub unsafe extern "C" fn rnp_key_is_protected(key: *mut RnpKey, result: *mut bool) -> RnpResult { rnp_function!(rnp_key_is_protected, crate::TRACE); let key = assert_ptr_mut!(key); let result = assert_ptr_mut!(result); *result = (key.has_secret() && ! key.has_unencrypted_secret()) || (*key.ctx).certs.key_on_agent(&key.fingerprint()); t!("Key {:X} is {}protected", key.fingerprint(), if *result { "" } else { "not " }); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_key_get_protection_type(key: *mut RnpKey, typ: *mut *const c_char) -> RnpResult { use openpgp::crypto::S2K; use openpgp::packet::key::SecretKeyMaterial; rnp_function!(rnp_key_get_protection_type, crate::TRACE); let key = assert_ptr_mut!(key); let typ = assert_ptr_mut!(typ); let r_typ = if (*(*key).ctx).certs.key_on_agent(&(*key).fingerprint()) { // The secret key material is available, but we have no idea // how it is protected (it's unclear why this is even // relevant!). But, if we don't return something that // suggests that the secret key material is available // *locally*, then Thunderbird will decide the key doesn't // really have secret key material (even if // rnp_key_have_secret returns true :/). So, we just say that // the secret key material is unencrypted. "None" } else if let Ok(key) = key.parts_as_secret() { match key.secret() { SecretKeyMaterial::Unencrypted(_) => "None", SecretKeyMaterial::Encrypted(e) => { #[allow(deprecated)] match e.s2k() { S2K::Iterated { .. } => "Encrypted-Hashed", S2K::Salted { .. } => "Encrypted", S2K::Simple { .. } => "Encrypted", S2K::Private { tag: 101, parameters } => if let Some(parameters) = parameters { match parameters.get(0) { Some(0) => // PGP_S2K_GPG_NONE "Unknown", Some(1) => // PGP_S2K_GPG_NO_SECRET "GPG-None", Some(2) => // PGP_S2K_GPG_SMARTCARD "GPG-Smartcard", _ => "Unknown", } } else { "Unknown" }, _ => "Unknown", } } } } else { "Unknown" }; t!("returning {:?}", r_typ); *typ = str_to_rnp_buffer(r_typ); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_key_protect(rnp_key: *mut RnpKey, password: *const c_char, cipher: *const c_char, cipher_mode: *const c_char, hash: *const c_char, iterations: size_t) -> RnpResult { rnp_function!(rnp_key_protect, crate::TRACE); let rnp_key = assert_ptr_mut!(rnp_key); let password = assert_str!(confidential => password); arg!(cipher); arg!(cipher_mode); arg!(hash); arg!(iterations); let fp = rnp_key.fingerprint(); let ctx = &mut *rnp_key.ctx; let mut key = if let Ok(key) = rnp_key.key.clone().parts_into_secret() { key } else if ctx.certs.key_on_agent(&fp) { // The secret key material is managed by the gpg-agent, we // cannot change the password, nor should we try to. Just // turn this into a NOP. rnp_return_status!(RNP_SUCCESS); } else { rnp_return_status!(RNP_ERROR_NO_SUITABLE_KEY); }; let f = || -> openpgp::Result<()> { if key.has_unencrypted_secret() { t!("Key {:X} is NOT currently protected", fp); } else { t!("Key {:X} is currently protected", fp); key = ctx.decrypt_key_for(None, key, RnpPasswordFor::Protect) .map_err(|_| Error::BadPassword)?; } let key = key.encrypt_secret(&password.into())?; // Update the copy of the key in this handle. rnp_key.key = key.clone().parts_into_unspecified(); if let Some(mut cert) = rnp_key.cert_mut() { let key: openpgp::Packet = if key.fingerprint() == cert.fingerprint() { key.role_into_primary().into() } else { key.role_into_subordinate().into() }; *cert = cert.clone().insert_packets(Some(key))?.0; Ok(()) } else { Err(anyhow::anyhow!("Key for {:X} not found in keyring", fp)) } }; rnp_return!(f()) } #[no_mangle] pub unsafe extern "C" fn rnp_key_unprotect(rnp_key: *mut RnpKey, password: *const c_char) -> RnpResult { rnp_function!(rnp_key_unprotect, crate::TRACE); let rnp_key = assert_ptr_mut!(rnp_key); let fp = rnp_key.fingerprint(); let password = if password.is_null() { arg!(password); None } else { arg!(""); Some(rnp_try!(cstr_to_str(password))) }; t!("password = {:?}", if password.is_some() { "Something" } else { "None" }); let ctx = &mut *rnp_key.ctx; let mut key = if let Ok(key) = rnp_key.key.clone().parts_into_secret() { key } else if ctx.certs.key_on_agent(&fp) { // The secret key material is managed by the gpg-agent, we // cannot change the password, nor should we try to. Just // turn this into a NOP. rnp_return_status!(RNP_SUCCESS); } else { rnp_return_status!(RNP_ERROR_NO_SUITABLE_KEY); }; let f = || -> openpgp::Result<()> { if key.has_unencrypted_secret() { t!("Key {:X} is NOT currently protected, we're done", fp); return Ok(()); } else { t!("Key {:X} is currently protected", fp); if let Some(p) = password { key = key.decrypt_secret(&p.into()) .map_err(|_| Error::BadPassword)?; } else { key = ctx.decrypt_key_for(None, key, RnpPasswordFor::Protect) .map_err(|_| Error::BadPassword)?; } } // Update the copy of the key in this handle. rnp_key.key = key.clone().parts_into_unspecified(); if let Some(mut cert) = rnp_key.cert_mut() { let key: openpgp::Packet = if key.fingerprint() == cert.fingerprint() { key.role_into_primary().into() } else { key.role_into_subordinate().into() }; *cert = cert.clone().insert_packets(Some(key))?.0; Ok(()) } else { Err(anyhow::anyhow!("Key for {:X} not found in keyring", fp)) } }; rnp_return!(f()) } #[no_mangle] pub unsafe extern "C" fn rnp_key_export_revocation(key: *mut RnpKey, output: *mut RnpOutput, flags: u32, hash: *const c_char, code: *const c_char, reason: *const c_char) -> RnpResult { rnp_function!(rnp_key_export_revocation, crate::TRACE); let key = assert_ptr_mut!(key); let output = assert_ptr_mut!(output); arg!(flags); let hash = if hash.is_null() { arg!(hash); None } else { let hash = assert_str!(hash); Some(rnp_try!(HashAlgorithm::from_rnp_id(hash))) }; let code = if code.is_null() { arg!(code); ReasonForRevocation::Unspecified } else { let code = assert_str!(code); rnp_try!(ReasonForRevocation::from_rnp_id(code)) }; let reason = if reason.is_null() { b"" } else { assert_str!(reason).as_bytes() }; if ! rnp_try!(key.is_primary()) { rnp_return_status!(RNP_ERROR_BAD_PARAMETERS); } if ! key.has_secret() { warn!("rnp_key_export_revocation: creating third-party revocation \ not implemented"); rnp_return_status!(RNP_ERROR_NOT_IMPLEMENTED); } fn f(key: &mut RnpKey, sink: &mut RnpOutput, hash: Option, code: ReasonForRevocation, reason: &[u8]) -> openpgp::Result<()> { let ctx = unsafe { &mut *key.ctx }; let primary_key = key.key.clone().parts_into_secret()?; let mut keypair = if let Some(cert) = key.cert().as_ref() { ctx.decrypt_key_for(Some(cert), primary_key, // XXX: This is what RNP // does, there is no // dedicated reason. RnpPasswordFor::Unlock)? } else { ctx.decrypt_key_for(None, primary_key, // XXX: This is what RNP // does, there is no // dedicated reason. RnpPasswordFor::Unlock)? } .into_keypair()?; let sig = CertRevocationBuilder::new() .set_reason_for_revocation(code, reason)? .build(&mut keypair, &*key.cert().expect("it is a primary"), hash)?; Packet::from(sig).serialize(sink)?; Ok(()) } rnp_return_status!(if let Err(e) = f(key, output, hash, code, reason) { warn!("rnp_key_export_revocation: {}", e); RNP_ERROR_GENERIC } else { RNP_SUCCESS }) } #[no_mangle] pub unsafe extern "C" fn rnp_key_revoke(key: *mut RnpKey, flags: u32, hash: *const c_char, code: *const c_char, reason: *const c_char) -> RnpResult { rnp_function!(rnp_key_revoke, crate::TRACE); let key = assert_ptr_mut!(key); arg!(flags); let ctx = &mut *key.ctx; let hash = if hash.is_null() { arg!(hash); None } else { let hash = assert_str!(hash); Some(rnp_try!(HashAlgorithm::from_rnp_id(hash))) }; let code = if code.is_null() { arg!(code); ReasonForRevocation::Unspecified } else { let code = assert_str!(code); rnp_try!(ReasonForRevocation::from_rnp_id(code)) }; let reason = if reason.is_null() { b"" } else { assert_str!(reason).as_bytes() }; let cert = if let Some(cert) = key.cert() { cert.clone() } else { rnp_return_status!(RNP_ERROR_NO_SUITABLE_KEY); }; if ! cert.primary_key().key().has_secret() { warn!("rnp_key_export_revocation: creating third-party revocation \ not implemented"); rnp_return_status!(RNP_ERROR_NOT_IMPLEMENTED); } fn f(ctx: &mut RnpContext, cert: Cert, key: &mut RnpKey, hash: Option, code: ReasonForRevocation, reason: &[u8]) -> openpgp::Result<()> { let is_primary = cert.fingerprint() == key.fingerprint(); let primary_key = cert.primary_key().key().clone().parts_into_secret()? .role_into_unspecified(); let mut keypair = if let Some(cert) = key.cert() { ctx.decrypt_key_for(Some(&*cert), primary_key, // XXX: This is what RNP // does, there is no // dedicated reason. RnpPasswordFor::Unlock)? } else { ctx.decrypt_key_for(None, primary_key, // XXX: This is what RNP // does, there is no // dedicated reason. RnpPasswordFor::Unlock)? } .into_keypair()?; let sig = if is_primary { CertRevocationBuilder::new() .set_reason_for_revocation(code, reason)? .build(&mut keypair, &cert, hash)? } else { SubkeyRevocationBuilder::new() .set_reason_for_revocation(code, reason)? .build(&mut keypair, &cert, key.key.role_as_subordinate(), hash)? }; // Merge the revocation into the store. ctx.certs.write().insert(cert.insert_packets(Some(sig))?.0); Ok(()) } rnp_return_status!(if let Err(e) = f(ctx, cert, key, hash, code, reason) { warn!("{}", e); RNP_ERROR_GENERIC } else { RNP_SUCCESS }) } #[no_mangle] pub unsafe extern "C" fn rnp_key_set_expiration(key: *mut RnpKey, expiry: u32) -> RnpResult { rnp_function!(rnp_key_set_expiration, crate::TRACE); let key = assert_ptr_mut!(key); arg!(expiry); let ctx = &mut *key.ctx; let expiry = if expiry == 0 { None } else { Some(Duration::new(expiry as u64, 0)) }; let cert = if let Some(cert) = key.cert() { cert.clone() } else { rnp_return_status!(RNP_ERROR_NO_SUITABLE_KEY); }; fn f(ctx: &mut RnpContext, cert: Cert, key: &RnpKey, expiry: Option) -> openpgp::Result<()> { let is_primary = cert.fingerprint() == key.fingerprint(); let absolute_expiration_time = expiry.map(|e| key.creation_time() + e); let mut primary_signer = match cert.primary_key().key().clone().role_into_unspecified() .parts_into_secret() { Ok(key) => { ctx.decrypt_key_for(Some(&cert), key, // XXX: This is what RNP // does, there is no // dedicated reason. RnpPasswordFor::Unlock)? .into_keypair()? }, Err(e) => { warn!("Cannot change expiration time without \ primary secret: {}", e); return Err(e); }, }; let policy = ctx.policy().clone(); let vcert = cert.with_policy(&policy, None)?; let sigs = if is_primary { vcert.primary_key() .set_expiration_time( &mut primary_signer, absolute_expiration_time)? } else { let vka = vcert.keys().key_handle(key.fingerprint()).nth(0) .ok_or(anyhow::anyhow!( "subkey not bound, cannot change expiration time"))?; let mut subkey_signer = if vka.for_signing() || vka.for_authentication() { match vka.key().clone().role_into_unspecified() .parts_into_secret() { Ok(key) => { Some(ctx.decrypt_key_for(Some(&cert), key, // XXX: This is what RNP // does, there is no // dedicated reason. RnpPasswordFor::Unlock)? .into_keypair()?) }, Err(e) => { warn!("Cannot change expiration time without \ subkey secret: {}", e); return Err(e); }, } } else { None }; vka.set_expiration_time( &mut primary_signer, subkey_signer.as_mut() .map(|pair| -> &mut dyn openpgp::crypto::Signer { &mut *pair }), absolute_expiration_time)? }; // Merge the updated cert into the store. ctx.certs.write().insert(cert.insert_packets(sigs)?.0); Ok(()) } rnp_return_status!(if let Err(e) = f(ctx, cert, key, expiry) { warn!("{}", e); RNP_ERROR_GENERIC } else { RNP_SUCCESS }) } #[no_mangle] pub unsafe extern "C" fn rnp_key_valid_till(key: *mut RnpKey, result: *mut u32) -> RnpResult { rnp_function!(rnp_key_valid_till, crate::TRACE); let result = assert_ptr_mut!(result); // The key expiration time is a 33-bit number, but the // RNP API only has place for a 32-bit number :/. let mut till = 0; let r = rnp_key_valid_till64(key, &mut till as *mut _); *result = min(u32::MAX as u64, till) as u32; r } #[no_mangle] pub unsafe extern "C" fn rnp_key_valid_till64(key: *mut RnpKey, result: *mut u64) -> RnpResult { rnp_function!(rnp_key_valid_till64, crate::TRACE); let key = assert_ptr_mut!(key); let result = assert_ptr_mut!(result); // We need to clone it, because key.cert() takes a &mut. let k = key.key.clone().parts_into_public(); let cert = if let Some(cert) = key.cert() { cert } else { t!("Could not find key's certificate."); rnp_return_status!(RNP_ERROR_BAD_PARAMETERS); }; // According to the rnp_key_valid_till's documentation // (https://github.com/rnpgp/rnp/blob/7b636f1/include/rnp/rnp.h#L1518), // this function returns 0 if the key was never valid. Otherwise, // it returns the minimum of the expiration and revocation time. // // Thunderbird only checks if the result is 0, and if so does not // show the key in the OpenPGP Key Manager. So, if a key was // *ever* valid, then we should return a non-zero result. This // means we want the Null policy (there is a cryptographically // valid signature), not the Standard Policy, which is stricter. let vc = if let Ok(vc) = cert.with_policy(crate::NP, None) { vc } else { // If it is not even valid with the null policy, return 0. *result = 0; t!("cert not valid."); rnp_return_status!(RNP_SUCCESS); }; // Make sure the key is valid. let ka = if let Some(ka) = vc.keys().filter(|ka| *ka.key() == k).nth(0) { ka } else { // If it is not even valid with the null policy, return 0. *result = 0; t!("key not valid."); rnp_return_status!(RNP_SUCCESS); }; // A key's expiration is the minimum of its expiration // and the primary key's expiration. let t_min = |a, b| { match (a, b) { (Some(p), Some(s)) => Some(min(p, s)), (None, Some(s)) => Some(s), (Some(p), None) => Some(p), (None, None) => None, } }; // Recall: None means the key doesn't expire. let mut t = t_min(vc.primary_key().key_expiration_time(), ka.key_expiration_time()); if let RevocationStatus::CouldBe(revs) = vc.revocation_status() { t = revs.into_iter().fold(t, |t, rev| { t_min(t, rev.signature_creation_time()) }); }; if let RevocationStatus::CouldBe(revs) = ka.revocation_status() { t = revs.into_iter().fold(t, |t, rev| { t_min(t, rev.signature_creation_time()) }); }; if let Ok(unix) = t .unwrap_or(std::time::UNIX_EPOCH + Duration::new(u32::MAX as u64, 0)) .duration_since(std::time::UNIX_EPOCH) { *result = unix.as_secs(); t!("valid till: {}", *result); } else { // Ummm... this shouldn't happen, but if it does // conservatively consider the key to be invalid. *result = 0; t!("time failure."); } rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_key_25519_bits_tweaked(key: *mut RnpKey, result_out: *mut bool) -> RnpResult { rnp_function!(rnp_key_25519_bits_tweaked, crate::TRACE); let key = assert_ptr_mut!(key); let ctx = &mut *key.ctx; let result_out = assert_ptr_mut!(result_out); // First, check that we are a Curve25519 key. use openpgp::crypto::mpi::PublicKey; match key.key.mpis() { | PublicKey::ECDH { curve, .. } => { use openpgp::types::Curve::*; // We want to return an error on unknown curves. match curve { Cv25519 => (), _ => rnp_return_status!(RNP_ERROR_BAD_PARAMETERS), }; }, // Not an ECC algorithm. _ => rnp_return_status!(RNP_ERROR_BAD_PARAMETERS), } // RNP's implementation also considers unlocked keys. Do the // same. let maybe_unlocked_key = ctx.key_unlocked_ref(&key.key) .map(|k| k.parts_as_unspecified()) .unwrap_or(&key.key); // Now check the secret. use openpgp::packet::key; rnp_return_status!(match maybe_unlocked_key.optional_secret() { Some(key::SecretKeyMaterial::Unencrypted(k)) => k.map(|secret| { use openpgp::crypto::mpi::SecretKeyMaterial; match secret { SecretKeyMaterial::ECDH { scalar } => { // OpenPGP stores the secret in reverse order. const CURVE25519_SIZE: usize = 32; const FIRST: usize = CURVE25519_SIZE - 1; const LAST: usize = 0; // Curve25519 Paper, Sec. 3: A user can, for // example, generate 32 uniform random bytes, // clear bits 0, 1, 2 of the first byte, clear bit // 7 of the last byte, and set bit 6 of the last // byte. let scalar = scalar.value_padded(CURVE25519_SIZE); *result_out = scalar[FIRST] & !0b1111_1000 == 0 && scalar[LAST] & 0b1000_0000 == 0 && scalar[LAST] & 0b0100_0000 == 0b0100_0000; RNP_SUCCESS }, _ => RNP_ERROR_BAD_PARAMETERS, } }), _ => RNP_ERROR_BAD_PARAMETERS, }) } #[no_mangle] pub unsafe extern "C" fn rnp_key_25519_bits_tweak(key: *mut RnpKey) -> RnpResult { rnp_function!(rnp_key_25519_bits_tweak, crate::TRACE); let key = assert_ptr_mut!(key); // First, check that we are a Curve25519 key. use openpgp::crypto::mpi::PublicKey; match key.key.mpis() { | PublicKey::ECDH { curve, .. } => { use openpgp::types::Curve::*; // We want to return an error on unknown curves. match curve { Cv25519 => (), _ => rnp_return_status!(RNP_ERROR_BAD_PARAMETERS), }; }, // Not an ECC algorithm. _ => rnp_return_status!(RNP_ERROR_BAD_PARAMETERS), } // Now check and clone the secret. const CURVE25519_SIZE: usize = 32; use openpgp::packet::key; use openpgp::crypto::mpi; let mut secret = match key.key.optional_secret() { Some(key::SecretKeyMaterial::Unencrypted(k)) => rnp_try!(k.map(|secret| { match secret { mpi::SecretKeyMaterial::ECDH { scalar } => Ok(scalar.value_padded(CURVE25519_SIZE)), _ => Err(RNP_ERROR_BAD_PARAMETERS), } })), _ => rnp_return_status!(RNP_ERROR_BAD_PARAMETERS), }; // OpenPGP stores the secret in reverse order. const FIRST: usize = CURVE25519_SIZE - 1; const LAST: usize = 0; // Curve25519 Paper, Sec. 3: A user can, for example, generate 32 // uniform random bytes, clear bits 0, 1, 2 of the first byte, // clear bit 7 of the last byte, and set bit 6 of the last byte. secret[FIRST] &= 0b1111_1000; secret[LAST] &= !0b1000_0000; secret[LAST] |= 0b0100_0000; // Finally, replace the secret in the key. key.key = key.key.clone().add_secret( key::SecretKeyMaterial::Unencrypted( mpi::SecretKeyMaterial::ECDH { scalar: secret.into(), } .into())) .0.parts_into_unspecified(); // And update the cert. let k = key.key.clone().parts_into_secret().unwrap(); if let Some(mut cert) = key.cert_mut() { let key: openpgp::Packet = if k.fingerprint() == cert.fingerprint() { k.role_into_primary().into() } else { k.role_into_subordinate().into() }; *cert = rnp_try_or!(cert.clone().insert_packets(Some(key)), RNP_ERROR_GENERIC).0; rnp_success!() } else { warn!("Key for {:X} not found in keyring", k.fingerprint()); rnp_return_status!(RNP_ERROR_GENERIC) } } /// Removes the given UID from KEY. #[no_mangle] pub unsafe extern "C" fn rnp_uid_remove(key: *mut RnpKey, uid: *mut RnpUserID) -> RnpResult { rnp_function!(rnp_uid_remove, crate::TRACE); let key = assert_ptr_mut!(key); let uid = assert_ptr_ref!(uid); rnp_return_status!(if let Some(mut cert) = key.cert_mut() { *cert = cert.clone().retain_userids(|u| u.userid() != uid.userid()); RNP_SUCCESS } else { RNP_ERROR_GENERIC }) } type RnpKeySignaturesCB = fn(*mut RnpContext, *mut c_void, *mut RnpSignature, *mut RnpKeyRemoveSignatureAction); /// Removes signatures from KEY. #[no_mangle] pub unsafe extern "C" fn rnp_key_remove_signatures(key: *mut RnpKey, flags: RnpKeyRemoveSignatureFlags, pred: *mut RnpKeySignaturesCB, cookie: *mut c_void) -> RnpResult { rnp_function!(rnp_key_remove_signatures, crate::TRACE); let key = assert_ptr_mut!(key); arg!(flags); arg!(pred); arg!(cookie); let remove_invalid = flags & RNP_KEY_SIGNATURE_INVALID > 0; if remove_invalid { warn!("Flag RNP_KEY_SIGNATURE_INVALID not implemented, ignoring."); } let remove_unknown_key = flags & RNP_KEY_SIGNATURE_UNKNOWN_KEY > 0; if remove_unknown_key { warn!("Flag RNP_KEY_SIGNATURE_UNKNOWN_KEY not implemented, ignoring."); } let remove_non_selfsig = flags & RNP_KEY_SIGNATURE_NON_SELF_SIG > 0; if ! pred.is_null() { warn!("Predicate parameter not implemented, ignoring."); } if let Some(mut cert) = key.cert_mut() { let mut acc = Vec::new(); // The primary key and related signatures. let pk_bundle = cert.primary_key().bundle(); acc.push(pk_bundle.key().clone().into()); for s in pk_bundle.self_signatures() { acc.push(s.clone().into()); } for s in pk_bundle.self_revocations() { acc.push(s.clone().into()); } if ! remove_non_selfsig { for s in pk_bundle.other_revocations() { acc.push(s.clone().into()); } for s in pk_bundle.certifications() { acc.push(s.clone().into()); } } // The subkeys and related signatures. for skb in cert.keys().subkeys() { acc.push(skb.key().clone().into()); for s in skb.self_signatures() { acc.push(s.clone().into()); } for s in skb.self_revocations() { acc.push(s.clone().into()); } if ! remove_non_selfsig { for s in skb.other_revocations() { acc.push(s.clone().into()); } for s in skb.certifications() { acc.push(s.clone().into()); } } } // The UserIDs. for uidb in cert.userids() { acc.push(uidb.userid().clone().into()); for s in uidb.self_signatures() { acc.push(s.clone().into()); } for s in uidb.self_revocations() { acc.push(s.clone().into()); } for s in uidb.approvals() { acc.push(s.clone().into()); } if ! remove_non_selfsig { for s in uidb.other_revocations() { acc.push(s.clone().into()); } for s in uidb.certifications() { acc.push(s.clone().into()); } } } // The UserAttributes. for uab in cert.user_attributes() { acc.push(uab.user_attribute().clone().into()); for s in uab.self_signatures() { acc.push(s.clone().into()); } for s in uab.self_revocations() { acc.push(s.clone().into()); } for s in uab.approvals() { acc.push(s.clone().into()); } if ! remove_non_selfsig { for s in uab.other_revocations() { acc.push(s.clone().into()); } for s in uab.certifications() { acc.push(s.clone().into()); } } } if ! remove_invalid { for s in cert.bad_signatures() { acc.push(s.clone().into()); } } *cert = rnp_try_or!(Cert::from_packets(acc.into_iter()), RNP_ERROR_GENERIC); rnp_success!() } else { rnp_return_status!(RNP_ERROR_GENERIC) } } #[cfg(test)] mod tests { use super::*; use libc::c_char; use std::ffi::CString; use sequoia_openpgp::serialize::SerializeInto; use crate::rnp_ffi_create; use crate::io::RnpInput; use crate::import::rnp_import_keys; use crate::rnp_output_to_memory; use crate::rnp_output_memory_get_buf; use crate::rnp_input_from_memory; use crate::rnp_input_destroy; use crate::rnp_output_destroy; use crate::rnp_ffi_destroy; const RNP_SUCCESS: u32 = crate::error::RNP_SUCCESS.as_u32(); #[test] fn key_consistency() -> openpgp::Result<()> { // Consider the following scenario: // // 1. Look up a key using rnp_locate_key and get back an // RnpKey handle. // // 2. Modify the underlying certificate without using the key // handle returned in (1). // // 3. Use the key handle returned in (1) to access the // certificate. // // Does (3) see the modifications made in (2) or not? It // should see them. // // To check for this, create a certificate with a password, // and (1) get a reference to the primary key. We then (2) // change the password of the certificate's key, and then (3) // export it. We then check if the password was actually // modified. If not, then the key handle did not see the // changes done in (2). let change_pw = |ctx: *mut RnpContext, cert: &Cert, old: Option<&str>, new: Option<&str>| -> openpgp::Result<()> { let old_c = old.map(|s| { CString::new(s.as_bytes()).unwrap().into_raw() as *const c_char }); let new_c = new.map(|s| { CString::new(s.as_bytes()).unwrap().into_raw() as *const c_char }); let mut primary = None; for (i, k) in cert.keys().enumerate() { let mut key: *mut RnpKey = std::ptr::null_mut(); let r = unsafe { rnp_locate_key(ctx, b"fingerprint\x00".as_ptr() as *const c_char, CString::new(k.key().fingerprint().to_string()) .unwrap().into_raw() as *const c_char, &mut key as *mut *mut _) }; assert_eq!(r, RNP_SUCCESS); if let Some(old_c) = old_c { let r = unsafe { rnp_key_unprotect(key, old_c) }; if r != RNP_SUCCESS { return Err(anyhow::anyhow!("Failed to change password")); } log!("After unprotecting {} with {}", k.key().fingerprint(), old.unwrap()); crate::cert_dump( &unsafe { &mut *ctx } .certs.read() .by_primary_fp(&cert.fingerprint()).unwrap()); } if let Some(new_c) = new_c { let r = unsafe { rnp_key_protect(key, new_c, std::ptr::null(), std::ptr::null(), std::ptr::null(), 0) }; assert_eq!(r, RNP_SUCCESS); log!("After protecting {} with {}", k.key().fingerprint(), new.unwrap()); crate::cert_dump( &unsafe { &mut *ctx } .certs.read() .by_primary_fp(&cert.fingerprint()).unwrap()); } if i == 0 { primary = Some(key); } else { let r = unsafe { rnp_key_handle_destroy(key) }; assert_eq!(r, RNP_SUCCESS); } } let primary = primary.unwrap(); // Export the key use the primary key handle. let mut output: *mut RnpOutput = std::ptr::null_mut(); let r = unsafe { rnp_output_to_memory(&mut output as *mut *mut _, 0) }; assert_eq!(r, RNP_SUCCESS); let r = unsafe { rnp_key_export(primary, output, RNP_KEY_EXPORT_ARMORED | RNP_KEY_EXPORT_PUBLIC | RNP_KEY_EXPORT_SECRET | RNP_KEY_EXPORT_SUBKEYS) }; assert_eq!(r, RNP_SUCCESS); let mut buf: *mut u8 = std::ptr::null_mut(); let mut len: libc::size_t = 0; let r = unsafe { rnp_output_memory_get_buf( output, &mut buf as *mut *mut _, &mut len as *mut _, false) }; assert_eq!(r, RNP_SUCCESS); let mut input: *mut RnpInput = std::ptr::null_mut(); let r = unsafe { rnp_input_from_memory( &mut input as *mut *mut _, buf, len, false) }; assert_eq!(r, RNP_SUCCESS); // And reload load. let r = unsafe { rnp_import_keys( ctx, input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS, std::ptr::null_mut() as *mut *mut _) }; assert_eq!(r, RNP_SUCCESS); let r = unsafe { rnp_input_destroy(input) }; assert_eq!(r, RNP_SUCCESS); let r = unsafe { rnp_output_destroy(output) }; assert_eq!(r, RNP_SUCCESS); let r = unsafe { rnp_key_handle_destroy(primary) }; assert_eq!(r, RNP_SUCCESS); Ok(()) }; let mut ctx: *mut RnpContext = std::ptr::null_mut(); let r = unsafe { rnp_ffi_create( &mut ctx as *mut *mut _, b"GPG\x00".as_ptr() as *const c_char, b"GPG\x00".as_ptr() as *const c_char) }; assert_eq!(r, RNP_SUCCESS); // Create a key. let (alice, _) = openpgp::cert::CertBuilder::new() .add_userid("alice") .add_signing_subkey() .set_password(Some("passw0rd".into())) .generate()?; // Import it into the context. let mut buffer = Vec::new(); alice.as_tsk().serialize(&mut buffer)?; let mut input: *mut RnpInput = std::ptr::null_mut(); let r = unsafe { rnp_input_from_memory( &mut input as *mut *mut _, buffer.as_ptr(), buffer.len(), false) }; assert_eq!(r, RNP_SUCCESS); // And load the certificate. let r = unsafe { rnp_import_keys( ctx, input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS, std::ptr::null_mut() as *mut *mut _) }; assert_eq!(r, RNP_SUCCESS); let r = unsafe { rnp_input_destroy(input) }; assert_eq!(r, RNP_SUCCESS); log!("ctx: keystore: {:?}", unsafe { &mut *ctx }.certs); change_pw(ctx, &alice, Some("passw0rd"), Some("new-password-1234")) .expect("success"); change_pw(ctx, &alice, Some("new-password-1234"), Some("third-password")) .expect("correct password"); // Make sure we can still change the password. assert!(change_pw(ctx, &alice, Some("bad-password"), Some("more-password")) .is_err()); let r = unsafe { rnp_ffi_destroy(ctx) }; assert_eq!(r, RNP_SUCCESS); Ok(()) } #[test] fn curve25519_secret_tweaking() { fn test(buffer: &[u8]) { let mut ctx: *mut RnpContext = std::ptr::null_mut(); let r = unsafe { rnp_ffi_create( &mut ctx as *mut *mut _, b"GPG\x00".as_ptr() as *const c_char, b"GPG\x00".as_ptr() as *const c_char) }; assert_eq!(r, RNP_SUCCESS); let mut input: *mut RnpInput = std::ptr::null_mut(); let r = unsafe { rnp_input_from_memory( &mut input as *mut *mut _, buffer.as_ptr(), buffer.len(), false) }; assert_eq!(r, RNP_SUCCESS); // And load the certificate. let r = unsafe { rnp_import_keys( ctx, input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS, std::ptr::null_mut() as *mut *mut _) }; assert_eq!(r, RNP_SUCCESS); let r = unsafe { rnp_input_destroy(input) }; assert_eq!(r, RNP_SUCCESS); log!("ctx: keystore: {:?}", unsafe { &mut *ctx }.certs); let mut key: *mut RnpKey = std::ptr::null_mut(); let r = unsafe { rnp_locate_key( ctx, b"fingerprint\x00".as_ptr() as *const c_char, b"EA02B24FFD4C1B96616D3DF24766F6B9D5F21EB6\x00".as_ptr() as *const c_char, &mut key as *mut *mut _) }; assert_eq!(r, RNP_SUCCESS); let mut result = false; let r = unsafe { rnp_key_25519_bits_tweaked(key, &mut result) }; assert_eq!(r, RNP_SUCCESS); assert_eq!(result, false); let r = unsafe { rnp_key_25519_bits_tweak(key) }; assert_eq!(r, RNP_SUCCESS); // Check modification against reference. let reference = b"-----BEGIN PGP PRIVATE KEY BLOCK----- Comment: EB85 BB5F A33A 75E1 5E94 4E63 F231 550C 4F47 E38E Comment: Alice Lovelace xVgEXEcE6RYJKwYBBAHaRw8BAQdArjWwk3FAqyiFbFBKT4TzXcVBqPTB3gmzlC/U b7O1u10AAP9XBeW6lzGOLx7zHH9AsUDUTb2pggYGMzd0P3ulJ2AfvQ4RzSZBbGlj ZSBMb3ZlbGFjZSA8YWxpY2VAb3BlbnBncC5leGFtcGxlPsKQBBMWCAA4AhsDBQsJ CAcCBhUKCQgLAgQWAgMBAh4BAheAFiEE64W7X6M6deFelE5j8jFVDE9H444FAl2l nzoACgkQ8jFVDE9H447pKwD6A5xwUqIDprBzrHfahrImaYEZzncqb25vkLV2arYf a78A/R3AwtLQvjxwLDuzk4dUtUwvUYibL2sAHwj2kGaHnfICx10EXEcE6RIKKwYB BAGXVQEFAQEHQEL/BiGtq0k84Km1wqQw2DIikVYrQrMttN8d7BPfnr4iAwEIBwAA /3/xFPG6U17rhTuq+07gmEvaFYKfxRB6sgAYiW6TMTpQEK7CeAQYFggAIBYhBOuF u1+jOnXhXpROY/IxVQxPR+OOBQJcRwTpAhsMAAoJEPIxVQxPR+OOWdABAMUdSzpM hzGs1O0RkWNQWbUzQ8nUOeD9wNbjE3zR+yfRAQDbYqvtWQKN4AQLTxVJN5X5AWyb Pnn+We1aTBhaGa86AQ== =3GfK -----END PGP PRIVATE KEY BLOCK----- "; use openpgp::parse::Parse; let reference_cert = openpgp::Cert::from_bytes(reference).unwrap(); unsafe { use std::ops::Deref; assert_eq!( (*key).cert.as_ref().unwrap().as_ref() .read().unwrap().deref(), &reference_cert); } let mut result = false; let r = unsafe { rnp_key_25519_bits_tweaked(key, &mut result) }; assert_eq!(r, RNP_SUCCESS); assert_eq!(result, true); let r = unsafe { rnp_key_handle_destroy(key) }; assert_eq!(r, RNP_SUCCESS); let r = unsafe { rnp_ffi_destroy(ctx) }; assert_eq!(r, RNP_SUCCESS); } // Secret with LSB set. test(b"-----BEGIN PGP PRIVATE KEY BLOCK----- xVgEXEcE6RYJKwYBBAHaRw8BAQdArjWwk3FAqyiFbFBKT4TzXcVBqPTB3gmzlC/U b7O1u10AAP9XBeW6lzGOLx7zHH9AsUDUTb2pggYGMzd0P3ulJ2AfvQ4RzSZBbGlj ZSBMb3ZlbGFjZSA8YWxpY2VAb3BlbnBncC5leGFtcGxlPsKQBBMWCAA4AhsDBQsJ CAcCBhUKCQgLAgQWAgMBAh4BAheAFiEE64W7X6M6deFelE5j8jFVDE9H444FAl2l nzoACgkQ8jFVDE9H447pKwD6A5xwUqIDprBzrHfahrImaYEZzncqb25vkLV2arYf a78A/R3AwtLQvjxwLDuzk4dUtUwvUYibL2sAHwj2kGaHnfICx10EXEcE6RIKKwYB BAGXVQEFAQEHQEL/BiGtq0k84Km1wqQw2DIikVYrQrMttN8d7BPfnr4iAwEIBwAA /3/xFPG6U17rhTuq+07gmEvaFYKfxRB6sgAYiW6TMTpREK/CeAQYFggAIBYhBOuF u1+jOnXhXpROY/IxVQxPR+OOBQJcRwTpAhsMAAoJEPIxVQxPR+OOWdABAMUdSzpM hzGs1O0RkWNQWbUzQ8nUOeD9wNbjE3zR+yfRAQDbYqvtWQKN4AQLTxVJN5X5AWyb Pnn+We1aTBhaGa86AQ== =iQ01 -----END PGP PRIVATE KEY BLOCK----- "); // Secret with MSB set. test(b"-----BEGIN PGP PRIVATE KEY BLOCK----- xVgEXEcE6RYJKwYBBAHaRw8BAQdArjWwk3FAqyiFbFBKT4TzXcVBqPTB3gmzlC/U b7O1u10AAP9XBeW6lzGOLx7zHH9AsUDUTb2pggYGMzd0P3ulJ2AfvQ4RzSZBbGlj ZSBMb3ZlbGFjZSA8YWxpY2VAb3BlbnBncC5leGFtcGxlPsKQBBMWCAA4AhsDBQsJ CAcCBhUKCQgLAgQWAgMBAh4BAheAFiEE64W7X6M6deFelE5j8jFVDE9H444FAl2l nzoACgkQ8jFVDE9H447pKwD6A5xwUqIDprBzrHfahrImaYEZzncqb25vkLV2arYf a78A/R3AwtLQvjxwLDuzk4dUtUwvUYibL2sAHwj2kGaHnfICx10EXEcE6RIKKwYB BAGXVQEFAQEHQEL/BiGtq0k84Km1wqQw2DIikVYrQrMttN8d7BPfnr4iAwEIBwAB AP/xFPG6U17rhTuq+07gmEvaFYKfxRB6sgAYiW6TMTpQEDDCeAQYFggAIBYhBOuF u1+jOnXhXpROY/IxVQxPR+OOBQJcRwTpAhsMAAoJEPIxVQxPR+OOWdABAMUdSzpM hzGs1O0RkWNQWbUzQ8nUOeD9wNbjE3zR+yfRAQDbYqvtWQKN4AQLTxVJN5X5AWyb Pnn+We1aTBhaGa86AQ== =YOA6 -----END PGP PRIVATE KEY BLOCK----- "); // Secret with 2^254 bit cleared. test(b"-----BEGIN PGP PRIVATE KEY BLOCK----- xVgEXEcE6RYJKwYBBAHaRw8BAQdArjWwk3FAqyiFbFBKT4TzXcVBqPTB3gmzlC/U b7O1u10AAP9XBeW6lzGOLx7zHH9AsUDUTb2pggYGMzd0P3ulJ2AfvQ4RzSZBbGlj ZSBMb3ZlbGFjZSA8YWxpY2VAb3BlbnBncC5leGFtcGxlPsKQBBMWCAA4AhsDBQsJ CAcCBhUKCQgLAgQWAgMBAh4BAheAFiEE64W7X6M6deFelE5j8jFVDE9H444FAl2l nzoACgkQ8jFVDE9H447pKwD6A5xwUqIDprBzrHfahrImaYEZzncqb25vkLV2arYf a78A/R3AwtLQvjxwLDuzk4dUtUwvUYibL2sAHwj2kGaHnfICx10EXEcE6RIKKwYB BAGXVQEFAQEHQEL/BiGtq0k84Km1wqQw2DIikVYrQrMttN8d7BPfnr4iAwEIBwAA /j/xFPG6U17rhTuq+07gmEvaFYKfxRB6sgAYiW6TMTpQEG3CeAQYFggAIBYhBOuF u1+jOnXhXpROY/IxVQxPR+OOBQJcRwTpAhsMAAoJEPIxVQxPR+OOWdABAMUdSzpM hzGs1O0RkWNQWbUzQ8nUOeD9wNbjE3zR+yfRAQDbYqvtWQKN4AQLTxVJN5X5AWyb Pnn+We1aTBhaGa86AQ== =Txnk -----END PGP PRIVATE KEY BLOCK----- "); } #[test] fn find_cert() -> anyhow::Result<()> { // Create a key. let (alice, _) = openpgp::cert::CertBuilder::new() .add_userid("alice") .add_signing_subkey() .generate()?; // Import it into the context. let buffer = alice.to_vec()?; for strip_secrets in [false, true] { eprintln!("stripping secrets {:?}", strip_secrets); let mut ctx: *mut RnpContext = std::ptr::null_mut(); let r = unsafe { rnp_ffi_create( &mut ctx as *mut *mut _, b"GPG\x00".as_ptr() as *const c_char, b"GPG\x00".as_ptr() as *const c_char) }; assert_eq!(r, RNP_SUCCESS); let mut input: *mut RnpInput = std::ptr::null_mut(); let r = unsafe { rnp_input_from_memory( &mut input as *mut *mut _, buffer.as_ptr(), buffer.len(), false) }; assert_eq!(r, RNP_SUCCESS); // And load the certificate. let r = unsafe { rnp_import_keys( ctx, input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS, std::ptr::null_mut() as *mut *mut _) }; assert_eq!(r, RNP_SUCCESS); let r = unsafe { rnp_input_destroy(input) }; assert_eq!(r, RNP_SUCCESS); log!("ctx: keystore: {:?}", unsafe { &mut *ctx }.certs); for (i, key) in alice.keys().enumerate() { eprintln!("{}. {}...", i, key.key().fingerprint()); let key = key.key().clone(); let key = if strip_secrets { key.take_secret().0 .parts_into_unspecified().role_into_unspecified() } else { key.parts_into_unspecified().role_into_unspecified() }; let rnp_key = RnpKey::without_cert(ctx, key); assert_eq!(rnp_key.cert.unwrap().read().unwrap().fingerprint(), alice.fingerprint()); } let r = unsafe { rnp_ffi_destroy(ctx) }; assert_eq!(r, RNP_SUCCESS); } Ok(()) } } sequoia-octopus-librnp-1.11.1/src/keystore.rs000064400000000000000000002415701046102023000173440ustar 00000000000000use std::{ collections::{ HashMap, HashSet, hash_map::Entry, }, fmt, fs, fs::File, io::Write, iter::FromIterator, path::PathBuf, sync::{ Arc, atomic, atomic::AtomicUsize, Condvar, mpsc, Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard, }, thread, time::{ Duration, SystemTime, }, }; use anyhow::Result; use sequoia_openpgp as openpgp; use openpgp::{ Fingerprint, KeyID, Cert, packet::prelude::*, policy::StandardPolicy, serialize::Serialize, }; use crate::{ gpg, Keygrip, parcimonie::Parcimonie, wot::{WoT, WoTWorkerHandle}, }; /// Controls tracing in this module. const TRACE: bool = crate::TRACE; #[derive(Debug)] struct MapEntry { cert: Arc>, // We cache the fingerprint so that we can figure out what // certificate is in here without locking cert. fingerprint: Fingerprint, extra: E, } impl MapEntry { pub fn cert(&self) -> RwLockReadGuard { self.cert.read().unwrap() } pub fn cert_mut(&self) -> RwLockWriteGuard { self.cert.write().unwrap() } pub fn cert_cell(&self) -> Arc> { Arc::clone(&self.cert) } fn fingerprint(&self) -> &Fingerprint { &self.fingerprint } fn extra(&self) -> &E { &self.extra } } impl MapEntry<()> { // This function takes a reader lock on cert. fn new(cert: &Arc>) -> Self { MapEntry { cert: Arc::clone(cert), fingerprint: cert.read().unwrap().fingerprint(), extra: (), } } } impl MapEntry { // referent is the key (not the certificate!) that refers to this // entry. // // This function takes a reader lock on cert. fn new2(cert: &Arc>, referent: Fingerprint) -> Self { MapEntry { cert: Arc::clone(cert), fingerprint: cert.read().unwrap().fingerprint(), extra: referent, } } } pub struct KeystoreData { gpg_ctx: Option, // The bool indicates whether the key is considered external. by_primary_fp: HashMap>, by_primary_id: HashMap>>, by_primary_grip: HashMap>>, by_subkey_fp: HashMap>>, by_subkey_id: HashMap>>, // It is possible for there to be a certificate that has multiple // subkeys with the same grip! If we remove one subkey, then the // grip should not be removed. To distinguish this case, we also // track the subkey. by_subkey_grip: HashMap>>, // Keys that are managed by the agent. keys_on_agent: HashSet, keys_on_agent_last_refresh: SystemTime, keys_on_agent_tag: Option, // The last time that the keystore was *possibly* modified. Each // write attempt is a tick. Unfortunately, this can also result // in false negatives, because Keystore::cert_cell can be used to // store a reference. Right now that function is only used // outside of the Keystore to lock and unlock secret key material. // But, since this is just a safety measure, this is good enough. last_update: AtomicUsize, // The location of the keystore. If set, the keystore is // periodically flushed to disk in the background. directory: Option, flush_thread: Option>, background_thread: Option>, } impl Default for KeystoreData { fn default() -> Self { Self { gpg_ctx: None, by_primary_fp: Default::default(), by_primary_id: Default::default(), by_primary_grip: Default::default(), by_subkey_fp: Default::default(), by_subkey_id: Default::default(), by_subkey_grip: Default::default(), keys_on_agent: Default::default(), keys_on_agent_last_refresh: std::time::UNIX_EPOCH, keys_on_agent_tag: None, last_update: AtomicUsize::new(0), directory: None, flush_thread: None, background_thread: None, } } } pub struct Keystore { data: Arc>, parcimonie: Option, /// Write-side of a channel to send commands to the background thread. background_sender: Option>>>, // Whether the keystore is in the process of loading a keyring. // See Keystore::load_keyring_in_background and // Keystore::block_on_load. background_busy: Arc<(Mutex, Condvar)>, /// Controls the WoT worker thread. wot_handle: Option, } enum BackgroundCommand { /// Update the GnuPG keyring. LoadGnuPGKeyring, /// Import a keyring in the background. LoadKeyring { keyring: Vec, import_secret_keys: bool, }, } impl Default for Keystore { fn default() -> Self { Self { data: Arc::new(RwLock::new(Default::default())), parcimonie: None, background_sender: None, background_busy: Arc::new((Mutex::new(0), Condvar::new())), wot_handle: None, } } } impl fmt::Debug for Keystore { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("Keystore") .field("parcimonie", &self.parcimonie.is_some()) .field("data", &self.data) .finish() } } impl Keystore { // Starts a parcimonie instance to keep this keystore up to date. // // If there is already parcimonie instance running, this does // nothing. pub fn start_parcimonie(&mut self, policy: StandardPolicy<'static>) -> Result<()> { if self.parcimonie.is_none() { self.parcimonie = Some(Parcimonie::new(policy, self)?); } Ok(()) } pub fn create_ref(&self) -> Arc> { Arc::clone(&self.data) } pub fn read(&self) -> RwLockReadGuard { self.data.read().unwrap() } pub fn write(&self) -> RwLockWriteGuard { self.data.write().unwrap() } /// Returns whether the key appears to be on the gpg agent. /// /// This is at most a few seconds out of date. pub fn key_on_agent(&self, key: &Fingerprint) -> bool { let (key_on_agent, need_refresh) = self.data.read().unwrap().key_on_agent(key); if need_refresh { self.data.write().unwrap().key_on_agent_hard(key) } else { key_on_agent } } // The location of the keystore (where the pubring.gpg and // secring.gpg files are). If set, the keystore is periodically // flushed to disk in the background. pub fn set_directory(&mut self, directory: PathBuf) -> Result<()> { let mut ks = self.data.write().unwrap(); ks.directory = Some(directory); if ks.flush_thread.is_none() { // Start a thread to periodically flush everything to // disk. let ks_ref = self.create_ref(); ks.flush_thread = Some(thread::Builder::new() .name("sq flusher".to_string()) .spawn(move || { KeystoreData::flush_thread(ks_ref); })?); } Ok(()) } // Start the thread that checks for updates to the gpg key store. // // Returns whether or not a thread was started. That is, if the // thread was already running, this returns false. fn background_threads_start(&mut self, policy: Arc>>) -> openpgp::Result { if self.background_sender.is_some() { // Already started. return Ok(false); } let mut ks = self.data.write().unwrap(); if self.background_sender.is_some() { // Someone else already started the thread while we were // blocked on self.data. assert!(ks.background_thread.is_some()); return Ok(false); } assert!(ks.background_thread.is_none()); assert!(self.background_sender.is_none()); assert!(self.wot_handle.is_none()); let wot_handle = WoT::background_thread_start(self.create_ref(), policy.clone())?; let ks_ref = self.create_ref(); let (sender, receiver) = mpsc::channel(); let background_busy = Arc::clone(&self.background_busy); // Cause block_on_load to wait until the gpg keyring is // loaded. let mut background_busy_ = self.background_busy.0.lock().unwrap(); assert_eq!(*background_busy_, 0); *background_busy_ += 1; drop(background_busy_); let wh = wot_handle.clone(); ks.background_thread = Some(thread::Builder::new().name("sq loader".to_string()) .spawn(move || KeystoreData::background_thread( ks_ref, wh,receiver, background_busy))?); self.background_sender = Some(Arc::new(Mutex::new(sender))); self.wot_handle = Some(wot_handle); Ok(true) } /// Load the gpg keyring into the keystore. /// /// Parse and load a keyring. If this encounters some corruption /// in the keyring and is able to recover, it does not report an /// error. /// /// This also starts a thread to monitor the gpg store. This /// imports updates from the gpg keyring (XXX: but not removals!). pub fn load_gpg_keyring_in_background(&mut self, policy: Arc>>) -> openpgp::Result<()> { rnp_function!(Keystore::load_gpg_keyring, TRACE); if !self.background_threads_start(policy)? { // If we just started the thread, then it will read the // gpg keyring on its own. Otherwise, signal that it // should reread it now. self.background_sender.as_ref().expect("started thread") .lock().unwrap() .send(BackgroundCommand::LoadGnuPGKeyring)?; } Ok(()) } /// Loads the specified keyring in the background. /// /// If import_secret_keys is true, then it also imports any secret /// key material. Otherwise, secret key material is stripped. pub fn load_keyring_in_background(&mut self, keyring: Vec, import_secret_keys: bool, policy: Arc>>) -> openpgp::Result<()> { rnp_function!(Keystore::load_keyring_in_background, TRACE); if let Err(err) = self.background_threads_start(policy) { warn!("Failed to start gpg thread: {}", err); KeystoreData::load_keyring( &mut self.data, &keyring, import_secret_keys, false)?; } else { *self.background_busy.0.lock().unwrap() += 1; self.background_sender.as_ref().expect("started thread") .lock().unwrap() .send(BackgroundCommand::LoadKeyring { keyring, import_secret_keys, })?; } Ok(()) } /// Updates the Web-of-Trust information in the background. pub fn update_wot_in_background(&mut self, policy: Arc>>) -> openpgp::Result<()> { rnp_function!(Keystore::update_wot_in_background, super::TRACE); self.background_threads_start(policy)?; self.wot_handle.as_ref().expect("started thread").notify(); Ok(()) } /// Blocks the current thread until any keyrings that are being /// loaded in the background have been loaded. /// /// This includes the initial load of the gpg keyring, and any /// keyrings added via `Keystore::load_keyring_in_background`. /// /// Returns whether we blocked. pub fn block_on_load(&mut self) -> openpgp::Result { // We do *not* start the gpg thread if it is not already // running. This must only be done by rnp_load_keys. rnp_function!(Keystore::block_on_load, TRACE); let background_busy = self.background_busy.0.lock().unwrap(); if *background_busy == 0 { return Ok(false); } // We're going to block TB. We can't convince TB to show a // progress bar, but we can show one ourselves. At least on // non-Windows systems. let mut child = None; if ! cfg!(windows) { use std::process::Command; use std::process::Stdio; // zenity requires something on stdin. let mut command = Command::new("zenity"); command .stdin(Stdio::piped()) .stdout(Stdio::inherit()) .stderr(Stdio::inherit()) .arg("--text=Loading keys, please wait.") .arg("--progress") .arg("--pulsate") .arg("--no-cancel"); t!("Running command: {:?}", command); match command.spawn() { Ok(c) => child = Some(c), Err(err) => t!("Running zenity: {}", err), }; // Try kdialog. if child.is_none() { let mut command = Command::new("kdialog"); command .stdin(Stdio::null()) .stdout(Stdio::null()) .stderr(Stdio::null()) .arg("--passivepopup") .arg("Loading OpenPGP keys, please wait") .arg("10s"); t!("Running command: {:?}", command); match command.spawn() { Ok(c) => child = Some(c), Err(err) => t!("Running kdialog: {}", err), }; } }; assert!(self.background_sender.is_some()); drop(self.background_busy.1 .wait_while(background_busy, |background_busy| *background_busy > 0) .unwrap()); if let Some(mut child) = child { if let Err(err) = child.kill() { t!("Killing zenity: {}", err); } } Ok(true) } } const VEC_MAP_ENTRY_EMPTY: Vec> = Vec::new(); const VEC_MAP_ENTRY_EMPTY_REF: &Vec> = &VEC_MAP_ENTRY_EMPTY; const VEC_MAP_ENTRY_FPR_EMPTY: Vec> = Vec::new(); const VEC_MAP_ENTRY_FPR_EMPTY_REF: &Vec> = &VEC_MAP_ENTRY_FPR_EMPTY; impl fmt::Debug for KeystoreData { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { writeln!(f, "KeystoreData {{")?; writeln!(f, " by_primary_fp {{")?; for (k, e) in self.by_primary_fp.iter() { writeln!(f, " {} => {}", k, e.extra())?; } writeln!(f, " }}")?; writeln!(f, " by_primary_id {{")?; for (k, v) in self.by_primary_id.iter() { writeln!(f, " {} =>\n {}", k, v.iter() .map(|e| e.fingerprint().to_string()) .collect::>().join(",\n "))?; } writeln!(f, " }}")?; writeln!(f, " by_primary_grip {{")?; for (g, v) in self.by_primary_grip.iter() { writeln!(f, " {} =>\n {}", g, v.iter() .map(|e| { format!("{}", e.fingerprint().to_string()) }) .collect::>().join(",\n "))?; } writeln!(f, " }}")?; writeln!(f, " by_subkey_fp {{")?; for (k, v) in self.by_subkey_fp.iter() { writeln!(f, " {} =>\n {}", k, v.iter() .map(|e| e.fingerprint().to_string()) .collect::>().join(",\n "))?; } writeln!(f, " }}")?; writeln!(f, " by_subkey_id {{")?; for (k, v) in self.by_subkey_id.iter() { writeln!(f, " {} =>\n {}", k, v.iter() .map(|e| e.fingerprint().to_string()) .collect::>().join(",\n "))?; } writeln!(f, " }}")?; writeln!(f, " by_subkey_grip {{")?; for (g, v) in self.by_subkey_grip.iter() { writeln!(f, " {} =>\n {}", g, v.iter() .map(|e| { format!("{} (via {})", e.fingerprint().to_string(), e.extra) }) .collect::>().join(",\n "))?; } writeln!(f, " }}")?; writeln!(f, "}}")?; Ok(()) } } impl KeystoreData { /// Returns the time (in ticks) where the keystore could have /// possibly been modified. pub fn last_update(&self) -> usize { self.last_update.load(atomic::Ordering::Relaxed) } /// Marks the keystore as updated. pub fn mark_updated(&self) { self.last_update.fetch_add(1, atomic::Ordering::Relaxed); } // Periodically flush the key store to disk. fn flush_thread(ks: Arc>) { rnp_function!(KeystoreData::flush_thread, TRACE); let mut last_flush = 0; loop { // About once an hour (but not quite to avoid possible // resonance). thread::sleep(Duration::new(59 * 60, 0)); let ks = ks.read().unwrap(); let last_update = ks.last_update(); if last_flush == last_update { t!("Not flushing keystore to disk: no changes"); continue; } else { last_flush = last_update } if let Some(ref dir) = ks.directory { let pubring_tmp = dir.join("pubring.gpg~"); let secring_tmp = dir.join("secring.gpg~"); t!("Writing keystore to {:?} and {:?}", pubring_tmp, secring_tmp); let mut pubring_fd = match File::create(&pubring_tmp) { Ok(fd) => fd, Err(err) => { t!("Creating {:?}: {}", pubring_tmp, err); continue; } }; let mut secring_fd = match File::create(&secring_tmp) { Ok(fd) => fd, Err(err) => { t!("Creating {:?}: {}", secring_tmp, err); continue; } }; let mut ok = true; let mut pub_count = 0; let mut sec_count = 0; for cert in ks.to_save() { let r = if cert.is_tsk() { sec_count += 1; cert.as_tsk().serialize(&mut secring_fd) } else { pub_count += 1; cert.serialize(&mut pubring_fd) }; if let Err(err) = r { t!("Serializing {} {}: {}", if cert.is_tsk() { "secret key" } else { "certificate" }, cert.fingerprint(), err); ok = false; break; } } if ok && pub_count == 0 { // We didn't write any bytes. Currently, // Thunderbird will not invoke rnp_load_keys if a // keyring is a zero-sized file. To avoid that, // i.e. make sure that rnp_load_keys is invoked, // we write a placeholder there. See the comments // in rnp_ffi_create for details. if let Err(_) = openpgp::Packet::Marker(Default::default()) .serialize(&mut pubring_fd) { ok = false; } } if ok && sec_count == 0 { // Likewise. if let Err(_) = openpgp::Packet::Marker(Default::default()) .serialize(&mut secring_fd) { ok = false; } } if ok { if let Err(err) = pubring_fd.flush() { t!("Failed to flush {:?}: {}", pubring_tmp, err); continue; } drop(pubring_fd); if let Err(err) = secring_fd.flush() { t!("Failed to flush {:?}: {}", secring_tmp, err); continue; } drop(secring_fd); let pubring = dir.join("pubring.gpg"); if let Err(err) = fs::rename(&pubring_tmp, &pubring) { warn!("Failed to rename {:?} to {:?}: {}", pubring_tmp, pubring, err); } let secring = dir.join("secring.gpg"); if let Err(err) = fs::rename(&secring_tmp, &secring) { warn!("Failed to rename {:?} to {:?}: {}", secring_tmp, secring, err); } t!("Keystore flushed to {:?} and {:?}", pubring, secring); } } } } fn load_keyring(ks: &Arc>, keyring: &[u8], import_secret_keys: bool, external: bool) -> openpgp::Result<()> { rnp_function!(KeystoreData::load_keyring, TRACE); // Parse the keyring without the lock. use sequoia_openpgp_mt::keyring; let certs = match keyring::parse(std::io::Cursor::new(keyring)) { Ok(certs) => certs, Err(err) => { t!("Parsing keyring: {}", err); return Err(err); } }; // And now take the lock to insert the certificates. let mut inserted = 0; let mut errors = 0; let mut ks = ks.write().unwrap(); for cert in certs { match cert { Ok(mut c) => { if ! import_secret_keys { c = c.strip_secret_key_material(); } ks.insert_(c, external); inserted += 1; } Err(e) => { // We definitely don't want to error out when // importing stuff from gpg. So, warn, but don't // stop. warn!("sequoia-octopus: Error parsing cert: {}", e); errors += 1; }, } } drop(ks); t!("Successfully added {} certificates, {} errors", inserted, errors); Ok(()) } /// The background worker thread. /// /// Monitor certificate stores (like the GnuPG keyring) for /// updates, reads certificates in the background, and updates the /// Web-of-Trust calculations. fn background_thread(ks: Arc>, wot_handle: WoTWorkerHandle, receiver: mpsc::Receiver, background_busy: Arc<(Mutex, Condvar)>) { rnp_function!(KeystoreData::background_thread, TRACE); struct GpgState { last_check: std::time::SystemTime, first_time: bool, check_now: bool, keyring_tag: Option, ctx: Option, } let mut gpg = GpgState { last_check: std::time::UNIX_EPOCH, first_time: true, check_now: true, keyring_tag: None, ctx: None, }; loop { // About once an hour (but not quite to avoid possible // resonance). The first time through, we don't import // the keyring. We did that from the main thread, because // reasons. let timeout = if gpg.check_now { Duration::new(0, 0) } else { let threshold = Duration::new(61 * 60, 0); let elapsed = SystemTime::now().duration_since(gpg.last_check) .unwrap_or_else(|_| { // Clock skew? Who knows. Just wait a bit // and try again. gpg.last_check = SystemTime::now(); Duration::new(0, 0) }); if elapsed > threshold { Duration::new(0, 0) } else { threshold - elapsed } }; if timeout > Duration::new(0, 0) { t!("Waiting (at most) {:?}", timeout); } let r = receiver.recv_timeout(timeout); match r { Ok(BackgroundCommand::LoadKeyring { keyring, import_secret_keys, }) => { if let Err(err) = KeystoreData::load_keyring( &ks, &keyring, import_secret_keys, false) { warn!("Loading keyring: {}", err); } let mut background_busy_ = background_busy.0.lock().unwrap(); assert!(*background_busy_ > 0); *background_busy_ -= 1; if *background_busy_ == 0 { background_busy.1.notify_all(); } drop(background_busy_); // Trigger a WoT update. wot_handle.notify(); continue; } Ok(BackgroundCommand::LoadGnuPGKeyring) => { gpg.check_now = true; // We loop to drain the message queue. continue; }, // Poll now. Err(mpsc::RecvTimeoutError::Timeout) => (), // ks was destroyed. Err(_) => { t!("Keystore disconnected. Time to shutdown."); return; } } gpg.check_now = false; let now = SystemTime::now(); let elapsed = now.duration_since(gpg.last_check); match elapsed { Err(err) => { t!("Error computing time since last update: {}", err); continue; }, Ok(elapsed) => { if elapsed < Duration::new(90, 0) { t!("Throttling GnuPG certstore updates."); continue; } } } if gpg.ctx.is_none() { // See if the user install gpg in the meantime. gpg.ctx = gpg::Ctx::new().ok(); } if let Some(gpg_ctx) = gpg.ctx.as_ref() { t!("Checking for gpg updates (last check was: {:?} ago)...", elapsed); // Import the gpg keyring. match gpg::export( gpg_ctx, None, gpg.keyring_tag.as_ref()) { Ok((keyring, keyring_tag_tmp)) => { t!("Got {} bytes", keyring.len()); gpg.keyring_tag = Some(keyring_tag_tmp); if let Err(err) = KeystoreData::load_keyring( &ks, &keyring, false, true) { warn!("Loading keyring: {}", err); } } Err(err) => { t!("Exporting gpg keyring: {}", err); } } } if gpg.first_time { gpg.first_time = false; let mut background_busy_ = background_busy.0.lock().unwrap(); assert!(*background_busy_ > 0); *background_busy_ -= 1; if *background_busy_ == 0 { background_busy.1.notify_all(); } drop(background_busy_); } // Trigger a WoT update. wot_handle.notify(); gpg.last_check = SystemTime::now(); } } /// Returns a reference to a gpg::Ctx. fn gpg_ctx(&mut self) -> Option<&gpg::Ctx> { rnp_function!(KeystoreData::gpg_ctx, TRACE); if self.gpg_ctx.is_none() { match gpg::Ctx::new() { Ok(ctx) => self.gpg_ctx = Some(ctx), Err(err) => t!("Creating a gpg context: {}", err), } } self.gpg_ctx.as_ref() } /// Returns whether the specified key appears to be on the gpg /// agent. /// /// This always tries to refresh the cache first. pub fn key_on_agent_hard(&mut self, key: &Fingerprint) -> bool { rnp_function!(KeystoreData::key_on_agent_hard, TRACE); self.gpg_ctx(); if let Some(ctx) = self.gpg_ctx.as_ref() { match gpg::list_secret_keys(ctx, None, self.keys_on_agent_tag.as_ref()) { Err(err) => { match err.downcast::() { // Unchanged. Short-circuit. Ok(gpg::Error::Unchanged(tag)) => { self.keys_on_agent_tag = Some(tag); }, Err(e) => t!("List gpg secret keys: {}", e), } } Ok((secret_keys, hash)) => { self.keys_on_agent_tag = Some(hash); self.keys_on_agent = HashSet::from_iter( secret_keys.into_iter().map(|(_primary, key)| key)); } } } // We even set this on error, as we want to throttle them too. self.keys_on_agent_last_refresh = SystemTime::now(); self.keys_on_agent.get(key).is_some() } /// Returns whether the key appears to be on the gpg agent. /// /// These values are cached. /// /// The first return value is whether, according to the cache, the /// key is on the agent. /// /// The second return value is whether the cache is old and should /// be refreshed. /// /// If the cache should be refreshed, you should follow up with a /// call to `key_on_agent_hard`. /// /// The calls are split so that this function doesn't need a write /// lock. pub fn key_on_agent(&self, key: &Fingerprint) -> (bool, bool) { rnp_function!(KeystoreData::key_on_agent, TRACE); let now = SystemTime::now(); let elapsed = now.duration_since(self.keys_on_agent_last_refresh); let expired = match elapsed { // If the cache is more than 1 minute out of date, // refresh it. Ok(elapsed) => { if elapsed > Duration::new(60, 0) { true } else { false } } Err(_err) => { true } }; (self.keys_on_agent.get(key).is_some(), expired) } /// Returns the number of certificates in the key store. pub fn count(&self) -> usize { self.by_primary_fp.len() } /// Returns an iterator over the fingerprint of each certificates /// in the store. pub fn fingerprints<'a>(&'a self) -> impl Iterator + 'a { self.by_primary_fp.keys().map(|fpr| fpr.clone()) } /// Returns an iterator over the keyid of each certificate in the /// store. pub fn keyids<'a>(&'a self) -> impl Iterator + 'a { self.by_primary_id.keys().map(|id| id.clone()) } /// Returns an iterator over the key grips of the primary key of /// each certificate in the store. pub fn keygrips<'a>(&'a self) -> impl Iterator + 'a { self.by_primary_grip.keys().map(|grip| grip.clone()) } /// Returns a clone of the specified certificate's RefCell. /// /// fp is the certificate's fingerprint. pub fn cert_cell_by_primary_fp(&self, fp: &Fingerprint) -> Option>> { self.by_primary_fp.get(fp).map(|e| e.cert_cell()) } /// Returns a clone of the specified certificates' storage cells. /// /// `fp` is a certificate or subkey fingerprint. pub fn cert_cell_by_fp<'a>(&'a self, fp: &Fingerprint) -> impl Iterator>> + 'a { std::iter::once(fp.clone()) .chain(self.by_subkey_fp.get(fp) .unwrap_or(VEC_MAP_ENTRY_EMPTY_REF) .iter() .map(|e| e.fingerprint().clone())) .filter_map(|fp| self.by_primary_fp.get(&fp).map(|e| e.cert_cell())) } /// Returns a reference to the cert with the given fingerprint, if /// any. pub fn by_primary_fp(&self, fp: &Fingerprint) -> Option> { self.by_primary_fp.get(fp).map(|e| e.cert()) } /// Returns a reference to each cert with the given keyid. pub fn by_primary_id(&self, id: &KeyID) -> impl Iterator> { self.by_primary_id.get(id) .unwrap_or(VEC_MAP_ENTRY_EMPTY_REF) .iter() .map(|e| e.cert()) } /// Returns a reference to each cert for which the primary key has /// the given keygrip. pub fn by_primary_grip(&self, grip: &Keygrip) -> impl Iterator> { self.by_primary_grip.get(grip) .unwrap_or(VEC_MAP_ENTRY_EMPTY_REF) .iter() .map(|e| e.cert()) } /// Returns a reference to each cert containing a subkey with the /// given fingerprint. pub fn by_subkey_fp(&self, fp: &Fingerprint) -> impl Iterator> { self.by_subkey_fp.get(fp) .unwrap_or(VEC_MAP_ENTRY_EMPTY_REF) .iter() .map(|e| e.cert()) } /// Returns a reference to each cert containing a subkey with the /// given keyid, if any. pub fn by_subkey_id(&self, id: &KeyID) -> impl Iterator> { self.by_subkey_id.get(id) .unwrap_or(VEC_MAP_ENTRY_EMPTY_REF) .iter() .map(|e| e.cert()) } /// Returns a reference to each cert containing a primary key or /// subkey with the given fingerprint. /// /// If a certificate contains a primary key and a subkey with the /// same fingerprint, that certificate is returned twice. pub fn by_fp(&self, fp: &Fingerprint) -> impl Iterator> { self.by_primary_fp(fp) .into_iter() .chain(self.by_subkey_fp(fp)) } /// Returns a reference to each cert containing a subkey with the /// given keygrip, if any. /// /// If a certificate contains multiple subkeys with the same /// keygrip, then the certificate is returned multiple times, one /// for each such subkey. pub fn by_subkey_grip(&self, grip: &Keygrip) -> impl Iterator> { self.by_subkey_grip.get(grip) .unwrap_or(VEC_MAP_ENTRY_FPR_EMPTY_REF) .iter() .map(|e| e.cert()) } /// Returns an iterator over all the certs in the key store. pub fn iter(&self) -> impl Iterator> { self.by_primary_fp.values().map(|e| e.cert()) } /// Returns an iterator over the certs in the key store, which are /// not marked as external. pub fn to_save(&self) -> impl Iterator> { self.by_primary_fp.values().filter_map(|e| { if *e.extra() { None } else { Some(e.cert()) } }) } /// Inserts a cert. /// /// This function prefers secret key material in the cert that is /// being merged. fn insert_(&mut self, cert: Cert, is_external: bool) { rnp_function!(Keystore::insert_, TRACE); let fp = cert.fingerprint(); let id = KeyID::from(&fp); let grip = Keygrip::of(cert.primary_key().key().mpis()).ok(); t!("Inserting {}, {:?} (external: {})", fp, cert.userids().nth(0).map(|ua| ua.userid()), is_external); let cert_cell: Arc>; let is_update = match self.by_primary_fp.entry(fp.clone()) { Entry::Occupied(mut oe) => { t!("Already exists in keystore, merging."); let e = oe.get_mut(); cert_cell = Arc::clone(&e.cert); let mut cert_ref = cert_cell.write().unwrap(); if cert_ref.as_tsk() == cert.as_tsk() { // They are identical. There is nothing to do. t!("Update doesn't change certificate. Short-circuiting."); return; } // Merge. We want to prefer secret keys in `cert` // over the one in the store. Cert::insert_packets // replaces existing key material. Hence, we insert // only those packets that are either secret keys or // public ones that are not in `cert_ref`. let known_keys: HashSet = cert_ref.keys().map(|k| k.key().fingerprint()).collect(); let cert_update = cert_ref.clone() .insert_packets(cert.into_tsk().into_packets() .filter(|p| match p { // Insert only unknown public keys. Packet::PublicKey(k) => ! known_keys.contains(&k.fingerprint()), Packet::PublicSubkey(k) => ! known_keys.contains(&k.fingerprint()), // Insert everything else, including secret keys. _ => true, })) .expect("does not fail if the primary keys are equal").0; if cert_ref.as_tsk() == cert_update.as_tsk() { // They are identical. There is nothing to do. t!("Update doesn't add anything new. Short-circuiting."); return; } *cert_ref = cert_update; e.extra = e.extra && is_external; true } Entry::Vacant(ve) => { t!("New certificate, adding."); cert_cell = Arc::new(RwLock::new(cert)); ve.insert(MapEntry { cert: Arc::clone(&cert_cell), fingerprint: fp.clone(), extra: is_external, }); false } }; self.mark_updated(); // If this is an update, then the primary key id and primary // key grip mapping exist and are correct. if ! is_update { self.by_primary_id .entry(id) .and_modify(|v| v.push(MapEntry::new(&cert_cell))) .or_insert(vec![ MapEntry::new(&cert_cell) ]); if let Some(g) = grip { self.by_primary_grip .entry(g) .and_modify(|v| v.push(MapEntry::new(&cert_cell))) .or_insert(vec![ MapEntry::new(&cert_cell) ]); } } // Add subkey indices. If this is an update, indices may // already exist. Don't duplicate them. { let cert_locked = cert_cell.read().unwrap(); let cert_fp = cert_locked.fingerprint(); for sk in cert_locked.keys().subkeys() { macro_rules! add { ($v:expr, $id:expr) => ({ let present = $v.iter() .position(|e| { e.extra == $id && e.fingerprint() == &fp }) .is_some(); if present { t!("Sub-x mapping already present"); } else { t!("Sub-x mapping not yet present"); } if is_update && ! present { $v.push(MapEntry { cert: Arc::clone(&cert_cell), fingerprint: cert_fp.clone(), extra: $id, }); } else if ! is_update { assert!(! present); $v.push(MapEntry { cert: Arc::clone(&cert_cell), fingerprint: cert_fp.clone(), extra: $id, }); } }); } t!("Adding subkey fingerprint mapping for {}", sk.key().fingerprint()); self.by_subkey_fp.entry(sk.key().fingerprint()) .and_modify(|v| add!(v, ())) .or_insert(vec![ MapEntry::new(&cert_cell) ]); t!("Adding subkey keyid mapping for {}", sk.key().keyid()); self.by_subkey_id.entry(sk.key().keyid()) .and_modify(|v| add!(v, ())) .or_insert(vec![ MapEntry::new(&cert_cell) ]); if let Ok(g) = Keygrip::of(sk.key().mpis()) { t!("Adding subkey keygrip mapping for {}", g); self.by_subkey_grip.entry(g) .and_modify(|v| add!(v, sk.key().fingerprint())) .or_insert(vec![ MapEntry::new2(&cert_cell, sk.key().fingerprint()) ]); } } } t!("Done."); } /// Inserts a cert. /// /// The certificate is inserted as-is; secret key material is not /// stripped. pub fn insert(&mut self, cert: Cert) { self.insert_(cert, false) } /// Inserts a cert, which comes from an external source. pub fn insert_external(&mut self, cert: Cert) { self.insert_(cert, true) } /// Removes the subkey or certificate with fingerprint `fp`. /// /// Returns true if at least one key/cert has been removed. pub fn remove_all(&mut self, fp: &Fingerprint) -> bool { let mut removed_one = false; macro_rules! remove { ($fp:expr, $e:expr) => ({ let fp: &Fingerprint = $fp; let e: Entry<_, Vec>> = $e; match e { Entry::Occupied(mut oe) => { if let Some(i) = oe.get_mut() .iter().position(|e| e.fingerprint() == fp) { if oe.get().len() == 1 { assert_eq!(i, 0); oe.remove_entry(); } else { oe.get_mut().remove(i); } } else { unreachable!("entry not present"); } } Entry::Vacant(_) => unreachable!("entry not present"), } }); } // First, remove the cert with that fingerprint. if let Some(e) = self.by_primary_fp.remove(fp) { removed_one = true; // Remove other primary key indices. remove!(fp, self.by_primary_id.entry(KeyID::from(fp))); if let Ok(g) = Keygrip::of(e.cert().primary_key().key().mpis()) { remove!(fp, self.by_primary_grip.entry(g)); } // And remove any subkey indices. for sk in e.cert().keys().subkeys() { remove!(fp, self.by_subkey_fp.entry(sk.key().fingerprint())); remove!(fp, self.by_subkey_id.entry(sk.key().keyid())); if let Ok(g) = Keygrip::of(sk.key().mpis()) { remove!(fp, self.by_subkey_grip.entry(g)); } } } // Remove all subkeys with that fingerprint. if let Some(subkeys) = self.by_subkey_fp.remove(fp) { for e in subkeys.into_iter() { let mut cert = e.cert_mut(); let cert_fp = cert.fingerprint(); // Strip the subkey. let mut had_one = false; *cert = cert.clone().retain_subkeys(|ska| { if ska.key().fingerprint() == *fp { had_one = true; removed_one = true; remove!(&cert_fp, self.by_subkey_id.entry(ska.key().keyid())); if let Ok(g) = Keygrip::of(ska.key().mpis()) { remove!(&cert_fp, self.by_subkey_grip.entry(g)); } false } else { true } }); assert!(had_one); } } if removed_one { self.mark_updated(); } removed_one } /// Removes secret key material associated with `fp`. /// /// Returns true if any secret key material has been removed. pub fn remove_secret_key_material(&mut self, fp: &Fingerprint) -> bool { let mut removed_one = false; // First, remove the cert with that fingerprint. if let Some(e) = self.by_primary_fp.get_mut(fp) { let mut cert_ref = e.cert_mut(); if cert_ref.is_tsk() { removed_one = true; *cert_ref = cert_ref.clone().strip_secret_key_material(); } } // Then, remove secret key material from all subkeys with that // fingerprint. if let Some(v) = self.by_subkey_fp.get_mut(fp) { for e in v { // There's no easy way to remove secret key material // from an individual subkey. Instead we convert it // to packets, strip the secret key material, and then // convert it back. let mut cert_ref = e.cert_mut(); let packets = cert_ref.clone().into_tsk().into_packets() .collect::>(); *cert_ref = Cert::from_packets( packets.into_iter() .map(|packet| { match packet { Packet::SecretSubkey(k) if &k.fingerprint() == fp => { removed_one = true; Packet::PublicSubkey(k.take_secret().0) } packet => packet, } })) .expect("still valid"); } } if removed_one { self.mark_updated(); } removed_one } } #[cfg(test)] mod tests { use super::*; use anyhow::Context; use openpgp::policy::StandardPolicy; use openpgp::types::SignatureType; use openpgp::packet::signature::subpacket::SubpacketTag; // Makes sure the maps contain the specified number of keys. // // If a subkey appears on multiple certificates, it is only // counted once. fn ks_count(ks: &KeystoreData, n_pks: P, n_sks: S, ignore_grips: bool) where P: Into>, S: Into> { if let Some(n_pks) = n_pks.into() { assert_eq!(ks.by_primary_fp.len(), n_pks, "Expected {} primary fps, got {}\n{:?}", n_pks, ks.by_primary_fp.len(), ks); assert_eq!(ks.by_primary_id.len(), n_pks, "Expected {} primary key ids, got {}\n{:?}", n_pks, ks.by_primary_id.len(), ks); if ! ignore_grips { assert_eq!(ks.by_primary_grip.len(), n_pks, "Expected {} primary grips, got {}\n{:?}", n_pks, ks.by_primary_grip.len(), ks); } } if let Some(n_sks) = n_sks.into() { assert_eq!(ks.by_subkey_fp.len(), n_sks, "Expected {} subkey fps, got {}\n{:?}", n_sks, ks.by_subkey_fp.len(), ks); assert_eq!(ks.by_subkey_id.len(), n_sks, "Expected {} subkey key ids, got {}\n{:?}", n_sks, ks.by_subkey_id.len(), ks); if ! ignore_grips { assert_eq!(ks.by_subkey_grip.len(), n_sks, "Expected {} subkey grips, got {}\n{:?}", n_sks, ks.by_subkey_grip.len(), ks); } } } // Makes sure the keystore's maps contain the specified keys // either as primary keys (the first bool) or as subkeys (the // second bool). fn ks_contains(ks: &KeystoreData, checks: &[(&Key, bool, bool)]) { for (key, as_primary, as_sub) in checks.iter() { let fpr = key.fingerprint(); match ks.by_primary_fp(&fpr) { Some(_) if *as_primary => (), Some(_) => panic!("{} found in by_primary_fp, expected nothing\n{:?}", fpr, ks), None if ! *as_primary => (), None => panic!("{} not found in by_primary_fp, but expected\n{:?}", fpr, ks), } if ks.by_subkey_fp(&fpr) .any(|c| c.keys().any(|ka| ka.key().fingerprint() == fpr)) { if ! *as_sub { panic!("{} found in by_subkey_fp, expected nothing\n{:?}", fpr, ks); } } else { if *as_sub { panic!("{} not found in by_subkey_fp, but expected\n{:?}", fpr, ks); } } // By keyid. let keyid = key.keyid(); if ks.by_primary_id(&keyid).any(|c| c.fingerprint() == fpr) { if ! *as_primary { panic!("{} found in by_primary_id, expected nothing\n{:?}", fpr, ks); } } else { if *as_primary { panic!("{} not found in by_primary_id, but expected\n{:?}", fpr, ks); } } if ks.by_subkey_id(&keyid) .any(|c| c.keys().any(|ka| ka.key().keyid() == keyid)) { if ! *as_sub { panic!("{} found in by_subkey_id, expected nothing\n{:?}", fpr, ks); } } else { if *as_sub { panic!("{} not found in by_subkey_id, but expected\n{:?}", fpr, ks); } } // By grip. if let Ok(grip) = Keygrip::of(key.mpis()) { if ks.by_primary_grip(&grip).any(|c| c.fingerprint() == fpr) { if ! *as_primary { panic!("Found {} in by_primary_grip, expected nothing\n{:?}", fpr, ks); } } else { if *as_primary { panic!("{} not found in by_primary_grip, but expected\n{:?}", fpr, ks); } } if ks.by_subkey_grip(&grip) .any(|c| c.keys().any(|ka| ka.key().keyid() == keyid)) { if ! *as_sub { panic!("Found {} in by_subkey_grip, expected nothing\n{:?}", fpr, ks); } } else { if *as_sub { panic!("{} not found in by_subkey_grip, but expected\n{:?}", fpr, ks); } } } } } // Asserts that a key does not appear in the key store, either in // the indices or in any of the certificates. fn ks_is_missing(ks: &KeystoreData, key: &Key) { // First check the maps. ks_contains(ks, &[ (key, false, false) ]); // Now iterate over everything and make sure it is really gone. for (table, cert) in ks.by_primary_fp.values() .map(|e| { ("by_primary_fp", e.cert()) }) .chain(ks.by_primary_id.values().flat_map(|v| { v.iter().map(|e| ("by_primary_id", e.cert())) })) .chain(ks.by_primary_grip.values().flat_map(|v| { v.iter().map(|e| ("by_primary_grip", e.cert())) })) .chain(ks.by_subkey_fp.values().flat_map(|v| { v.iter().map(|e| ("by_subkey_fp", e.cert())) })) .chain(ks.by_subkey_id.values().flat_map(|v| { v.iter().map(|e| ("by_subkey_id", e.cert())) })) .chain(ks.by_subkey_grip.values().flat_map(|v| { v.iter().map(|e| ("by_subkey_grip", e.cert())) })) { for k in cert.keys() { assert!(k.key().fingerprint() != key.fingerprint(), "{}: {} still exists in {}, {:?}\n{:?}", table, key.fingerprint(), k.key().fingerprint(), cert.with_policy(&StandardPolicy::new(), None) .map(|cert| { cert.primary_userid() .map(|ua| { String::from_utf8_lossy(ua.userid().value()) .into_owned() }) .unwrap_or(""[..].into()) }) .unwrap_or("".into()), ks); } } } // Have cert adopt the specified key. Uses template to create the // self signature. fn adopt(cert: Cert, key: Key, template: &Signature) -> openpgp::Result { let mut builder: SignatureBuilder = match template.typ() { SignatureType::SubkeyBinding => template.clone().into(), SignatureType::DirectKey | SignatureType::PositiveCertification | SignatureType::CasualCertification | SignatureType::PersonaCertification | SignatureType::GenericCertification => { // Convert to a binding // signature. let kf = template.key_flags() .context("Missing required \ subpacket, KeyFlags")?; SignatureBuilder::new( SignatureType::SubkeyBinding) .set_key_flags(kf)? }, _ => panic!("Unsupported binding \ signature: {:?}", template), }; // Get a signer. let pk = cert.primary_key().key(); let mut pk_signer = pk.clone().parts_into_secret()?.into_keypair()?; // Add the keys and signatures to cert. let mut packets: Vec = vec![]; // If there is a valid backsig, recreate it. let need_backsig = builder.key_flags() .map(|kf| kf.for_signing() || kf.for_certification()) .expect("Missing keyflags"); if need_backsig { // Derive a signer. let mut subkey_signer = key.clone().parts_into_secret()?.into_keypair()?; let backsig = builder.embedded_signatures() .filter(|backsig| { (*backsig).clone().verify_primary_key_binding( cert.primary_key().key(), &key).is_ok() }) .nth(0) .map(|sig| SignatureBuilder::from(sig.clone())) .unwrap_or_else(|| { SignatureBuilder::new(SignatureType::PrimaryKeyBinding) }) .sign_primary_key_binding(&mut subkey_signer, pk, &key)?; builder = builder.set_embedded_signature(backsig)?; } else { builder = builder.modify_hashed_area(|mut a| { a.remove_all(SubpacketTag::EmbeddedSignature); Ok(a) })?; } let sig = builder.sign_subkey_binding(&mut pk_signer, pk, &key)?; // Verify it. if let Err(err) = sig.verify_subkey_binding(pk_signer.public(), pk, &key) { panic!("invalid signature: {}", err); } packets.push(key.clone().into()); packets.push(sig.into()); let cert = cert.clone().insert_packets(packets.clone())?.0; Ok(cert) } #[test] fn insert_remove() -> openpgp::Result<()> { let ks = Keystore::default(); let mut ks = ks.write(); ks_count(&ks, 0, 0, false); // Generate a key and insert it. let (alice, revocation) = openpgp::cert::CertBuilder::general_purpose( Some("alice")).generate()?; ks.insert(alice.clone()); let n_subkeys = alice.keys().subkeys().count(); ks_count(&ks, 1, n_subkeys, false); ks_contains(&ks, &[ (&alice.primary_key().key().role_as_unspecified(), true, false) ]); for sk in alice.keys().subkeys() { // Try lookups. ks_contains(&ks, &[ (&sk.key().role_as_unspecified(), false, true) ]); } // Now, remove one subkey. let removed_sk: &Key = alice.keys().subkeys().nth(0).unwrap().key().role_as_unspecified(); let fp = removed_sk.fingerprint(); let r = ks.remove_all(&fp); assert!(r); ks_is_missing(&ks, &removed_sk); // Remove the subkey from our copy of Alice's cert, and redo // all the lookups. let alice = alice.retain_subkeys(|sk| sk.key().fingerprint() != fp); let n_subkeys = alice.keys().subkeys().count(); ks_count(&ks, 1, n_subkeys, false); ks_contains(&ks, &[ (&alice.primary_key().key().role_as_unspecified(), true, false) ]); for sk in alice.keys().subkeys() { // Try lookups. ks_contains(&ks, &[ (&sk.key().role_as_unspecified(), false, true) ]); } // Merge the revocation certificate. { let cell = ks.cert_cell_by_primary_fp(&alice.fingerprint()).unwrap(); let mut cert = cell.write().unwrap(); *cert = cert.clone().insert_packets(Some(revocation))?.0; } use openpgp::types::RevocationStatus; let p = &openpgp::policy::StandardPolicy::new(); let fp = alice.fingerprint(); let id = KeyID::from(&fp); let grip = Keygrip::of(alice.primary_key().key().mpis())?; assert!(matches!( ks.by_primary_fp(&fp).unwrap().revocation_status(p, None), RevocationStatus::Revoked(_))); assert!(matches!( ks.by_primary_id(&id).nth(0).unwrap().revocation_status(p, None), RevocationStatus::Revoked(_))); assert!(matches!( ks.by_primary_grip(&grip).nth(0).unwrap().revocation_status(p, None), RevocationStatus::Revoked(_))); ks_contains(&ks, &[ (&alice.primary_key().key().role_as_unspecified(), true, false) ]); for sk in alice.keys().subkeys() { let fp = sk.key().fingerprint(); let id = KeyID::from(&fp); let grip = Keygrip::of(sk.key().mpis())?; assert!(ks.by_primary_fp(&fp).is_none()); assert_eq!(ks.by_primary_id(&id).count(), 0); assert_eq!(ks.by_primary_grip(&grip).count(), 0); assert!(matches!( ks.by_subkey_fp(&fp).nth(0).unwrap().revocation_status(p, None), RevocationStatus::Revoked(_))); assert!(matches!( ks.by_subkey_id(&id).nth(0).unwrap().revocation_status(p, None), RevocationStatus::Revoked(_))); assert!(matches!( ks.by_subkey_grip(&grip).nth(0).unwrap().revocation_status(p, None), RevocationStatus::Revoked(_))); ks_contains(&ks, &[ (&sk.key().role_as_unspecified(), false, true) ]); } // Finally, remove the cert. let r = ks.remove_all(&alice.fingerprint()); assert!(r); ks_count(&ks, 0, 0, false); Ok(()) } // Given a certificate with multiple subkeys, add the subkeys one // at a time. #[test] fn add() -> openpgp::Result<()> { let ks = Keystore::default(); let mut ks = ks.write(); ks_count(&ks, 0, 0, false); // Generate a key and insert it. let (alice, _) = openpgp::cert::CertBuilder::new() .add_userid("alice") .add_signing_subkey() .add_signing_subkey() .add_signing_subkey() .add_signing_subkey() .add_signing_subkey() .add_signing_subkey() .add_signing_subkey() .generate()?; for _ in 0..2 { let mut added: Vec = Vec::new(); for i in 0..alice.keys().subkeys().count() { let subset = alice.clone().retain_subkeys(|sk| { if added.iter().any(|fpr| fpr == &sk.key().fingerprint()) { // Already present. Ignore it. false } else if added.len() < i { // Add it. added.push(sk.key().fingerprint()); true } else { // We have enough. false } }); // We should have just one subkey. if i > 0 { assert!(subset.keys().subkeys().count() == 1); } else { assert!(subset.keys().subkeys().count() == 0); } // Add it. ks.insert(subset.clone()); // Make sure it was merged correctly. ks_count(&ks, 1, i, false); ks_contains(&ks, &[(&alice.primary_key().key().role_as_unspecified(), true, false)]); for sk in alice.keys().subkeys() { if added.iter().any(|fpr| fpr == &sk.key().fingerprint()) { // Should have been added. ks_contains(&ks, &[(&sk.key().role_as_unspecified(), false, true)]); } else { // Hasn't been added yet. ks_contains(&ks, &[(&sk.key().role_as_unspecified(), false, false)]); } } } // Finally, remove the cert. let r = ks.remove_all(&alice.fingerprint()); assert!(r); ks_count(&ks, 0, 0, false); } Ok(()) } // Two certificates with the same subkey. #[test] fn same_subkey() -> openpgp::Result<()> { let p = &openpgp::policy::StandardPolicy::new(); let ks = Keystore::default(); let mut ks = ks.write(); ks_count(&ks, 0, 0, false); ks_contains(&ks, &[]); // Generate the keys. let (alice, _) = openpgp::cert::CertBuilder::general_purpose( Some("alice")).generate()?; let (bob, _) = openpgp::cert::CertBuilder::new() .add_userid("bob") .add_signing_subkey() .generate()?; let alice_pk = alice.primary_key().key().role_as_unspecified(); let alice_enc = alice.with_policy(p, None)? .keys().subkeys().for_transport_encryption().nth(0) .expect("encryption-capable subkey"); let alice_signing = alice.with_policy(p, None)? .keys().subkeys().for_signing().nth(0) .expect("signing-capable subkey") .amalgamation() .key().role_as_unspecified(); // Add Alice's encryption capable subkey to Bob's certificate. let key = alice_enc.key().clone(); let bob = adopt(bob, key.clone() .parts_into_secret() .expect("secret key material"), alice_enc.binding_signature())?; let common: &Key = alice_enc.key().role_as_unspecified(); let bob_pk = bob.primary_key().key().role_as_unspecified(); let bob_enc: &Key = bob.with_policy(p, None)? .keys().subkeys().for_transport_encryption().nth(0) .expect("encryption-capable subkey") .amalgamation() .key().role_as_unspecified(); assert_eq!(common.fingerprint(), bob_enc.fingerprint()); let bob_signing = bob.with_policy(p, None)? .keys().subkeys().for_signing().nth(0) .expect("encryption-capable subkey") .amalgamation() .key().role_as_unspecified(); ks.insert(alice.clone()); assert_eq!(ks.by_subkey_fp(&common.fingerprint()).count(), 1); ks.insert(bob.clone()); assert_eq!(ks.by_subkey_fp(&common.fingerprint()).count(), 2); // We have 3 subkeys, not 4. ks_count(&ks, 2, 3, false); // The common subkey appears twice. assert_eq!(ks.by_subkey_fp(&common.fingerprint()).count(), 2); ks_contains(&ks, &[ (&alice_pk, true, false), (&alice_signing, false, true), (&bob_pk, true, false), (&bob_signing, false, true), (&common, false, true), ]); // Remove Bob's signing subkey. let r = ks.remove_all(&bob_signing.fingerprint()); assert!(r); ks_count(&ks, 2, 2, false); assert_eq!(ks.by_subkey_fp(&common.fingerprint()).count(), 2); ks_contains(&ks, &[ (&alice_pk, true, false), (&alice_signing, false, true), (&bob_pk, true, false), (&bob_signing, false, false), (&common, false, true), ]); let r = ks.remove_all(&common.fingerprint()); assert!(r); ks_count(&ks, 2, 1, false); assert_eq!(ks.by_subkey_fp(&common.fingerprint()).count(), 0); ks_contains(&ks, &[ (&alice_pk, true, false), (&alice_signing, false, true), (&bob_pk, true, false), (&bob_signing, false, false), (&common, false, false), ]); assert!(! ks.by_primary_fp(&alice.fingerprint()).unwrap() .keys().any(|ka| ka.key().fingerprint() == common.fingerprint())); assert!(! ks.by_primary_fp(&bob.fingerprint()).unwrap() .keys().any(|ka| ka.key().fingerprint() == common.fingerprint())); // Finally, remove the cert. let r = ks.remove_all(&alice.fingerprint()); assert!(r); ks_is_missing(&ks, &common); let r = ks.remove_all(&bob.fingerprint()); assert!(r); ks_count(&ks, 0, 0, false); // Add alice and bob. Remove bob. Make sure everything is // still sane. ks.insert(alice.clone()); assert_eq!(ks.by_subkey_fp(&common.fingerprint()).count(), 1); ks.insert(bob.clone()); assert_eq!(ks.by_subkey_fp(&common.fingerprint()).count(), 2); // We have 3 subkeys, not 4. ks_count(&ks, 2, 3, false); // The common subkey appears twice. assert_eq!(ks.by_subkey_fp(&common.fingerprint()).count(), 2); ks_contains(&ks, &[ (&alice_pk, true, false), (&alice_signing, false, true), (&bob_pk, true, false), (&bob_signing, false, true), (&common, false, true), ]); eprintln!("{:?}", ks); // Remove Bob's key. let r = ks.remove_all(&bob.fingerprint()); assert!(r); ks_count(&ks, 1, 2, false); assert_eq!(ks.by_subkey_fp(&common.fingerprint()).count(), 1); ks_contains(&ks, &[ (&alice_pk, true, false), (&alice_signing, false, true), (&bob_pk, false, false), (&bob_signing, false, false), (&common, false, true), ]); Ok(()) } // The same MPIs appear on two certificates (but the keys are // different!). #[test] fn same_grip() -> openpgp::Result<()> { let p = &openpgp::policy::StandardPolicy::new(); let ks = Keystore::default(); let mut ks = ks.write(); ks_count(&ks, 0, 0, false); ks_contains(&ks, &[]); // Generate the keys. let t = std::time::SystemTime::now() - std::time::Duration::new(10, 0); let (alice, _) = openpgp::cert::CertBuilder::general_purpose( Some("alice")) .set_creation_time(t) .generate()?; let (bob, _) = openpgp::cert::CertBuilder::new() .add_userid("bob") .add_signing_subkey() .set_creation_time(t) .generate()?; let alice_pk = alice.primary_key().key().role_as_unspecified(); let alice_enc = alice.with_policy(p, None)? .keys().subkeys().for_transport_encryption().nth(0) .expect("encryption-capable subkey"); let alice_signing = alice.with_policy(p, None)? .keys().subkeys().for_signing().nth(0) .expect("signing-capable subkey") .amalgamation() .key().role_as_unspecified(); // Add the encryption subkey to Bob's key. Change the // creation time so that it has the same grip, but a different // fingerprint. let mut key = alice_enc.key().clone(); let ct = key.creation_time() + std::time::Duration::new(1, 0); key.set_creation_time(ct)?; let bob = adopt(bob, key.clone() .parts_into_secret() .expect("secret key material"), alice_enc.binding_signature())?; let alice_enc: &Key = alice_enc.amalgamation().key().role_as_unspecified(); let bob_pk = bob.primary_key().key().role_as_unspecified(); let bob_enc: &Key = bob.with_policy(p, None)? .keys().subkeys().for_transport_encryption().nth(0) .expect("encryption-capable subkey") .amalgamation() .key().role_as_unspecified(); let bob_signing = bob.with_policy(p, None)? .keys().subkeys().for_signing().nth(0) .expect("encryption-capable subkey") .amalgamation() .key().role_as_unspecified(); let common_grip = Keygrip::of(alice_enc.mpis()).unwrap(); assert_eq!(common_grip, Keygrip::of(bob_enc.mpis()).unwrap()); ks.insert(alice.clone()); assert_eq!(ks.by_subkey_grip(&common_grip).count(), 1); ks.insert(bob.clone()); assert_eq!(ks.by_subkey_grip(&common_grip).count(), 2); // We have 4 subkeys: the two subkeys with the same grip have // different fingerprints. ks_count(&ks, 2, 4, true); // The common grip appears twice. assert_eq!(ks.by_subkey_grip(&common_grip).count(), 2); ks_contains(&ks, &[ (&alice_pk, true, false), (&alice_signing, false, true), (&alice_enc, false, true), (&bob_pk, true, false), (&bob_signing, false, true), (&bob_enc, false, true), ]); // Remove Bob's encryption subkey, which is the one with the // shared grip. let r = ks.remove_all(&bob_enc.fingerprint()); assert!(r); ks_count(&ks, 2, 3, true); assert_eq!(ks.by_subkey_grip(&common_grip).count(), 1); ks_contains(&ks, &[ (&alice_pk, true, false), (&alice_signing, false, true), (&alice_enc, false, true), (&bob_pk, true, false), (&bob_signing, false, true), (&bob_enc, false, false), ]); let r = ks.remove_all(&alice_enc.fingerprint()); assert!(r); ks_count(&ks, 2, 2, true); assert_eq!(ks.by_subkey_grip(&common_grip).count(), 0); ks_contains(&ks, &[ (&alice_pk, true, false), (&alice_signing, false, true), (&alice_enc, false, false), (&bob_pk, true, false), (&bob_signing, false, true), (&bob_enc, false, false), ]); assert!(! ks.by_primary_fp(&alice.fingerprint()).unwrap() .keys().any(|ka| Keygrip::of(ka.key().mpis()).unwrap() == common_grip)); assert!(! ks.by_primary_fp(&bob.fingerprint()).unwrap() .keys().any(|ka| Keygrip::of(ka.key().mpis()).unwrap() == common_grip)); // Finally, remove the cert. let r = ks.remove_all(&alice.fingerprint()); assert!(r); let r = ks.remove_all(&bob.fingerprint()); assert!(r); ks_count(&ks, 0, 0, false); // Add alice and bob. Remove bob. Make sure everything is // still sane. ks.insert(alice.clone()); assert_eq!(ks.by_subkey_grip(&common_grip).count(), 1); ks.insert(bob.clone()); assert_eq!(ks.by_subkey_grip(&common_grip).count(), 2); // We have 4 subkeys. ks_count(&ks, 2, 4, true); // The common grip appears twice. assert_eq!(ks.by_subkey_grip(&common_grip).count(), 2); ks_contains(&ks, &[ (&alice_pk, true, false), (&alice_signing, false, true), (&alice_enc, false, true), (&bob_pk, true, false), (&bob_signing, false, true), (&bob_enc, false, true), ]); eprintln!("{:?}", ks); // Remove Bob's key. let r = ks.remove_all(&bob.fingerprint()); assert!(r); ks_count(&ks, 1, 2, false); assert_eq!(ks.by_subkey_grip(&common_grip).count(), 1); ks_contains(&ks, &[ (&alice_pk, true, false), (&alice_signing, false, true), (&alice_enc, false, true), (&bob_pk, false, false), (&bob_signing, false, false), (&bob_enc, false, false), ]); Ok(()) } // The same MPIs appear on same certificate as two different // subkeys. #[test] fn same_grip_same_cert() -> openpgp::Result<()> { let p = &openpgp::policy::StandardPolicy::new(); let ks = Keystore::default(); let mut ks = ks.write(); ks_count(&ks, 0, 0, false); ks_contains(&ks, &[]); // Generate the keys. let t = std::time::SystemTime::now() - std::time::Duration::new(10, 0); let (alice, _) = openpgp::cert::CertBuilder::new() .add_userid("alice") .add_signing_subkey() .set_creation_time(t) .generate()?; let alice_pk = alice.primary_key().key().role_as_unspecified(); let alice_signing = alice.with_policy(p, None)? .keys().subkeys().for_signing().nth(0) .expect("signing-capable subkey"); ks.insert(alice.clone()); // We have 1 key with 1 subkey. ks_count(&ks, 1, 1, false); ks_contains(&ks, &[ (&alice_pk, true, false), (&alice_signing.clone().amalgamation().key().role_as_unspecified(), false, true), ]); // Create a second subkey with the same grip. let mut key = alice_signing.key().clone(); let ct = key.creation_time() + std::time::Duration::new(1, 0); key.set_creation_time(ct)?; let template = alice_signing.binding_signature().clone(); let alice = adopt(alice, key.clone() .parts_into_secret() .expect("secret key material"), &template)?; let alice_pk = alice.primary_key().key().role_as_unspecified(); let alice_signing: &Key = alice.keys().subkeys().nth(0).unwrap().key().role_as_unspecified(); let alice_signing2: &Key = alice.keys().subkeys().nth(1).unwrap().key().role_as_unspecified(); let common_grip = Keygrip::of(alice_signing.mpis()).unwrap(); assert_eq!(common_grip, Keygrip::of(alice_signing2.mpis()).unwrap()); ks.insert(alice.clone()); // We have 2 subkeys with the same grip. ks_count(&ks, 1, 2, true); // The common grip appears twice. assert_eq!(ks.by_subkey_grip(&common_grip).count(), 2, "{:?}", ks); ks_contains(&ks, &[ (&alice_pk, true, false), (&alice_signing, false, true), (&alice_signing2, false, true), ]); // Remove one of the signing keys with the shared grip. let r = ks.remove_all(&alice_signing2.fingerprint()); assert!(r); ks_count(&ks, 1, 1, false); assert_eq!(ks.by_subkey_grip(&common_grip).count(), 1); ks_contains(&ks, &[ (&alice_pk, true, false), (&alice_signing, false, true), (&alice_signing2, false, false), ]); let r = ks.remove_all(&alice_signing.fingerprint()); assert!(r); ks_count(&ks, 1, 0, false); assert_eq!(ks.by_subkey_grip(&common_grip).count(), 0); ks_contains(&ks, &[ (&alice_pk, true, false), (&alice_signing, false, false), (&alice_signing2, false, false), ]); // Finally, remove the cert. let r = ks.remove_all(&alice.fingerprint()); assert!(r); ks_count(&ks, 0, 0, false); // Add alice and bob. Remove bob. Make sure everything is // still sane. ks.insert(alice.clone()); assert_eq!(ks.by_subkey_grip(&common_grip).count(), 2); // The common grip appears twice. ks_count(&ks, 1, 2, true); assert_eq!(ks.by_subkey_grip(&common_grip).count(), 2); ks_contains(&ks, &[ (&alice_pk, true, false), (&alice_signing, false, true), (&alice_signing2, false, true), ]); eprintln!("{:?}", ks); Ok(()) } // Inserting the public key must not remove secrets. #[test] fn key_cert_secrets_preserved() -> openpgp::Result<()> { let (key, _) = openpgp::cert::CertBuilder::general_purpose(Some("Hanna")).generate()?; let fp = key.fingerprint(); let a = key.clone(); let b = key.strip_secret_key_material(); // Test both ways. for (first, second) in vec![(&a, &b), (&b, &a)] { eprintln!("First has {}secret, second has {}secret.", if first.is_tsk() { "" } else { "no " }, if second.is_tsk() { "" } else { "no " }); let ks = Keystore::default(); let mut ks = ks.write(); ks.insert(first.clone()); ks.insert(second.clone()); let k = ks.by_primary_fp(&fp).unwrap(); assert!(k.is_tsk()); assert!(k.primary_key().key().has_unencrypted_secret()); assert_eq!(k.keys().unencrypted_secret().count(), k.keys().count()); } Ok(()) } } sequoia-octopus-librnp-1.11.1/src/lib.rs000064400000000000000000000670471046102023000162520ustar 00000000000000#![doc = include_str!("../README.md")] use std::{ collections::{ HashMap, }, sync::atomic, }; use std::sync::{Arc, RwLock, RwLockReadGuard}; use libc::{ c_char, c_int, c_void, size_t, }; use sequoia_openpgp as openpgp; use openpgp::{ Fingerprint, KeyHandle, KeyID, cert::{ Cert, }, crypto::{ Password, mem::Protected, }, packet::{ Key, key::{SecretParts, UnspecifiedParts, UnspecifiedRole}, UserID, }, policy::{ HashAlgoSecurity, NullPolicy, StandardPolicy, }, serialize::Serialize, types::HashAlgorithm, }; /// Controls tracing. const TRACE: bool = cfg!(debug_assertions) && option_env!("SEQUOIA_OCTOPUS_DISABLE_TRACING").is_none(); /// Enable Thunderbird-specific quirks /// /// If true, then the octopus will deviate from RNP's behavior in /// order to work around a bug or assumption in Thunderbird. This /// also clearly marks all occurrences of workarounds in this crate. const THUNDERBIRD_WORKAROUND: bool = true; #[allow(unused_macros)] macro_rules! stub { ($s: ident) => { #[no_mangle] pub extern "C" fn $s() -> crate::RnpResult { log!("\nSTUB: {}\n", stringify!($s)); crate::RNP_ERROR_NOT_IMPLEMENTED } }; } #[allow(dead_code)] #[macro_use] pub mod error; use error::*; #[allow(dead_code)] pub mod stubs; use sequoia_ipc::Keygrip; pub mod keystore; use keystore::Keystore; pub mod buffer; use buffer::*; #[allow(dead_code)] pub mod flags; use flags::*; #[allow(dead_code)] pub mod io; use io::*; #[allow(dead_code)] pub mod utils; use utils::*; #[allow(dead_code)] pub mod conversions; use conversions::*; pub mod version; #[allow(dead_code)] pub mod op_verify; #[allow(dead_code)] pub mod op_encrypt; #[allow(dead_code)] pub mod op_sign; pub mod recombine; #[allow(dead_code)] pub mod op_generate; #[allow(dead_code)] pub mod signature; use signature::RnpSignature; #[allow(dead_code)] pub mod key; use key::RnpKey; #[allow(dead_code)] pub mod iter; #[allow(dead_code)] pub mod userid; use userid::RnpUserID; #[allow(dead_code)] pub mod import; pub mod security_rules; pub mod dump_packets; pub mod armor; // The gpg module is copied from OpenPGP CA. We don't want to modify // it. #[allow(dead_code)] pub mod gpg; pub mod tbprofile; pub mod wot; pub mod parcimonie; pub const NP: &NullPolicy = unsafe { &NullPolicy::new() }; #[allow(dead_code)] fn cert_dump(cert: &Cert) { use openpgp::packet::key::SecretKeyMaterial; eprintln!("Cert: {}, {}", cert.fingerprint(), cert.with_policy(&StandardPolicy::new(), None) .map(|cert| { cert.primary_userid() .map(|ua| { String::from_utf8_lossy(ua.userid().value()) .into_owned() }) .unwrap_or(""[..].into()) }) .unwrap_or("".into())); for (i, k) in cert.keys().enumerate() { eprint!(" {}. {}", i, k.key().fingerprint()); match k.key().optional_secret() { Some(SecretKeyMaterial::Unencrypted(_)) => { eprint!(" has unencrypted secret key material"); } Some(SecretKeyMaterial::Encrypted(_)) => { eprint!(" has encrypted secret key material"); } None => { eprint!(" has NO secret key material"); } } eprintln!(""); } } #[derive(Default)] pub struct RnpContext { policy: Arc>>, certs: Keystore, unlocked_keys: HashMap>, password_cb: Option<(RnpPasswordCb, *mut c_void)>, plaintext_cache: recombine::PlaintextCache, } type RnpPasswordCb = unsafe extern "C" fn(*mut RnpContext, *mut c_void, *const RnpKey, *const c_char, *mut c_char, size_t) -> bool; #[no_mangle] pub unsafe extern "C" fn rnp_ffi_create(ctx: *mut *mut RnpContext, pub_fmt: *const c_char, sec_fmt: *const c_char) -> RnpResult { rnp_function!(rnp_ffi_create, crate::TRACE); assert_ptr!(ctx); let pub_fmt = assert_str!(pub_fmt); let sec_fmt = assert_str!(sec_fmt); if pub_fmt != "GPG" || sec_fmt != "GPG" { rnp_return_status!(RNP_ERROR_BAD_FORMAT); } // Try to make sure that a pubring.gpg exists. Thunderbird will // read in the file, and if that succeeds, invoke rnp_load_keys // with it. It is important to us that this call happens, because // this will set up our GnuPG synchronization. // // This is a best-effort mechanism. if let Some(profile) = tbprofile::TBProfile::path() { let maybe_create_keyring = |path: std::path::PathBuf| { // Create an empty keyring if the file does not exist. if let Ok(mut sink) = std::fs::OpenOptions::new().write(true).create_new(true) .open(&path) { // The empty keyring must not be zero-sized, because // Thunderbird equates that with no file or any other io // error reading the file. So, let's write a marker // packet. // // A marker packet is safe, it will be ignored by both RNP // and Sequoia. Thunderbird with both RNP and the Octopus // will happily import certs into such a keyring. match openpgp::Packet::Marker(Default::default()) .serialize(&mut sink) { Ok(_) => t!("Created new empty keyring in {}", path.display()), Err(e) => t!("Creating new empty keyring in {} failed: {}", path.display(), e), } } else if let Ok(mut sink) = std::fs::OpenOptions::new().write(true).create(false) .open(&path) { // See if the existing file is empty. if let Ok(0) = sink.metadata().map(|m| m.len()) { // It is. This will prevent rnp_load_keys from // being invoked, see above. Modify it in place. // No one else is updating these files in place, // so we don't risk clobbering the keyring here. match openpgp::Packet::Marker(Default::default()) .serialize(&mut sink) { Ok(_) => t!("Wrote marker to empty keyring in {}", path.display()), Err(e) => t!("Writing marker to empty keyring in {} \ failed: {}", path.display(), e), } } } }; maybe_create_keyring(profile.join("pubring.gpg")); maybe_create_keyring(profile.join("secring.gpg")); } let mut policy = sequoia_policy_config::ConfiguredStandardPolicy::new(); if let Err(e) = policy.parse_default_config() { global_warn!("Reading crypto policy: {}", e); } let mut policy = policy.build(); // Thunderbird checks that MD5 and SHA-1 for self-signatures are // disabled and refuses to fully initialize RNP otherwise. Meet // its expectations. let now = std::time::SystemTime::now(); for (algo, prop) in [ (HashAlgorithm::MD5, HashAlgoSecurity::CollisionResistance), (HashAlgorithm::MD5, HashAlgoSecurity::SecondPreImageResistance), (HashAlgorithm::SHA1, HashAlgoSecurity::CollisionResistance), ] { let cutoff = policy.hash_cutoff(algo, prop); t!("{} for {:?}: {:?}", algo, prop, cutoff); if cutoff.unwrap_or(now) >= now { warn!("Your crypto policy enables {} in contexts where {:?} is \ needed ({:?}). Unconditionally rejecting it.", algo, prop, cutoff); policy.reject_hash_property_at( algo, prop, std::time::UNIX_EPOCH); } } *ctx = Box::into_raw(Box::new(RnpContext { policy: Arc::new(RwLock::new(policy)), ..Default::default() })); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_ffi_destroy(ctx: *mut RnpContext) -> RnpResult { rnp_function!(rnp_ffi_destroy, crate::TRACE); arg!(ctx); if ! ctx.is_null() { drop(Box::from_raw(ctx)); } rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_ffi_set_log_fd(ctx: *mut RnpContext, _fd: c_int) -> RnpResult { rnp_function!(rnp_ffi_set_log_fd, crate::TRACE); let _ = assert_ptr_mut!(ctx); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_ffi_set_pass_provider(ctx: *mut RnpContext, cb: RnpPasswordCb, cookie: *mut c_void) -> RnpResult { rnp_function!(rnp_ffi_set_pass_provider, crate::TRACE); let ctx = assert_ptr_mut!(ctx); arg!(cb); arg!(cookie); ctx.password_cb = Some((cb, cookie)); rnp_success!() } impl RnpContext { pub fn policy(&self) -> RwLockReadGuard> { self.policy.read().unwrap() } /// Inserts a cert into the keystore. /// /// This strips any secret key material. /// /// # Locking /// /// This acquires a write lock on the keystore and, if the /// certificate is already present, a write lock on the /// certificate's cell. pub fn insert_cert(&mut self, cert: Cert) { self.certs.write().insert(cert.strip_secret_key_material()); } /// Inserts a cert from an external source into the keystore. /// /// This strips any secret key material. /// /// certs from external sources won't be serialized. /// /// # Locking /// /// This acquires a write lock on the keystore and, if the /// certificate is already present, a write lock on the /// certificate's cell. pub fn insert_cert_external(&mut self, cert: Cert) { self.certs.write().insert_external(cert.strip_secret_key_material()); } /// Inserts a key into the keystore. /// /// Secret key material is preserved. /// /// # Locking /// /// This acquires a write lock on the keystore and, if the /// certificate is already present, a write lock on the /// certificate's cell. pub fn insert_key(&mut self, cert: Cert) { self.certs.write().insert(cert); } /// Retrieves a certificate from the keystore by userid. /// /// RNP searches both the certring and the keyring, and the /// keyhandle can thus refer to two certificates, potentially /// different versions of the same, or even different /// certificates! Since we merge the key keyrings, this is not a /// problem for us. /// /// # Locking /// /// This acquires a read lock on the keystore and one or more /// certificates' cells. See the corresponding search methods for /// details. pub fn cert(&self, by: &RnpIdentifier) -> Option { rnp_function!(RnpContext::cert, TRACE); use RnpIdentifier::*; let cert = match by { UserID(id) => self.cert_by_userid(id), KeyID(id) => self.cert_by_subkey_id(id), Fingerprint(fp) => self.cert_by_subkey_fp(fp), Keygrip(grip) => self.cert_by_subkey_grip(grip), }; t!("Lookup by {:?} returned cert {:?}", by, cert.as_ref().map(|c| c.fingerprint())); cert } /// Retrieves a certificate by userid. /// /// XXX: This is super dodgy. rnp.h says "Note: only valid /// userids are checked while searching by userid." but it is not /// clear what that means. /// /// XXX: I think it would be better to fail these lookups. Are /// they used by TB? /// /// # Locking /// /// This acquires a read lock on the keystore. Currently, this /// function performs a linear scan of all keys. As such, it /// potentially acquires (in turn) a read lock on all of the /// certificates' cells. pub fn cert_by_userid(&self, uid: &UserID) -> Option { let mut r_cert = None; // XXX O(n) for cert in self.certs.read().iter() { if cert.userids().any(|u| u.userid() == uid) { r_cert = Some(cert.clone()); break; } } r_cert } /// Retrieves a certificate from the keystore by (sub)key /// handle. /// /// # Locking /// /// This acquires a read lock on the keystore and, if a matching /// certificate is present, a read lock on the certificate's cell. pub fn cert_by_subkey_handle(&self, handle: &KeyHandle) -> Option { match handle { KeyHandle::Fingerprint(fp) => self.cert_by_subkey_fp(fp), KeyHandle::KeyID(id) => self.cert_by_subkey_id(id), } } /// Retrieves a certificate from the keystore by (sub)key /// fingerprint. /// /// # Locking /// /// This acquires a read lock on the keystore and, if a matching /// certificate is present, a read lock on the certificate's cell. pub fn cert_by_subkey_fp(&self, fp: &Fingerprint) -> Option { self.certs.read().by_fp(fp).nth(0).map(|c| c.clone()) } /// Retrieves a certificate from the keystore by (sub)key /// keyid. /// /// # Locking /// /// This acquires a read lock on the keystore and, if a matching /// certificate is present, a read lock on the certificate's cell. pub fn cert_by_subkey_id(&self, id: &KeyID) -> Option { let ks = self.certs.read(); let r = ks.by_primary_id(id).nth(0) .or_else(|| ks.by_subkey_id(id).nth(0)) .map(|c| c.clone()); r } /// Retrieves a certificate from the keystore by (sub)key /// keygrip. /// /// # Locking /// /// This acquires a read lock on the keystore and, if a matching /// certificate is present, a read lock on the certificate's cell. pub fn cert_by_subkey_grip(&self, grip: &Keygrip) -> Option { let ks = self.certs.read(); let r = ks.by_primary_grip(grip).nth(0) .or_else(|| ks.by_subkey_grip(grip).nth(0)) .map(|c| c.clone()); r } } #[derive(Debug)] pub enum RnpPasswordFor { AddSubkey, AddUserID, Sign, Decrypt, Unlock, Protect, Unprotect, DecryptSymmetric, EncryptSymmetric, } impl RnpPasswordFor { fn pgp_context(&self) -> *const c_char { use RnpPasswordFor::*; (match self { AddSubkey => b"add subkey\x00".as_ptr(), AddUserID => b"add userid\x00".as_ptr(), Sign => b"sign\x00".as_ptr(), Decrypt => b"decrypt\x00".as_ptr(), Unlock => b"unlock\x00".as_ptr(), Protect => b"protect\x00".as_ptr(), Unprotect => b"unprotect\x00".as_ptr(), DecryptSymmetric => b"decrypt (symmetric)\x00".as_ptr(), EncryptSymmetric => b"encrypt (symmetric)\x00".as_ptr(), }) as *const c_char } } impl RnpContext { pub fn request_password(&mut self, key: Option<&RnpKey>, reason: RnpPasswordFor) -> Option { rnp_function!(RnpContext::request_password, TRACE); t!("key = {:?}, reason = {:?}", key.map(|k| k.fingerprint()), reason); if let Some((f, cookie)) = self.password_cb { let mut buf: Protected = vec![0; 128].into(); let len = buf.len(); let ok = unsafe { f(self, cookie, key.map(|k| k as *const _).unwrap_or(std::ptr::null()), reason.pgp_context(), buf.as_mut().as_mut_ptr() as *mut c_char, len) }; if ! ok { t!("password_cb returned failure"); return None; } if let Some(got) = buf.iter().position(|b| *b == 0) { t!("password_cb returned a password"); Some(Password::from(&buf[..got])) } else { eprintln!("sequoia-octopus: given password exceeded buffer"); None } } else { t!("No password_cb set"); None } } /// Decrypts the given key, if necessary. pub fn decrypt_key_for(&mut self, cert: Option<&Cert>, mut key: Key, reason: RnpPasswordFor) -> openpgp::Result> { rnp_function!(RnpContext::decrypt_key_for, TRACE); t!("cert = {:?}, key = {}, reason = {:?}", cert.map(|c| c.fingerprint()), key.fingerprint(), reason); if ! key.has_unencrypted_secret() { if let Some(k) = self.unlocked_keys.get(&key.fingerprint()) { // Use the unlocked key instead of prompting for a // password. t!("Found unlocked key in cache"); return Ok(k.clone()); } let rnp_key = if let Some(cert) = cert { RnpKey::new(self, key.into(), cert) } else { RnpKey::without_cert(self, key.into()) }; if let Some(pw) = self.request_password(Some(&rnp_key), reason) { key = Key::::from(rnp_key) .parts_into_secret()?; let k = key.clone(); key.secret_mut().decrypt_in_place(&k, &pw) .map_err(|_| Error::BadPassword)?; t!("Key decrypted successfully") } else { return Err(anyhow::anyhow!("no password given")); } } else { t!("Key is not encrypted, nothing to do"); } Ok(key) } /// Returns false iff the key has not been unlocked. pub fn key_is_locked(&mut self, key: &Key) -> bool { ! self.unlocked_keys.contains_key(&key.fingerprint()) } /// Locks the key. pub fn key_lock(&mut self, key: &Key) { self.unlocked_keys.remove(&key.fingerprint()); } /// Unlocks the key. /// /// If `password` is None, this function will ask for a password /// using the callback. pub fn key_unlock(&mut self, mut key: Key, password: Option) -> openpgp::Result<()> { rnp_function!(RnpContext::key_unlock, crate::TRACE); t!("key: {}; password: {}", key.fingerprint(), if password.is_some() { "Some(_)" } else { "None" }); if ! key.has_unencrypted_secret() { if let Some(pw) = password .or_else(|| self.request_password( None, RnpPasswordFor::Unlock)) { let k = key.clone(); key.secret_mut().decrypt_in_place(&k, &pw) .map_err(|_| Error::BadPassword)?; } else { return Err(anyhow::anyhow!("no password given")); } } assert!(key.has_unencrypted_secret()); self.unlocked_keys.insert(key.fingerprint(), key); Ok(()) } /// Returns a reference to the unlocked key in the cache, if it /// exists. pub fn key_unlocked_ref(&self, key: &Key) -> Option<&Key> { self.unlocked_keys.get(&key.fingerprint()) } } #[no_mangle] pub unsafe extern "C" fn rnp_load_keys(ctx: *mut RnpContext, format: *const c_char, input: *mut RnpInput, flags: RnpLoadSaveFlags) -> RnpResult { rnp_function!(rnp_load_keys, TRACE); let ctx = assert_ptr_mut!(ctx); let format = assert_str!(format); let input = assert_ptr_mut!(input); arg!(flags); static BANNER_SHOWN: atomic::AtomicBool = atomic::AtomicBool::new(false); if ! BANNER_SHOWN.load(atomic::Ordering::Relaxed) { warn!("Your Thunderbird is using Sequoia's Octopus, version {}\n\ (sequoia-openpgp: {}). For details, and to report issues please\n\ see https://gitlab.com/sequoia-pgp/sequoia-octopus-librnp .", env!("CARGO_PKG_VERSION"), sequoia_openpgp::VERSION); if let Some(path) = crate::tbprofile::TBProfile::path() { warn!("Your Thunderbird profile appears to be: {:?}", path); } else { warn!("Failed to detect your Thunderbird profile. Please report\n\ open an issue at https://gitlab.com/sequoia-pgp/sequoia-octopus-librnp ."); } BANNER_SHOWN.store(true, atomic::Ordering::Relaxed); } if format != "GPG" { rnp_return_status!(RNP_ERROR_BAD_FORMAT); } let input_size = input.size(); match flags { RNP_LOAD_SAVE_PUBLIC_KEYS => { if let Ok(input_size) = input_size { if let Some(profile) = tbprofile::TBProfile::path() { let pubring = profile.join("pubring.gpg"); if let Ok(pubring) = std::fs::metadata(pubring) { let file_size = pubring.len(); t!("input is {} bytes, pubring.gpg is {} bytes.", input_size, file_size); if input_size == file_size { t!("Looks like a match. Periodically flushing \ the keystore to disk."); rnp_try_or!((*ctx).certs.set_directory(profile), RNP_ERROR_GENERIC); } else { t!("pubring.gpg does not match input. \ Conservatively disabling flushing the \ keystore to disk."); } } } } // Also load GPG's public key database. if let Err(err) = (*ctx).certs.load_gpg_keyring_in_background( (*ctx).policy.clone()) { warn!("Import gpg's keyring: {}", err); } let policy = ctx.policy().clone(); rnp_try_or!(ctx.certs.start_parcimonie(policy), RNP_ERROR_GENERIC); } RNP_LOAD_SAVE_SECRET_KEYS => (), f => { warn!("sequoia-octopus: unexpected flags to rnp_load_keys: {:x}", f); rnp_return_status!(RNP_ERROR_BAD_PARAMETERS); }, } use std::io::Read; let mut data = Vec::new(); if let Err(err) = input.read_to_end(&mut data) { warn!("sequoia-octopus: Error reading input: {}", err); rnp_return_status!(RNP_ERROR_GENERIC); } let policy = (*ctx).policy.clone(); if let Err(err) = (*ctx).certs.load_keyring_in_background( data, flags == RNP_LOAD_SAVE_SECRET_KEYS, policy) { warn!("sequoia-octopus: Error reading certs: {}", err); rnp_return_status!(RNP_ERROR_GENERIC); } rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_save_keys(ctx: *mut RnpContext, format: *const c_char, output: *mut RnpOutput, flags: RnpLoadSaveFlags) -> RnpResult { rnp_function!(rnp_save_keys, TRACE); let ctx = assert_ptr_mut!(ctx); let format = assert_str!(format); let output = assert_ptr_mut!(output); if format != "GPG" { rnp_return_status!(RNP_ERROR_BAD_FORMAT); } let mut r = Ok(()); let mut count = 0; match flags { RNP_LOAD_SAVE_PUBLIC_KEYS => { let _ = (*ctx).certs.block_on_load(); for cert in (*ctx).certs.read().to_save().filter(|cert| ! cert.is_tsk()) { if let Err(err) = cert.serialize(output) { r = Err(err); break; } else { count += 1; } } }, RNP_LOAD_SAVE_SECRET_KEYS => { let _ = (*ctx).certs.block_on_load(); for cert in (*ctx).certs.read().to_save().filter(|cert| cert.is_tsk()) { if let Err(err) = cert.as_tsk().serialize(output) { r = Err(err); break; } else { count += 1; } } } f => { warn!("unexpected flags to rnp_save_keys: {:x}", f); rnp_return_status!(RNP_ERROR_BAD_PARAMETERS); }, }; if count == 0 { // We didn't write any bytes. Currently, Thunderbird will not // invoke rnp_load_keys if a keyring is a zero-sized file. To // avoid that, i.e. make sure that rnp_load_keys is invoked, // we write a placeholder there. See the comments in // rnp_ffi_create for details. if let Err(err) = openpgp::Packet::Marker(Default::default()) .serialize(output) { if r.is_ok() { r = Err(err); } } } rnp_return_status!(if let Err(err) = r { warn!("failed saving keys: {}", err); RNP_ERROR_GENERIC } else { RNP_SUCCESS }) } #[no_mangle] pub unsafe extern "C" fn rnp_get_public_key_count(ctx: *mut RnpContext, count: *mut size_t) -> RnpResult { rnp_function!(rnp_get_public_key_count, crate::TRACE); let ctx = assert_ptr_mut!(ctx); let count = assert_ptr_mut!(count); // We load the keyrings in the background. But, once TB tries to // get the number of certs, we have to wait on that process. let _ = ctx.certs.block_on_load(); // Make sure the agent listing is up to date. let mut ks = ctx.certs.write(); ks.key_on_agent_hard(&Fingerprint::V4(Default::default())); drop(ks); let ks = ctx.certs.read(); *count = ks.iter().filter(|cert| { if cert.is_tsk() { false } else if ks.key_on_agent(&cert.fingerprint()).0 { false } else { true } }).count(); t!("-> {}", *count); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_get_secret_key_count(ctx: *mut RnpContext, count: *mut size_t) -> RnpResult { rnp_function!(rnp_get_secret_key_count, TRACE); let ctx = assert_ptr_mut!(ctx); let count = assert_ptr_mut!(count); // We load the keyrings in the background. But, once TB tries to // get the number of certs, we have to wait on that process. let _ = ctx.certs.block_on_load(); // Make sure the agent listing is up to date. let mut ks = ctx.certs.write(); ks.key_on_agent_hard(&Fingerprint::V4(Default::default())); drop(ks); let ks = ctx.certs.read(); *count = ks.iter().filter(|cert| { if cert.is_tsk() { true } else { let fpr = &cert.fingerprint(); ks.key_on_agent(fpr).0 } }).count(); t!("-> {}", *count); rnp_success!() } sequoia-octopus-librnp-1.11.1/src/op_encrypt.rs000064400000000000000000000417511046102023000176600ustar 00000000000000use libc::{ c_char, c_int, c_void, }; use sequoia_openpgp as openpgp; use openpgp::{ Cert, cert::{ Preferences, amalgamation::ValidAmalgamation, }, packet::{ Key, key::{ PublicParts, UnspecifiedParts, SecretParts, UnspecifiedRole, }, }, serialize::stream::*, types::{ AEADAlgorithm, Features, HashAlgorithm, SymmetricAlgorithm, }, }; use crate::{ RnpContext, RnpResult, RnpInput, RnpOutput, RnpPasswordFor, conversions::FromRnpId, gpg, key::RnpKey, error::*, flags::*, }; pub struct RnpOpEncrypt<'a> { ctx: &'a mut RnpContext, input: &'a mut RnpInput, output: &'a mut RnpOutput<'a>, recipients: Vec<(Features, Key)>, signers: Vec>, agent_signers: Vec<(Option, Key)>, cipher: Option, hash: Option, armor: bool, /// Disables wrapping data in a Literal Data Packet. no_wrap: bool, } impl RnpOpEncrypt<'_> { /// Returns whether we have registered signers. fn have_signers(&self) -> bool { ! self.signers.is_empty() && ! self.agent_signers.is_empty() } } #[no_mangle] pub unsafe extern "C" fn rnp_op_encrypt_create<'a>(op: *mut *mut RnpOpEncrypt<'a>, ctx: *mut RnpContext, input: *mut RnpInput, output: *mut RnpOutput<'a>) -> RnpResult { rnp_function!(rnp_op_encrypt_create, crate::TRACE); let op = assert_ptr_mut!(op); let ctx = assert_ptr_mut!(ctx); let input = assert_ptr_mut!(input); let output = assert_ptr_mut!(output); *op = Box::into_raw(Box::new(RnpOpEncrypt { ctx, input, output, recipients: Vec::new(), signers: Vec::new(), agent_signers: Vec::new(), cipher: None, hash: None, armor: false, no_wrap: false, })); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_op_encrypt_destroy(op: *mut RnpOpEncrypt) -> RnpResult { rnp_function!(rnp_op_encrypt_destroy, crate::TRACE); arg!(op); if ! op.is_null() { drop(Box::from_raw(op)); } rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_op_encrypt_execute(op: *mut RnpOpEncrypt) -> RnpResult { rnp_function!(rnp_op_encrypt_execute, crate::TRACE); let op = assert_ptr_mut!(op); fn f(op: &mut RnpOpEncrypt) -> openpgp::Result<()> { // Currently, Thunderbird uses the RFC 1847 Encapsulation // method. We detect that, and transparently replace it with // the combined method. let cached_plaintext = if op.have_signers() { // This is already a combined operation, therefore we // create signatures over existing signatures. // Undoing the encapsulation would change semantics. None } else if let Ok(Some((plaintext, issuers))) = op.ctx.plaintext_cache.get(&op.input) { t!("Plaintext cache hit, attempting recombination"); let mut issuer_keys = Vec::new(); for issuer in &issuers { if let Some(key) = op.ctx.cert(&issuer.clone().into()) .and_then(|cert| { cert.keys().key_handle(issuer.clone()).nth(0) .and_then(|ka| { ka.key().clone().parts_into_secret().ok() }) }) { issuer_keys.push(key); } } // Did we find all keys that previously did the signing? if issuer_keys.len() == issuers.len() { t!("Recombination successful"); op.signers = issuer_keys; Some(plaintext) } else { None } } else { None }; let mut message = Message::new(&mut op.output); if op.armor { message = Armorer::new(message).build()?; } // Encrypt the message. let mut message = Encryptor::for_recipients( message, op.recipients.iter() .map(|(f, k)| Recipient::new(f.clone(), k.key_handle(), k))) .symmetric_algo(op.cipher.unwrap_or_default()) .build()?; // XXX: Pad the message if we implemented SEIPDv2 and the // padding packet. // Maybe sign the message. if let Some(key) = op.signers.pop() { let s = op.ctx.decrypt_key_for(None, key, RnpPasswordFor::Sign)? .into_keypair()?; let mut signer = Signer::new(message, s)? .hash_algo(op.hash.unwrap_or_default())?; for key in op.signers.drain(..) { let s = op.ctx.decrypt_key_for(None, key, RnpPasswordFor::Sign)? .into_keypair()?; signer = signer.add_signer(s)?; } for (cert, key) in &op.agent_signers { let s = gpg::agent_keypair(&*op.ctx.policy(), &cert, &key)?; signer = signer.add_signer(s)?; } for (_, r) in &op.recipients { if let Some(key) = op.ctx.cert_by_subkey_fp(&r.fingerprint()) { signer = signer.add_intended_recipient(&key); } } message = signer.build()?; } else if let Some((cert, key)) = op.agent_signers.get(0) { let s = gpg::agent_keypair(&*op.ctx.policy(), &cert, &key)?; let mut signer = Signer::new(message, s)? .hash_algo(op.hash.unwrap_or_default())?; for (cert, key) in &op.agent_signers[1..] { let s = gpg::agent_keypair(&*op.ctx.policy(), &cert, &key)?; signer = signer.add_signer(s)?; } for (_, r) in &op.recipients { if let Some(key) = op.ctx.cert_by_subkey_fp(&r.fingerprint()) { signer = signer.add_intended_recipient(&key); } } message = signer.build()?; } if op.no_wrap { // This implicitly disables our recombination workaround // because this is RNP's native way to support the // combined method when GnuPG is used to literal wrap and // sign the data. t!("Literal data wrapping disabled, encrypting as-is."); std::io::copy(op.input, &mut message)?; } else { // Literal wrapping. message = LiteralWriter::new(message).build()?; if let Some(mut plaintext) = cached_plaintext { // Create a combined message over the original plaintext. std::io::copy(&mut plaintext, &mut message)?; } else { std::io::copy(op.input, &mut message)?; } } message.finalize()?; Ok(()) } rnp_return!(f(op)) } #[no_mangle] pub unsafe extern "C" fn rnp_op_encrypt_add_recipient(op: *mut RnpOpEncrypt, key: *const RnpKey) -> RnpResult { rnp_function!(rnp_op_encrypt_add_recipient, crate::TRACE); let op = assert_ptr_mut!(op); let key = assert_ptr_ref!(key); t!("Adding encryption-capable (sub)keys from {:X}", key.fingerprint()); // Try to locate encryption-capable subkeys. let mut found_some = false; if let Some(cert) = key.try_cert() { for ka in cert.keys().with_policy(&*op.ctx.policy(), None) .for_transport_encryption() .for_storage_encryption() .revoked(false) .supported() .alive() { t!("Adding (sub)key {:X}", ka.key().fingerprint()); op.recipients.push( (ka.valid_cert().features().unwrap_or_else(Features::empty), ka.key().clone().parts_into_unspecified())); found_some = true; } } if ! found_some { t!("Found no encryption-capable (sub)keys, adding primary key {:X}", key.fingerprint()); rnp_return_status!(RNP_ERROR_KEY_NOT_FOUND); } rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_op_encrypt_add_signature(op: *mut RnpOpEncrypt, key: *const RnpKey, sig: *mut *mut c_void) -> RnpResult { rnp_function!(rnp_op_encrypt_add_signature, crate::TRACE); use std::ops::Deref; let op = assert_ptr_mut!(op); let key = assert_ptr_ref!(key); arg!(sig); if ! sig.is_null() { warn!("changing signature parameters not implemented"); rnp_return_status!(RNP_ERROR_NOT_IMPLEMENTED); } rnp_return_status!(if let Ok(k) = key.deref().clone().parts_into_secret() { op.signers.push(k); RNP_SUCCESS } else if (*key.ctx).certs.key_on_agent(&key.fingerprint()) { let cert = key.try_cert().map(|c| c.deref().clone()); let key = key.deref().clone().parts_into_public(); op.agent_signers.push((cert, key)); RNP_SUCCESS } else { RNP_ERROR_NO_SUITABLE_KEY }) } #[no_mangle] pub unsafe extern "C" fn rnp_op_encrypt_set_armor(op: *mut RnpOpEncrypt, armored: bool) -> RnpResult { rnp_function!(rnp_op_encrypt_set_armor, crate::TRACE); let op = assert_ptr_mut!(op); arg!(armored); op.armor = armored; rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_op_encrypt_set_cipher(op: *mut RnpOpEncrypt, cipher: *const c_char) -> RnpResult { rnp_function!(rnp_op_encrypt_set_cipher, crate::TRACE); let op = assert_ptr_mut!(op); let cipher = assert_str!(cipher); (*op).cipher = Some(rnp_try!(SymmetricAlgorithm::from_rnp_id(cipher))); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_op_encrypt_set_aead(op: *mut RnpOpEncrypt, algo: *const c_char) -> RnpResult { rnp_function!(rnp_op_encrypt_set_aead, crate::TRACE); let _ = assert_ptr_mut!(op); let algo = assert_str!(algo); let aead = rnp_try!(Option::::from_rnp_id(algo)); rnp_return_status!(match aead { None => RNP_SUCCESS, Some(_) => RNP_ERROR_NOT_SUPPORTED, }) } #[no_mangle] pub unsafe extern "C" fn rnp_op_encrypt_set_aead_bits(op: *mut RnpOpEncrypt, encoded_bits: c_int) -> RnpResult { rnp_function!(rnp_op_encrypt_set_aead_bits, crate::TRACE); let _ = assert_ptr_mut!(op); arg!(encoded_bits); // This sets the chunk size using the OpenPGP wire encoding. rnp_return_status!(if (0..=16).contains(&encoded_bits) { // Range is okay, we just don't do anything. RNP_SUCCESS } else { RNP_ERROR_BAD_PARAMETERS }) } #[no_mangle] pub unsafe extern "C" fn rnp_op_encrypt_set_hash(op: *mut RnpOpEncrypt, hash: *const c_char) -> RnpResult { rnp_function!(rnp_op_encrypt_set_hash, crate::TRACE); let op = assert_ptr_mut!(op); let hash = assert_str!(hash); (*op).hash = Some(rnp_try!(HashAlgorithm::from_rnp_id(hash))); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_op_encrypt_set_flags(op: *mut RnpOpEncrypt, flags: RnpEncryptFlags) -> RnpResult { rnp_function!(rnp_op_encrypt_set_flags, crate::TRACE); let op = assert_ptr_mut!(op); arg!(flags); if flags & RNP_ENCRYPT_NOWRAP > 0 { t!("Disabling literal data wrapping."); op.no_wrap = true; } else { t!("Enabling literal data wrapping (the default)."); op.no_wrap = false; } rnp_success!() } #[cfg(test)] mod tests { use std::io::{Read, Write}; use std::ffi::CString; use std::ptr::null_mut; use super::*; use libc::c_char; use crate::rnp_ffi_create; use crate::io::RnpInput; use crate::import::rnp_import_keys; use crate::key::*; use crate::rnp_output_to_memory; use crate::rnp_output_memory_get_buf; use crate::rnp_input_from_memory; use crate::rnp_input_destroy; use crate::rnp_output_destroy; use crate::rnp_ffi_destroy; const RNP_SUCCESS: u32 = crate::error::RNP_SUCCESS.as_u32(); use openpgp::{ Cert, crypto::SessionKey, packet::prelude::*, parse::{ Parse, stream::*, }, serialize::SerializeInto, }; macro_rules! rnp_try { ($e: expr) => {{ let r = unsafe { $e }; assert_eq!(r, RNP_SUCCESS); }} } #[test] fn encrypt_no_wrap() -> openpgp::Result<()> { let (key, _) = openpgp::cert::CertBuilder::general_purpose(Some("uid")) .generate()?; let cert = key.to_vec()?; let mut ctx: *mut RnpContext = std::ptr::null_mut(); rnp_try!(rnp_ffi_create( &mut ctx as *mut *mut _, b"GPG\x00".as_ptr() as *const c_char, b"GPG\x00".as_ptr() as *const c_char)); let mut input: *mut RnpInput = std::ptr::null_mut(); rnp_try!(rnp_input_from_memory( &mut input as *mut *mut _, cert.as_ptr(), cert.len(), false)); // And load the certificate. rnp_try!(rnp_import_keys( ctx, input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS, std::ptr::null_mut() as *mut *mut _)); rnp_try!(rnp_input_destroy(input)); // Manually wrap a message. const MESSAGE: &[u8] = b"Hello World :)"; let mut wrapped = Vec::new(); let mut message = LiteralWriter::new(Message::new(&mut wrapped)).build()?; message.write_all(&MESSAGE)?; message.finalize()?; // Now encrypt it. let mut input: *mut RnpInput = std::ptr::null_mut(); rnp_try!(rnp_input_from_memory( &mut input as *mut *mut _, wrapped.as_ptr(), wrapped.len(), false)); let mut output: *mut RnpOutput = std::ptr::null_mut(); rnp_try!(rnp_output_to_memory(&mut output as *mut *mut _, 0)); let mut op = null_mut(); rnp_try!(rnp_op_encrypt_create(&mut op as *mut _, ctx, input, output)); rnp_try!(rnp_op_encrypt_set_flags(op, RNP_ENCRYPT_NOWRAP)); let fp = CString::new(key.fingerprint().to_string())?; let mut recipient = null_mut(); rnp_try!(rnp_locate_key(ctx, b"fingerprint\0".as_ptr() as *const _, fp.as_ptr(), &mut recipient as *mut _)); rnp_try!(rnp_op_encrypt_add_recipient(op, recipient)); rnp_try!(rnp_op_encrypt_execute(op)); let mut buf: *mut u8 = std::ptr::null_mut(); let mut len: libc::size_t = 0; rnp_try!(rnp_output_memory_get_buf( output, &mut buf as *mut *mut _, &mut len as *mut _, false)); let ciphertext = unsafe { std::slice::from_raw_parts(buf, len) }; // Decrypt it. struct Helper { key: openpgp::Cert, } impl VerificationHelper for Helper { fn get_certs(&mut self, _ids: &[openpgp::KeyHandle]) -> openpgp::Result> { Ok(Vec::new()) } fn check(&mut self, _structure: MessageStructure) -> openpgp::Result<()> { Ok(()) } } impl DecryptionHelper for Helper { fn decrypt(&mut self, pkesks: &[PKESK], skesks: &[SKESK], _sym_algo: Option, decrypt: &mut dyn FnMut(Option, &SessionKey) -> bool) -> openpgp::Result> { assert_eq!(pkesks.len(), 1); assert_eq!(skesks.len(), 0); let mut key = self.key.keys().secret().key_handle(pkesks[0].recipient().unwrap()) .next().unwrap().key().clone().into_keypair()?; pkesks[0].decrypt(&mut key, None) .map(|(algo, session_key)| decrypt(algo, &session_key)); Ok(None) } } let p = openpgp::policy::StandardPolicy::new(); let h = Helper { key, }; let mut v = DecryptorBuilder::from_bytes(ciphertext)? .with_policy(&p, None, h)?; let mut content = Vec::new(); v.read_to_end(&mut content)?; assert_eq!(&content, MESSAGE); rnp_try!(rnp_key_handle_destroy(recipient)); rnp_try!(rnp_input_destroy(input)); rnp_try!(rnp_output_destroy(output)); rnp_try!(rnp_ffi_destroy(ctx)); Ok(()) } } sequoia-octopus-librnp-1.11.1/src/op_generate.rs000064400000000000000000000264201046102023000177620ustar 00000000000000use std::{ time, }; use libc::{ c_char, }; use sequoia_openpgp as openpgp; use openpgp::{ Fingerprint, crypto::{ Password, }, cert::{ CertBuilder, CipherSuite, }, packet::{ Packet, UserID, Key, key::{ Key4, Key6, UnspecifiedParts, UnspecifiedRole, }, signature::{ SignatureBuilder, }, }, types::{ Curve, KeyFlags, PublicKeyAlgorithm, SignatureType, }, }; use crate::{ RnpContext, RnpResult, RnpPasswordFor, cstr_to_str, conversions::FromRnpId, key::RnpKey, error::*, }; pub struct RnpOpGenerate<'a> { ctx: &'a mut RnpContext, mode: Mode, pk_algo: PublicKeyAlgorithm, curve: Option, bits: Option, password: Option, expiration: Option, } enum Mode { PrimaryKey { userids: Vec, }, SubKey { version: u8, primary: Fingerprint, }, Generated { key: Key, }, } #[no_mangle] pub unsafe extern "C" fn rnp_op_generate_create(op: *mut *mut RnpOpGenerate, ctx: *mut RnpContext, alg: *const c_char) -> RnpResult { rnp_function!(rnp_op_generate_create, crate::TRACE); let op = assert_ptr_mut!(op); let ctx = assert_ptr_mut!(ctx); let alg = assert_str!(alg); let pk_algo = rnp_try!(PublicKeyAlgorithm::from_rnp_id(alg)); use PublicKeyAlgorithm::*; #[allow(deprecated)] match pk_algo { RSAEncryptSign | DSA | ECDSA | EdDSA => (), // Ok. _ => { warn!("public key algorithm unsupported or not signing-capable: {}", pk_algo); rnp_return_status!(RNP_ERROR_BAD_PARAMETERS); }, } *op = Box::into_raw(Box::new(RnpOpGenerate { ctx, mode: Mode::PrimaryKey { userids: Vec::new(), }, pk_algo, curve: None, bits: None, password: None, expiration: None, })); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_op_generate_subkey_create(op: *mut *mut RnpOpGenerate, ctx: *mut RnpContext, primary: *const RnpKey, alg: *const c_char) -> RnpResult { rnp_function!(rnp_op_generate_subkey_create, crate::TRACE); let op = assert_ptr_mut!(op); let ctx = assert_ptr_mut!(ctx); let primary = assert_ptr_ref!(primary); let alg = assert_str!(alg); let pk_algo = rnp_try!(PublicKeyAlgorithm::from_rnp_id(alg)); *op = Box::into_raw(Box::new(RnpOpGenerate { ctx: &mut *ctx, mode: Mode::SubKey { version: (*primary).version(), primary: (*primary).fingerprint(), }, pk_algo, curve: None, bits: None, password: None, expiration: None, })); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_op_generate_destroy(op: *mut RnpOpGenerate) -> RnpResult { rnp_function!(rnp_op_generate_destroy, crate::TRACE); arg!(op); if ! op.is_null() { drop(Box::from_raw(op)); } rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_op_generate_execute(op: *mut RnpOpGenerate) -> RnpResult { rnp_function!(rnp_op_generate_execute, crate::TRACE); let op = assert_ptr_mut!(op); fn f(op: &mut RnpOpGenerate) -> openpgp::Result<()> { let generated_key; match &op.mode { Mode::PrimaryKey { userids } => { use PublicKeyAlgorithm::*; use CipherSuite::*; use Curve::*; let cs = match op.pk_algo { RSAEncryptSign => match op.bits { None => RSA3k, Some(n) if n <= 2048 => RSA2k, Some(n) if n <= 3072 => RSA3k, _ => RSA4k, }, ECDSA => match op.curve { None => P256, Some(NistP256) => P256, Some(NistP384) => P384, Some(NistP521) => P521, Some(_) => // XXX: we could support more exotic curves here return Err(anyhow::anyhow!("not supported")), }, EdDSA => CipherSuite::Cv25519, _ => return Err(anyhow::anyhow!("not a suitable algorithm")), }; let mut builder = CertBuilder::new() .set_cipher_suite(cs) .set_primary_key_flags(KeyFlags::empty() .set_signing().set_certification()) .set_password(op.password.clone()) .set_validity_period(op.expiration); for u in userids { builder = builder.add_userid(u.clone()); } let (cert, _) = builder.generate()?; generated_key = cert.primary_key().key().clone() .parts_into_unspecified() .role_into_unspecified(); op.ctx.certs.write().insert(cert); }, Mode::SubKey { version, primary } => { use PublicKeyAlgorithm::*; use openpgp::packet::key::SubordinateRole; let mut key: Key<_, SubordinateRole> = match op.pk_algo { RSAEncryptSign => { let bits = match op.bits { None => 3072, Some(n) if n <= 2048 => 2048, Some(n) if n <= 3072 => 3072, _ => 4096, }; match version { 4 => Key4::generate_rsa(bits)?.into(), 6 | _ => Key6::generate_rsa(bits)?.into(), } }, ECDH => match version { 4 => Key4::generate_ecc( false, op.curve.take().unwrap_or(Curve::Cv25519))?.into(), 6 | _ => Key6::generate_ecc( false, op.curve.take().unwrap_or(Curve::Cv25519))?.into(), }, _ => return Err(anyhow::anyhow!("not a suitable algorithm")), }; if let Some(p) = op.password.as_ref() { key = key.encrypt_secret(p)?; } // We don't want to (and actually can't) hold the key // store lock around the decrypt_key_for: if we prompt // the user for a password, it could take a while. let cert = op.ctx.certs.read().by_primary_fp(primary) .ok_or_else(|| anyhow::anyhow!("key not found"))? .clone(); let primary_key = cert.primary_key().key().clone() .role_into_unspecified() .parts_into_secret()?; let primary_key = op.ctx.decrypt_key_for( Some(&cert), primary_key, RnpPasswordFor::AddSubkey)?; let mut signer = primary_key.into_keypair()?; let binding = key.bind( &mut signer, &cert, SignatureBuilder::new(SignatureType::SubkeyBinding) .set_key_flags(KeyFlags::empty() .set_storage_encryption() .set_transport_encryption())? .set_key_validity_period(op.expiration)?)?; op.ctx.certs.write() .insert(cert.insert_packets(vec![Packet::from(key.clone()), binding.into()])?.0); generated_key = key.parts_into_unspecified().role_into_unspecified(); }, Mode::Generated { .. } => { return Err(anyhow::anyhow!("key already generated")); }, } op.mode = Mode::Generated { key: generated_key, }; Ok(()) } rnp_return_status!(if let Err(e) = f(&mut *op) { log!("sequoia-octopus: failed to generate key: {}", e); RNP_ERROR_GENERIC } else { RNP_SUCCESS }) } #[no_mangle] pub unsafe extern "C" fn rnp_op_generate_get_key(op: *mut RnpOpGenerate, key: *mut *mut RnpKey) -> RnpResult { rnp_function!(rnp_op_generate_get_key, crate::TRACE); let op = assert_ptr_mut!(op); let key = assert_ptr_mut!(key); let key_out = key; rnp_return_status!(match &op.mode { Mode::Generated { key } => { *key_out = Box::into_raw(Box::new(RnpKey::without_cert(op.ctx, key.clone()))); RNP_SUCCESS }, _ => RNP_ERROR_BAD_PARAMETERS, }) } #[no_mangle] pub unsafe extern "C" fn rnp_op_generate_set_protection_password(op: *mut RnpOpGenerate, password: *const c_char) -> RnpResult { rnp_function!(rnp_op_generate_set_protection_password, crate::TRACE); let op = assert_ptr_mut!(op); assert_ptr!(password); (*op).password = Some(rnp_try!(cstr_to_str(password)).to_string().into()); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_op_generate_set_userid(op: *mut RnpOpGenerate, userid: *const c_char) -> RnpResult { rnp_function!(rnp_op_generate_set_userid, crate::TRACE); let op = assert_ptr_mut!(op); let userid = assert_str!(userid); rnp_return_status!(match (*op).mode { Mode::PrimaryKey { ref mut userids } => { userids.push(userid.into()); RNP_SUCCESS }, _ => RNP_ERROR_BAD_PARAMETERS, }) } #[no_mangle] pub unsafe extern "C" fn rnp_op_generate_set_bits(op: *mut RnpOpGenerate, bits: u32) -> RnpResult { rnp_function!(rnp_op_generate_set_bits, crate::TRACE); let op = assert_ptr_mut!(op); arg!(bits); (*op).bits = Some(bits); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_op_generate_set_curve(op: *mut RnpOpGenerate, curve: *const c_char) -> RnpResult { rnp_function!(rnp_op_generate_set_curve, crate::TRACE); let op = assert_ptr_mut!(op); let curve = assert_str!(curve); (*op).curve = Some(rnp_try!(Curve::from_rnp_id(curve))); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_op_generate_set_expiration(op: *mut RnpOpGenerate, expiration: u32) -> RnpResult { rnp_function!(rnp_op_generate_set_expiration, crate::TRACE); let op = assert_ptr_mut!(op); arg!(expiration); (*op).expiration = Some(time::Duration::new(expiration as u64, 0)); rnp_success!() } sequoia-octopus-librnp-1.11.1/src/op_sign.rs000064400000000000000000000177671046102023000171460ustar 00000000000000use std::io::Write; use libc::{ c_char, c_void, }; use sequoia_openpgp as openpgp; use openpgp::{ Cert, packet::{ Key, key::{ PublicParts, SecretParts, UnspecifiedRole, }, }, serialize::stream::*, types::{ HashAlgorithm, }, }; use crate::{ RnpContext, RnpResult, RnpInput, RnpOutput, RnpPasswordFor, conversions::FromRnpId, gpg, key::RnpKey, error::*, }; pub struct RnpOpSign<'a> { ctx: &'a mut RnpContext, input: &'a mut RnpInput, output: &'a mut RnpOutput<'a>, secret_keys: Vec>, agent_keys: Vec<(Option, Key)>, hash: Option, armor: bool, csf: bool, } #[no_mangle] pub unsafe extern "C" fn rnp_op_sign_detached_create<'a>(op: *mut *mut RnpOpSign<'a>, ctx: *mut RnpContext, input: *mut RnpInput, output: *mut RnpOutput<'a>) -> RnpResult { rnp_function!(rnp_op_sign_detached_create, crate::TRACE); let op = assert_ptr_mut!(op); let ctx = assert_ptr_mut!(ctx); let input = assert_ptr_mut!(input); let output = assert_ptr_mut!(output); *op = Box::into_raw(Box::new(RnpOpSign { ctx, input, output, secret_keys: Vec::new(), agent_keys: Vec::new(), hash: None, armor: false, csf: false, })); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_op_sign_cleartext_create<'a>(op: *mut *mut RnpOpSign<'a>, ctx: *mut RnpContext, input: *mut RnpInput, output: *mut RnpOutput<'a>) -> RnpResult { rnp_function!(rnp_op_sign_cleartext_create, crate::TRACE); let op = assert_ptr_mut!(op); let ctx = assert_ptr_mut!(ctx); let input = assert_ptr_mut!(input); let output = assert_ptr_mut!(output); *op = Box::into_raw(Box::new(RnpOpSign { ctx, input, output, secret_keys: Vec::new(), agent_keys: Vec::new(), hash: None, armor: false, csf: true, })); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_op_sign_destroy(op: *mut RnpOpSign) -> RnpResult { rnp_function!(rnp_op_sign_destroy, crate::TRACE); arg!(op); if ! op.is_null() { drop(Box::from_raw(op)); } rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_op_sign_execute(op: *mut RnpOpSign) -> RnpResult { rnp_function!(rnp_op_sign_execute, crate::TRACE); let op = assert_ptr_mut!(op); fn sign(op: &mut RnpOpSign) -> openpgp::Result<()> { assert!(! op.csf); let mut signature = Vec::new(); let mut message = Message::new(&mut signature); if op.armor { message = Armorer::new(message) .kind(openpgp::armor::Kind::Signature) .build()?; } // Maybe sign the message. if let Some(key) = op.secret_keys.pop() { let s = op.ctx.decrypt_key_for(None, key, RnpPasswordFor::Sign)? .into_keypair()?; let mut signer = Signer::new(message, s)? .hash_algo(op.hash.unwrap_or_default())? .detached(); for key in op.secret_keys.drain(..) { let s = op.ctx.decrypt_key_for(None, key, RnpPasswordFor::Sign)? .into_keypair()?; signer = signer.add_signer(s)?; } for (cert, key) in &op.agent_keys { let s = gpg::agent_keypair(&*op.ctx.policy(), &cert, &key)?; signer = signer.add_signer(s)?; } message = signer.build()?; } else if let Some((cert, key)) = op.agent_keys.get(0) { let s = gpg::agent_keypair(&*op.ctx.policy(), &cert, &key)?; let mut signer = Signer::new(message, s)? .hash_algo(op.hash.unwrap_or_default())? .detached(); for (cert, key) in &op.agent_keys[1..] { let s = gpg::agent_keypair(&*op.ctx.policy(), &cert, &key)?; signer = signer.add_signer(s)?; } message = signer.build()?; } std::io::copy(op.input, &mut message)?; message.finalize()?; // Stash the message. op.ctx.plaintext_cache.stash(&op.input, &signature); // And write the signature to the sink. op.output.write_all(&signature)?; Ok(()) } fn clearsign(op: &mut RnpOpSign) -> openpgp::Result<()> { assert!(op.csf); let mut message = Message::new(&mut op.output); // Maybe sign the message. if let Some(key) = op.secret_keys.pop() { let s = op.ctx.decrypt_key_for(None, key, RnpPasswordFor::Sign)? .into_keypair()?; let mut signer = Signer::new(message, s)? .hash_algo(op.hash.unwrap_or_default())? .cleartext(); for key in op.secret_keys.drain(..) { let s = op.ctx.decrypt_key_for(None, key, RnpPasswordFor::Sign)? .into_keypair()?; signer = signer.add_signer(s)?; } for (cert, key) in &op.agent_keys { let s = gpg::agent_keypair(&*op.ctx.policy(), &cert, &key)?; signer = signer.add_signer(s)?; } message = signer.build()?; } else if let Some((cert, key)) = op.agent_keys.get(0) { let s = gpg::agent_keypair(&*op.ctx.policy(), &cert, &key)?; let mut signer = Signer::new(message, s)? .hash_algo(op.hash.unwrap_or_default())? .cleartext(); for (cert, key) in &op.agent_keys[1..] { let s = gpg::agent_keypair(&*op.ctx.policy(), &cert, &key)?; signer = signer.add_signer(s)?; } message = signer.build()?; } std::io::copy(op.input, &mut message)?; message.finalize()?; Ok(()) } if op.csf { rnp_return!(clearsign(op)) } else { rnp_return!(sign(op)) } } #[no_mangle] pub unsafe extern "C" fn rnp_op_sign_add_signature(op: *mut RnpOpSign, key: *const RnpKey, sig: *mut *mut c_void) -> RnpResult { rnp_function!(rnp_op_sign_add_signature, crate::TRACE); use std::ops::Deref; let op = assert_ptr_mut!(op); let key = assert_ptr_ref!(key); arg!(sig); if ! sig.is_null() { warn!("changing signature parameters not implemented"); rnp_return_status!(RNP_ERROR_NOT_IMPLEMENTED); } rnp_return_status!(if let Ok(k) = key.deref().clone().parts_into_secret() { op.secret_keys.push(k); RNP_SUCCESS } else if (*key.ctx).certs.key_on_agent(&key.fingerprint()) { let cert = key.try_cert().map(|c| c.deref().clone()); let key = key.deref().clone().parts_into_public(); op.agent_keys.push((cert, key)); RNP_SUCCESS } else { RNP_ERROR_NO_SUITABLE_KEY }) } #[no_mangle] pub unsafe extern "C" fn rnp_op_sign_set_armor(op: *mut RnpOpSign, armored: bool) -> RnpResult { rnp_function!(rnp_op_sign_set_armor, crate::TRACE); let op = assert_ptr_mut!(op); arg!(armored); (*op).armor = armored; rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_op_sign_set_hash(op: *mut RnpOpSign, hash: *const c_char) -> RnpResult { rnp_function!(rnp_op_sign_set_hash, crate::TRACE); let op = assert_ptr_mut!(op); let hash = assert_str!(hash); (*op).hash = Some(rnp_try!(HashAlgorithm::from_rnp_id(hash))); rnp_success!() } sequoia-octopus-librnp-1.11.1/src/op_verify.rs000064400000000000000000001176241046102023000175030ustar 00000000000000use std::{ fmt, io, }; use libc::{ c_char, size_t, }; use sequoia_openpgp as openpgp; use openpgp::{ KeyHandle, KeyID, cert::{ Cert, }, packet::{ Signature, Key, key::{ PublicParts, SecretParts, UnspecifiedParts, UnspecifiedRole, }, }, parse::{ Parse, stream::*, }, types::{ SymmetricAlgorithm, AEADAlgorithm, }, }; use openpgp::crypto::{self, SessionKey}; use openpgp::packet::{PKESK, SKESK}; use sequoia_gpg_agent::gnupg; use crate::{ RnpContext, RnpResult, RnpInput, RnpOutput, RnpPasswordFor, RnpSignature, str_to_rnp_buffer, conversions::ToRnpId, key::RnpKey, error::*, }; /// Controls tracing in this module. const TRACE: bool = crate::TRACE; pub struct RnpOpVerify<'a> { ctx: &'a mut RnpContext, input: &'a mut RnpInput, mode: SignatureMode<'a>, result: RnpOpVerifyResult, } enum SignatureMode<'a> { Inline(&'a mut RnpOutput<'a>), Detached(&'a mut RnpInput), } #[derive(Default)] pub struct RnpOpVerifyResult { // Information about the symmetric encryption. mode: RnpProtectionMode, cipher: Option, pkesks: Vec, skesks: Vec, // rnp_op_verify_get_used_recipient returns the info from the // PKESK used to decrypt the message. pkesk_used: Option, // rnp_op_verify_get_used_symenc returns the info from the // SKESK used to decrypt the message. skesk_used: Option, // Any signatures. signatures: Vec, } impl fmt::Debug for RnpOpVerifyResult { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("RnpOpVerifyResult") .field("mode", &self.mode) .field("cipher", &self.cipher) .field("pkesks", &self.pkesks.iter().map(|pkesk| { pkesk.recipient().map(|r| r.to_hex()) .unwrap_or_else(|| "".into()) }).collect::>()) .field("skesk count", &self.skesks.len()) .field("pkesk_used", &self.pkesk_used.as_ref().map(|pkesk| { self.pkesks.iter().position(|x| x == pkesk).expect("added") })) .field("skesk_used", &self.skesk_used.as_ref().map(|skesk| { self.skesks.iter().position(|x| x == skesk).expect("added") })) .field("signatures", &self.signatures) .finish() } } impl RnpOpVerifyResult { /// Returns whether we operated on an encrypted message. fn encrypted_message(&self) -> bool { ! (self.pkesks.is_empty() && self.skesks.is_empty()) } /// Returns whether the message was decrypted successfully. fn decrypted_successfully(&self) -> bool { self.pkesk_used.is_some() || self.skesk_used.is_some() } /// Returns an verification failure (arbitrarily picks the first). fn verification_error(&self) -> Option { for s in &self.signatures { if s.status != RNP_SUCCESS { return Some(s.status); } } None } } pub struct RnpOpVerifySignature { ctx: *mut RnpContext, status: RnpStatus, sig: Signature, key: Option<(Key, Cert)>, } impl fmt::Debug for RnpOpVerifySignature { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("RnpOpVerifySignature") .field("ctx", &self.ctx) .field("status", &format_args!("{:X}", self.status)) .field("sig", &self.sig) .field("key", &self.key.as_ref().map(|(k, c)| { (c.fingerprint().to_hex(), k.fingerprint().to_hex()) })) .finish() } } #[derive(Debug, PartialEq, Eq)] pub enum RnpProtectionMode { None, Cfb, CfbMdc, Aead(AEADAlgorithm), } impl Default for RnpProtectionMode { fn default() -> Self { Self::None } } impl ToRnpId for RnpProtectionMode { fn to_rnp_id(&self) -> &str { use RnpProtectionMode::*; match self { None => "none", Cfb => "cfb", CfbMdc => "cfb-mdc", Aead(AEADAlgorithm::EAX) => "aead-eax", Aead(AEADAlgorithm::OCB) => "aead-ocb", Aead(_) => "aead-unknown", } } } #[no_mangle] pub unsafe extern "C" fn rnp_op_verify_create<'a>(op: *mut *mut RnpOpVerify<'a>, ctx: *mut RnpContext, input: *mut RnpInput, output: *mut RnpOutput<'a>) -> RnpResult { rnp_function!(rnp_op_verify_create, crate::TRACE); let op = assert_ptr_mut!(op); let ctx = assert_ptr_mut!(ctx); let input = assert_ptr_mut!(input); let output = assert_ptr_mut!(output); *op = Box::into_raw(Box::new(RnpOpVerify { ctx, input, mode: SignatureMode::Inline(output), result: Default::default(), })); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_op_verify_detached_create<'a>(op: *mut *mut RnpOpVerify<'a>, ctx: *mut RnpContext, input: *mut RnpInput, signature: *mut RnpInput) -> RnpResult { rnp_function!(rnp_op_verify_detached_create, crate::TRACE); let op = assert_ptr_mut!(op); let ctx = assert_ptr_mut!(ctx); let input = assert_ptr_mut!(input); let signature = assert_ptr_mut!(signature); *op = Box::into_raw(Box::new(RnpOpVerify { ctx, input, mode: SignatureMode::Detached(signature), result: Default::default(), })); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_op_verify_destroy(op: *mut RnpOpVerify) -> RnpResult { rnp_function!(rnp_op_verify_destroy, crate::TRACE); arg!(op); if ! op.is_null() { drop(Box::from_raw(op)); } rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_op_verify_execute(op: *mut RnpOpVerify) -> RnpResult { rnp_function!(rnp_op_verify_execute, crate::TRACE); let op = assert_ptr_mut!(op); fn f(op: &mut RnpOpVerify) -> openpgp::Result<()> { let policy = op.ctx.policy().clone(); match &mut op.mode { SignatureMode::Inline(output) => { t!("Inline signature verification or decryption"); // We first try to verify without decryption. This // way, Sequoia will handle messages using the // Cleartext Signature Framework. let mut input = op.input.try_clone()?; let h = Helper { ctx: op.ctx, result: &mut op.result, }; if VerifierBuilder::from_reader(&mut input) .and_then(|b| b.with_policy(&policy, None, h)) .and_then(|mut v| io::copy(&mut v, output).map_err(Into::into)) .is_ok() { t!("Verification without decryption successful: {:#?}", op.result); return Ok(()); } let h = Helper { ctx: op.ctx, result: &mut op.result, }; let mut v = DecryptorBuilder::from_reader(&mut op.input)? .with_policy(&policy, None, h)?; io::copy(&mut v, output)?; t!("Decryption successful: {:#?}", op.result); }, SignatureMode::Detached(signature) => { t!("Detached signature verification"); let h = Helper { ctx: op.ctx, result: &mut op.result, }; let mut v = DetachedVerifierBuilder::from_reader(signature)? .with_policy(&policy, None, h)?; v.verify_reader(&mut op.input)?; t!("Verification without decryption successful: {:#?}", op.result); }, } Ok(()) } rnp_return_status!(if let Err(e) = f(op) { if op.result.encrypted_message() && ! op.result.decrypted_successfully() { warn!("failed to decrypt: {}", e); RNP_ERROR_DECRYPT_FAILED } else if let Some(status) = op.result.verification_error() { warn!("failed to verify sig: {}", e); status } else { warn!("{}", e); // We need to return an error that Thunderbird // understands. It does not understand RNP_ERROR_GENERIC // in this context. See: // // https://searchfox.org/comm-central/rev/35c9e2929a5ae37d07192315db3787dc01f73441/mail/extensions/openpgp/content/modules/RNP.jsm#953 RNP_ERROR_DECRYPT_FAILED } } else { RNP_SUCCESS }) } #[no_mangle] pub unsafe extern "C" fn rnp_op_verify_get_signature_at(op: *mut RnpOpVerify, idx: size_t, signature: *mut *const RnpOpVerifySignature) -> RnpResult { rnp_function!(rnp_op_verify_get_signature_at, crate::TRACE); let op = assert_ptr_ref!(op); arg!(idx); let signature = assert_ptr_mut!(signature); rnp_return_status!(if let Some(s) = op.result.signatures.get(idx) { *signature = s as *const _; RNP_SUCCESS } else { RNP_ERROR_BAD_PARAMETERS }) } #[no_mangle] pub unsafe extern "C" fn rnp_op_verify_get_signature_count(op: *mut RnpOpVerify, count: *mut size_t) -> RnpResult { rnp_function!(rnp_op_verify_get_signature_count, crate::TRACE); let op = assert_ptr_ref!(op); let count = assert_ptr_mut!(count); *count = op.result.signatures.len(); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_op_verify_signature_get_handle(sig: *const RnpOpVerifySignature, handle: *mut *mut RnpSignature) -> RnpResult { rnp_function!(rnp_op_verify_signature_get_handle, crate::TRACE); let sig = assert_ptr_ref!(sig); let handle = assert_ptr_mut!(handle); *handle = Box::into_raw(Box::new( RnpSignature::new(sig.ctx, sig.sig.clone(), Some(sig.status == RNP_SUCCESS)))); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_op_verify_signature_get_status(sig: *const RnpOpVerifySignature) -> RnpResult { rnp_function!(rnp_op_verify_signature_get_status, crate::TRACE); assert_ptr!(sig); rnp_return_status!((*sig).status) } #[no_mangle] pub unsafe extern "C" fn rnp_op_verify_signature_get_key(sig: *const RnpOpVerifySignature, key_out: *mut *mut RnpKey) -> RnpResult { rnp_function!(rnp_op_verify_signature_get_key, crate::TRACE); let sig = assert_ptr_ref!(sig); let key_out = assert_ptr_mut!(key_out); rnp_return_status!(if let Some(key) = (*sig).key.as_ref().cloned() { *key_out = Box::into_raw(Box::new(RnpKey::new((*sig).ctx, key.0, &key.1))); RNP_SUCCESS } else { *key_out = std::ptr::null_mut(); RNP_ERROR_KEY_NOT_FOUND }) } #[no_mangle] pub unsafe extern "C" fn rnp_op_verify_signature_get_times(sig: *const RnpOpVerifySignature, created: *mut u32, expires: *mut u32) -> RnpResult { rnp_function!(rnp_op_verify_signature_get_times, crate::TRACE); use std::time::UNIX_EPOCH; let sig = assert_ptr_ref!(sig); arg!(created); arg!(expires); if ! created.is_null() { *created = (*sig).sig.signature_creation_time().unwrap_or(UNIX_EPOCH) .duration_since(UNIX_EPOCH).unwrap().as_secs() as u32; } if ! expires.is_null() { *expires = (*sig).sig.signature_expiration_time().map(|e| { e.duration_since(UNIX_EPOCH).unwrap().as_secs() as u32 }).unwrap_or(0); } rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_op_verify_get_protection_info(op: *const RnpOpVerify, mode: *mut *mut c_char, cipher: *mut *mut c_char, valid: *mut bool) -> RnpResult { rnp_function!(rnp_op_verify_get_protection_info, crate::TRACE); let op = assert_ptr_ref!(op); arg!(mode); arg!(cipher); arg!(valid); if ! mode.is_null() { *mode = str_to_rnp_buffer(op.result.mode.to_rnp_id()); } if ! cipher.is_null() { *cipher = str_to_rnp_buffer( op.result.cipher.unwrap_or(SymmetricAlgorithm::Unencrypted) .to_rnp_id()); } if ! valid.is_null() { *valid = if op.result.cipher.unwrap_or(SymmetricAlgorithm::Unencrypted) != SymmetricAlgorithm::Unencrypted && op.result.mode != RnpProtectionMode::None && op.result.mode != RnpProtectionMode::Cfb { true } else { false }; } rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_op_verify_get_used_recipient(op: *const RnpOpVerify, pkesk: *mut *const PKESK) -> RnpResult { rnp_function!(rnp_op_verify_get_used_recipient, crate::TRACE); let op = assert_ptr_ref!(op); let pkesk = assert_ptr_mut!(pkesk); *pkesk = op.result.pkesk_used.as_ref().map(|p| p as *const _) .unwrap_or(std::ptr::null()); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_op_verify_get_recipient_at(op: *const RnpOpVerify, idx: size_t, pkesk: *mut *const PKESK) -> RnpResult { rnp_function!(rnp_op_verify_get_recipient_at, crate::TRACE); let op = assert_ptr_ref!(op); arg!(idx); let pkesk = assert_ptr_mut!(pkesk); rnp_return_status!(if let Some(p) = op.result.pkesks.get(idx) { *pkesk = p as *const _; RNP_SUCCESS } else { RNP_ERROR_BAD_PARAMETERS }) } #[no_mangle] pub unsafe extern "C" fn rnp_op_verify_get_recipient_count(op: *const RnpOpVerify, count: *mut size_t) -> RnpResult { rnp_function!(rnp_op_verify_get_recipient_count, crate::TRACE); let op = assert_ptr_ref!(op); let count = assert_ptr_mut!(count); *count = op.result.pkesks.len(); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_recipient_get_keyid(recipient: *const PKESK, keyid: *mut *mut c_char) -> RnpResult { rnp_function!(rnp_recipient_get_keyid, crate::TRACE); let recipient = assert_ptr_ref!(recipient); let keyid = assert_ptr_mut!(keyid); let r = (*recipient).recipient(); *keyid = str_to_rnp_buffer(format!("{:X}", match r { Some(KeyHandle::KeyID(i)) => i.clone(), Some(KeyHandle::Fingerprint(i)) => i.into(), // XXX: Best effort given the API. None => KeyID::wildcard(), })); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_recipient_get_alg(recipient: *const PKESK, alg: *mut *mut c_char) -> RnpResult { rnp_function!(rnp_recipient_get_alg, crate::TRACE); let recipient = assert_ptr_ref!(recipient); let alg = assert_ptr_mut!(alg); *alg = str_to_rnp_buffer((*recipient).pk_algo().to_rnp_id()); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_op_verify_get_used_symenc(op: *const RnpOpVerify, skesk: *mut *const SKESK) -> RnpResult { rnp_function!(rnp_op_verify_get_used_symenc, crate::TRACE); let op = assert_ptr_ref!(op); let skesk = assert_ptr_mut!(skesk); *skesk = op.result.skesk_used.as_ref().map(|p| p as *const _) .unwrap_or(std::ptr::null()); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_op_verify_get_symenc_at(op: *const RnpOpVerify, idx: size_t, skesk: *mut *const SKESK) -> RnpResult { rnp_function!(rnp_op_verify_get_symenc_at, crate::TRACE); let op = assert_ptr_ref!(op); arg!(idx); let skesk = assert_ptr_mut!(skesk); rnp_return_status!(if let Some(p) = op.result.skesks.get(idx) { *skesk = p as *const _; RNP_SUCCESS } else { RNP_ERROR_BAD_PARAMETERS }) } #[no_mangle] pub unsafe extern "C" fn rnp_op_verify_get_symenc_count(op: *const RnpOpVerify, count: *mut size_t) -> RnpResult { rnp_function!(rnp_op_verify_get_symenc_count, crate::TRACE); let op = assert_ptr_ref!(op); let count = assert_ptr_mut!(count); *count = op.result.skesks.len(); rnp_success!() } struct Helper<'a> { ctx: &'a mut RnpContext, result: &'a mut RnpOpVerifyResult, } impl<'a> VerificationHelper for Helper<'a> { fn get_certs(&mut self, ids: &[KeyHandle]) -> openpgp::Result> { Ok(ids.iter().filter_map(|id| { match self.ctx.cert(&id.into()) { Some(cert) => Some(cert), None => { // The lookup failed. If we are in the process of // loading a keyring, wait until that is done, and // then retry the lookup. if let Ok(true) = self.ctx.certs.block_on_load() { self.ctx.cert(&id.into()) } else { None } } } }).collect()) } fn check(&mut self, structure: MessageStructure) -> openpgp::Result<()> { rnp_function!(Helper::check, TRACE); use self::VerificationError::*; // Thunderbird doesn't deal well with not having a key // associated with a signature, even though that seems // possible in RNP. Make a last-ditch effort to find the key. fn last_ditch_key_lookup(ctx: &RnpContext, sig: &Signature, cert: Option<&Cert>) -> Option<(Key, Cert)> { t!("Doing a last-ditch key lookup for {:?}, {:?}", sig, cert); if let Some(cert) = cert { for h in sig.get_issuers() { if let Some(key) = cert.keys().key_handle(h).nth(0) { t!("success: key {}, cert {}", key.key().fingerprint(), cert.fingerprint()); return Some((key.parts_as_unspecified().key().clone(), cert.clone())); } } } for h in sig.get_issuers() { if let Some(cert) = ctx.cert_by_subkey_handle(&h) { if let Some(key) = cert.keys().key_handle(h).nth(0) { t!("success: key {}, cert {}", key.key().fingerprint(), cert.fingerprint()); return Some((key.parts_as_unspecified().key().clone(), cert.clone())); } } } t!("failed"); None } let n_layers = structure.iter().count(); for (i, layer) in structure.into_iter().enumerate() { match (i, layer) { (i, MessageLayer::SignatureGroup { results }) if i == n_layers - 1 => { for (signo, result) in results.into_iter().enumerate() { match result { Ok(GoodChecksum { sig, ka, .. }) => { t!("Signature {}.{} is good", i, signo); self.result.signatures.push( RnpOpVerifySignature { ctx: self.ctx as *mut _, status: RNP_SUCCESS, sig: sig.clone(), key: Some((ka.key().clone() .parts_into_unspecified(), ka.cert().clone())), }); }, Err(MalformedSignature { sig, error }) => { t!("Signature {}.{} is malformed: {}", i, signo, error); self.result.signatures.push( RnpOpVerifySignature { ctx: self.ctx as *mut _, status: RNP_ERROR_SIGNATURE_INVALID, sig: sig.clone(), key: last_ditch_key_lookup( self.ctx, sig, None), }); }, Err(MissingKey { sig, .. }) => { t!("Signature {}.{}: can't verify, missing key", i, signo); self.result.signatures.push( RnpOpVerifySignature { ctx: self.ctx as *mut _, status: RNP_ERROR_KEY_NOT_FOUND, sig: sig.clone(), key: last_ditch_key_lookup( self.ctx, sig, None), }); }, Err(UnboundKey { sig, cert, error }) => { t!("Signature {}.{}: can't verify, \ unbound key from {}: {}", i, signo, cert.fingerprint(), error); self.result.signatures.push( RnpOpVerifySignature { ctx: self.ctx as *mut _, status: RNP_ERROR_SIGNATURE_INVALID, sig: sig.clone(), // The key was not bound under // the policy, let's see if we // can get it without // validating the cert. key: last_ditch_key_lookup( self.ctx, sig, Some(cert)), }); }, Err(BadKey { sig, ka, error }) => { t!("Signature {}.{}: can't verify, \ bad key ({}): {}", i, signo, ka.key().fingerprint(), error); self.result.signatures.push( RnpOpVerifySignature { ctx: self.ctx as *mut _, status: RNP_ERROR_SIGNATURE_INVALID, sig: sig.clone(), key: Some((ka.key().clone() .parts_into_unspecified(), ka.cert().clone())), }); }, Err(BadSignature { sig, ka, error }) => { t!("Signature {}.{}: bad signature by {}: {}", i, signo, ka.key().fingerprint(), error); self.result.signatures.push( RnpOpVerifySignature { ctx: self.ctx as *mut _, status: if sig.signature_alive(None, None).is_err() { RNP_ERROR_SIGNATURE_EXPIRED } else { RNP_ERROR_SIGNATURE_INVALID }, sig: sig.clone(), key: Some((ka.key().clone() .parts_into_unspecified(), ka.cert().clone())), }); }, Err(UnknownSignature { sig, .. }) => { t!("Signature {}.{}: unknown signature: {}", i, signo, sig.error()); // XXX: Not sure what to do with it. }, Err(e) => { t!("Signature {}.{}: {}", i, signo, e); // XXX: Not sure what to do with it. }, } } } (n, MessageLayer::Compression { .. }) if n == 0 || n == 1 => { // Do nothing. }, (0, MessageLayer::Encryption { sym_algo, aead_algo, .. }) => { self.result.cipher = Some(sym_algo); self.result.mode = match aead_algo { Some(a) => RnpProtectionMode::Aead(a), None => RnpProtectionMode::CfbMdc, }; }, _ => { return Err( anyhow::anyhow!("Unsupported message structure") ); }, } } Ok(()) } } impl<'a> Helper<'a> { /// Returns the decryption key with the given keyid. fn get_decryption_key(&self, id: &KeyID) -> Option> { rnp_function!(Helper::get_decryption_key, crate::TRACE); t!("want key {}", id); let ks = self.ctx.certs.read(); let r = ks.by_subkey_id(id) .chain(ks.by_primary_id(id)) .filter_map(|cert| { t!("candidate cert {}", cert.fingerprint()); match cert.with_policy(&*self.ctx.policy(), None) { Ok(vcert) => { let r = vcert.keys() .secret() .for_storage_encryption() .for_transport_encryption() .key_handle(id).nth(0) .map(|vka| vka.key().clone()); // Some diagnostics: if r.is_none() { if vcert.keys() .for_storage_encryption() .for_transport_encryption() .key_handle(id).nth(0).is_some() { t!("the key exists, but we don't have \ the secret"); } if let Some(k) = vcert.keys().key_handle(id).nth(0) { t!("the key exists, but it is not \ marked as encryption capable"); t!("key flags: {:?}", k.key_flags()); } } r }, Err(e) => { t!("but it is not valid: {}", e); None }, } }) .nth(0); r } /// Returns the decryption key with the given keyid. fn get_public_key(&self, id: &KeyID) -> Option<(Cert, Key)> { rnp_function!(Helper::get_public_key, crate::TRACE); t!("want key {}", id); let ks = self.ctx.certs.read(); let r = ks.by_subkey_id(id) .chain(ks.by_primary_id(id)) .filter_map(|cert| { t!("candidate cert {}", cert.fingerprint()); match cert.with_policy(&*self.ctx.policy(), None) { Ok(vcert) => { vcert.keys() .key_handle(id).nth(0) .map(|vka| (cert.clone(), vka.key().clone())) }, Err(e) => { t!("but it is not valid: {}", e); None }, } }) .nth(0); r } /// Tries to decrypt the given PKESK packet with `keypair` and try /// to decrypt the packet parser using `decrypt`. fn try_decrypt(&self, pkesk: &PKESK, algo: Option, keypair: &mut dyn crypto::Decryptor, decrypt: &mut dyn FnMut(Option, &SessionKey) -> bool) -> Option<(Option, SessionKey, Option)> { let fp = keypair.public().fingerprint(); let (algo, sk) = pkesk.decrypt(keypair, algo) .and_then(|(algo, sk)| { if decrypt(algo, &sk) { Some((algo, sk)) } else { None } })?; // We need the recipient's fingerprint for the intended // recipient test. If `keypair` is a subkey, look up its cert // and compute the fingerprint. Otherwise, `fp` is our // identity. let ks = self.ctx.certs.read(); // First, try a subkey lookup. let mut recipient = ks.by_subkey_fp(&fp) .filter(|fp| fp.is_tsk()) .nth(0) .map(|cert| cert.clone().strip_secret_key_material()); // Then lookup as cert, unlikely as that may be. if recipient.is_none() { recipient = ks.by_fp(&fp) .filter(|fp| fp.is_tsk()) .nth(0) .map(|cert| cert.clone().strip_secret_key_material()); } Some((algo, sk, recipient)) } } impl<'a> DecryptionHelper for Helper<'a> { fn decrypt(&mut self, pkesks: &[PKESK], skesks: &[SKESK], algo: Option, decrypt: &mut dyn FnMut(Option, &SessionKey) -> bool) -> openpgp::Result> { rnp_function!(Helper::decrypt, crate::TRACE); t!("{} PKESKs, {} SKESKs, algo: {:?}", pkesks.len(), skesks.len(), algo); // Preserve PKESKs and SKESKs for later inspection. self.result.pkesks = pkesks.iter().cloned().collect(); self.result.skesks = skesks.iter().cloned().collect(); // Even if we happen to be loading a keyring in the // background, try the lookup. t!("trying to decrypt PKESKs with unencrypted keys"); for &block in [false, true].iter() { if block { if let Ok(false) = self.ctx.certs.block_on_load() { // We didn't block. It is extremely unlikely that // the keystore was updated. t!("we didn't block, breaking out of the loop."); break; } } // First, we try those keys that we can use without // prompting for a password. for pkesk in pkesks { let keyid = if let Some(r) = pkesk.recipient() { KeyID::from(r) } else { // Skip anonymous recipients. continue; }; t!("trying to decrypt PKESK with recipient {:?}", keyid); if let Some(key) = self.get_decryption_key(&keyid) { if ! key.secret().is_encrypted() { if let Some((_algo, _sk, fp)) = key.clone().into_keypair().ok().and_then(|mut k| { self.try_decrypt(pkesk, algo, &mut k, decrypt) }) { t!("success!"); self.result.pkesk_used = Some(pkesk.clone()); return Ok(fp); } else { t!("failure!"); } } else { t!("but it is encrypted!"); } } else { t!("but we don't have that key!"); } } } // Second, we try to decrypt PKESK packets with wildcard // recipients using those keys that we can use without // prompting for a password. t!("trying to decrypt PKESKs using wildcard recipients with \ unencrypted keys"); let ks = self.ctx.certs.read(); for pkesk in pkesks.iter().filter(|p| p.recipient().is_none()) { t!("trying to decrypt PKESK with wildcard recipient..."); // Get all possible decryption keys from the context. for cert in ks.iter().filter(|cert| cert.is_tsk()) { t!("... using cert {}...", cert.fingerprint()); for key in cert.with_policy(&*self.ctx.policy(), None).ok() .iter() .flat_map(|vcert| vcert.keys() .secret() .for_storage_encryption() .for_transport_encryption() .map(|vka| vka.key().clone())) { t!("... and key {}", key.fingerprint()); if ! key.secret().is_encrypted() { if let Some((_algo, _sk, fp)) = key.clone().into_keypair().ok().and_then(|mut k| { self.try_decrypt(pkesk, algo, &mut k, decrypt) }) { t!("success!"); self.result.pkesk_used = Some(pkesk.clone()); return Ok(fp); } else { t!("failure!"); } } else { t!("but it is encrypted!"); } } } } drop(ks); t!("trying to decrypt PKESKs with encrypted keys"); // Third, we try those keys that are encrypted. 'next_pkesk: for pkesk in pkesks { // Don't ask the user to decrypt a key if we don't support // the algorithm. if ! pkesk.pk_algo().is_supported() { continue; } let keyid = if let Some(r) = pkesk.recipient() { KeyID::from(r) } else { // Skip anonymous recipients. continue; }; t!("trying to decrypt PKESK with recipient {}", keyid); if let Some(key) = self.get_decryption_key(&keyid) { let mut keypair = if ! key.secret().is_encrypted() { t!("odd, it is not encrypted, why didn't we \ decrypt this PKESK before?"); key.clone().into_keypair()? } else { match self.ctx.decrypt_key_for( None, // XXX key.clone(), RnpPasswordFor::Decrypt) { Ok(k) => k.into_keypair()?, Err(_) => { t!("failed to decrypt the key!"); break 'next_pkesk; }, } }; if let Some((_algo, _sk, fp)) = self.try_decrypt(pkesk, algo, &mut keypair, decrypt) { t!("success!"); self.result.pkesk_used = Some(pkesk.clone()); return Ok(fp); } else { t!("failure!"); } } else { t!("but we don't have that key!"); } } t!("trying to decrypt PKESKs with gpg-agent"); for pkesk in pkesks { let keyid = if let Some(r) = pkesk.recipient() { KeyID::from(r) } else { // Skip anonymous recipients. continue; }; t!("trying to decrypt PKESK with recipient {}", keyid); if let Some((cert, key)) = self.get_public_key(&keyid) { match gnupg::Context::new() { Ok(ctx) => match sequoia_gpg_agent::KeyPair::new_for_gnupg_context( &ctx, key.parts_as_public()) { Ok(mut pair) => { if let Ok(vcert) = cert.with_policy( &*self.ctx.policy(), None) { pair = pair.with_cert(&vcert); } if pkesk.decrypt(&mut pair, algo) .map(|(algo, session_key)| decrypt(algo, &session_key)) .unwrap_or(false) { t!("success!"); self.result.pkesk_used = Some(pkesk.clone()); return Ok(Some(cert.clone())); } else { t!("failure!"); } }, Err(e) => t!("but gpg-agent failed: {}", e), }, Err(e) => t!("but creating the GnuPG context failed: {}", e), } } else { t!("but we don't even have that cert!"); } } if skesks.is_empty() { t!("decryption using PKESKs failed and there are no SKESKs"); return Err(anyhow::anyhow!("No key to decrypt message")); } // Finally, try to decrypt using the SKESKs. //for password in self.passwords.iter() { // for skesk in skesks { // if let Some((algo, sk)) = skesk.decrypt(password).ok() // .and_then(|(algo, sk)| { // if decrypt(algo, &sk) { // Some((algo, sk)) // } else { // None // } // }) // { // return Ok(None); // } // } //} t!("decryption using SKESKs is not supported by Thunderbird, \ giving up"); Err(anyhow::anyhow!("Decryption failed")) } } sequoia-octopus-librnp-1.11.1/src/parcimonie.rs000064400000000000000000000552121046102023000176210ustar 00000000000000//! Parcimonie support. //! //! If a user publishes a revocation certificate or a certificate //! update, we don't want to wait more than a week to find out about //! it. //! //! If the user has N certificates that they are monitoring for //! updates, and we check if there are updates for them all at once, //! then we reveal all of the certificates that the client is //! interested in to the keyserver. Further, the keyserver is able to //! fingerprint the client, because most users have different sets of //! keys. A key server could use this information to withhold some //! information from an individual, such as a revocation certificate, //! for instance. //! //! That's not good, and we can do better. First, we can stagger the //! updates. Then, the keyserver operator has to do more work to link //! the individual requests together. Second, we can obscure the //! origin of the request so that it is more difficult to determine //! what request came from what client. //! //! Staggering updates is straightforward: the implementation just //! needs to be adjusted. Hiding the client is more difficult. An //! effective way to do this is to use Tor. But, even without Tor, a //! user can still hide in the crowd. For instance, if the client is //! behind a NAT (relative to the attacker) and there are other //! clients performing updates on the same network, then it will be //! harder for the key server to distinguish the different clients //! behind the NAT. //! //! If we distribute updates evenly, i.e., waiting 1 week / N time //! between updates, then once the attacker sees two updates, they //! know when the client will do the next update. //! //! We can improve upon this by instead using a random update //! interval. In particular, we want to sample from a memoryless //! distribution. This prevents an attacker predicting when we will //! do our next update. //! //! This isn't a cure all. If a client uses this approach, an //! attacker who can observe the network and attribute requests to a //! single client can still determine N after observing many requests. //! //! Poisson is a memory-less distribution. Its parameter lambda is the //! mean time between events. In our case, an event is an update. //! Since we want to update every certificate once a week, and we have //! N certificates, we set lambda to 1 week / N. //! //! To further obscure N, we round N to the next power of 1.5. //! //! To avoid flooding the network, we set a lower bound to 5 minutes. //! Because the user may add new certificates and we only recompute //! the time to sleep after doing an update, we limit lambda to 19 //! hours. use std::sync::Arc; use std::collections::btree_map::{BTreeMap, Entry}; use std::fmt; use std::sync::RwLock; use std::sync::RwLockReadGuard; use std::thread; use std::time::Duration; use anyhow::Result; use tokio::task::JoinSet; use sequoia_openpgp as openpgp; use openpgp::{Fingerprint, KeyHandle}; use openpgp::cert::prelude::*; use openpgp::types::RevocationStatus; use openpgp::packet::prelude::*; use openpgp::policy::Policy; use sequoia_net as net; use rand::prelude::*; use rand_distr::{Poisson, Distribution}; use crate::Keystore; use crate::keystore::KeystoreData; /// Controls tracing in this module. const TRACE: bool = crate::TRACE; // This is a simple heuristic to check whether a certificate might be // flooded. If a User ID or attribute has more than this number of // third-party certificates that we prune ones that are not useful. const THIRD_PARTY_SIG_THRESHOLD: usize = 250; pub struct Parcimonie { } impl Parcimonie { /// Creates a new parcimonie instance. /// /// This starts a work thread, which looks for updates for the /// specified certificates. /// /// You can poll for updates using `Parcimonie::updates`. To /// watch additional certificates, use `Parcimonie::monitor`. pub fn new

(policy: P, ks: &Keystore) -> Result where P: Policy + 'static { ParcimonieServer::new(policy, ks.create_ref())?; Ok(Parcimonie { }) } } pub struct ParcimonieServer { policy: P, ks: Arc>, } impl ParcimonieServer

{ // Instantiates a new parcimonie server, which keeps the key store // up to date. fn new(policy: P, ks: Arc>) -> Result<()> { let pdata = ParcimonieServer { ks: ks, policy, }; thread::Builder::new().name("sq parcimonie".to_string()) .spawn(move || ParcimonieServer::run(pdata))?; Ok(()) } fn run(mut self) { rnp_function!(ParcimonieServer::run, TRACE); // Sleep for a while to avoid interfering with TB's startup. let delay = if TRACE { 30 } else { 5 * 60 }; thread::sleep(Duration::new(delay, 0)); let rt = tokio::runtime::Runtime::new() .expect("failed to start a tokio runtime"); loop { let r = rt.block_on(self.worker()); // This shouldn't happen. If it does, sleep a while and // then restart. t!("worker returned unexpectedly: {:?}", r); thread::sleep(Duration::new(5 * 60, 0)); } } async fn worker(&mut self) -> openpgp::Result<()> { rnp_function!(Parcimonie::worker, TRACE); let mut rng = rand::thread_rng(); let http_client = net::reqwest::Client::builder() .user_agent(format!("sequoia-octopus-librnp {}", env!("CARGO_PKG_VERSION"))) .connect_timeout(Duration::new(15, 0)) .timeout(Duration::new(5, 0)) .build()?; let keyservers = [ "hkps://keys.openpgp.org", "hkps://mail-api.proton.me", "hkps://keys.mailvelope.com", "hkps://keyserver.ubuntu.com", ]; let mut n = self.ks.read().unwrap().count(); loop { { let bucket = 1.5f32.powf((n as f32).log(1.5).round()); t!("n: {} => bucket: {}", n, bucket); const LOWER_BOUND: f32 = 5. * 60.; const UPPER_BOUND: f32 = 19. * 60. * 60.; let lambda: f32 = match (7. * 24. * 60. * 60.) / if bucket > 1. { bucket } else { 1. } { lambda if lambda < LOWER_BOUND => LOWER_BOUND, lambda if lambda > UPPER_BOUND => UPPER_BOUND, lambda => lambda, }; let poi = Poisson::new(lambda).expect("valid argument"); let s = poi.sample(&mut rng) as u64; t!("poisson({:?}) sample: {:?}", Duration::new(lambda as u64, 0), Duration::new(s, 0)); // An extra, extra safety measure: wait at least a few // seconds between updates. let s = std::cmp::max(s, 5); let duration = Duration::new(s, 0); t!("Waiting {:?} seconds before checking for \ next update", duration); // Now sleep. thread::sleep(duration); } // Extract the information we need to do the update and // then drop the lock. let (fpr, emails) = { let ks = self.ks.read().unwrap(); // While sleeping, the number of certificates that we // monitor may have changed. n = ks.count(); if n == 0 { // The key store is empty. Go back to sleep. continue; } // If everything is revoked, don't spin forever. let mut cert = None; for _ in 0..20 { let i = rng.gen_range(0..n); let c = ks.iter().nth(i).unwrap(); match c.with_policy(&self.policy, None) { Ok(vc) => { if let RevocationStatus::Revoked(_) = vc.revocation_status() { // The certificate is revoked. Don't // bother looking for updates. continue; } else { cert = Some(c); break; } } Err(_) => { // Don't bother to look for updates for // certificates that are not valid under // the standard policy. // // Note: this also means that we won't // look for updates to stripped keys, // e.g., those returned by // keys.openpgp.org whose User IDs have // been stripped, and that don't have a // direct key signature. continue; } } } let cert = if let Some(cert) = cert { cert } else { t!("Not bothering to update an invalid or \ revoked certificate, sleeping."); continue; }; let fpr = cert.fingerprint(); t!("Checking for updates to {}!", fpr); // Get all of the valid, non-revoked email addresses. let emails: Vec = match cert.with_policy(&self.policy, None) { Ok(vcert) => { let mut emails: Vec = vcert.userids() .filter_map(|ua| { if let RevocationStatus::Revoked(_) = ua.revocation_status() { None } else { ua.userid().email().unwrap_or(None) .map(ToString::to_string) } }) .collect(); emails.sort(); emails.dedup(); emails } Err(_) => Vec::with_capacity(0), }; (fpr, emails) }; // Do this is parallel. Not to be fast, but to overlap I/O. let mut requests = JoinSet::new(); for ks in keyservers.iter().map( |ks| net::KeyServer::with_client(ks, http_client.clone())) .collect::>>()? { let fp = fpr.clone(); requests.spawn(async move { let results = ks.get(&fp).await; Response { query: Query::Handle(fp.into()), results, method: Method::KeyServer( ks.url().as_str().to_string()), } }); } for email in emails { let client = http_client.clone(); let email = email.clone(); requests.spawn(async move { let results = net::wkd::get(&client, &email).await; Response { query: Query::Address(email.to_string()), results, method: Method::WKD, } }); } let mut certs = BTreeMap::new(); while let Some(response) = requests.join_next().await { let response = response?; match response.results { Ok(returned_certs) => for cert in returned_certs { match cert { Ok(cert) => { t!("{}({}): {:?}", response.method, response.query, cert.keyid()); match certs.entry(cert.fingerprint()) { Entry::Vacant(e) => { e.insert(cert); }, Entry::Occupied(mut e) => { let old = e.get().clone(); e.insert(old.merge_public(cert)?); }, } }, Err(e) => t!("{}({}): {:?}", response.method, response.query, e), } }, Err(e) => t!("{}({}): {:?}", response.method, response.query, e), } } if certs.len() > 0 { let ks = self.ks.read().unwrap(); let certs = certs.into_values() .filter_map(|cert| { let cert = cert.strip_secret_key_material(); // Merge the update into the existing key // material, if any. let cert = if let Some(existing) = ks.by_primary_fp(&cert.fingerprint()) { existing.clone().merge_public(cert) .expect("same certificate") } else { cert }; // See if it needs cleaning. self.clean(cert) }) .collect::>(); // Upgrade the lock. drop(ks); let mut ks = self.ks.write().unwrap(); for cert in certs.into_iter() { ks.insert(cert); } ks.mark_updated(); } } } /// Cleans a certificate. /// /// This tries to detect if a certificate is flooded and if so, /// tries to recover. If the certificate is flooded and not valid /// under the policy, then it is simply dropped. /// /// This function takes a read lock on the keystore. fn clean(&self, cert: Cert) -> Option { rnp_function!(Parcimonie::clean, TRACE); use std::collections::HashMap; use std::collections::hash_map::Entry; // Check for an excess of third-party signatures. let flooded_uids = cert.userids() .any(|ua| { let c = ua.certifications().count(); if c > THIRD_PARTY_SIG_THRESHOLD { t!("{}, {} appears to be flooded ({} certifications)", cert.fingerprint(), ua.userid(), c); true } else { false } }); let flooded_uas = cert.user_attributes().enumerate() .any(|(i, ua)| { let c = ua.certifications().count(); if c > THIRD_PARTY_SIG_THRESHOLD { t!("{}, UA #{} appears to be flooded ({} certifications)", cert.fingerprint(), i, c); true } else { false } }); if ! flooded_uids && ! flooded_uas { t!("Certificate does not appear to be flooded"); return Some(cert); } t!("Certificate might be flooded, \ dropping 3rd party certifications that we can't check"); // Iterate over all of the Cert components, pushing // packets we want to keep into the accumulator. let vc = match cert.with_policy(&self.policy, None) { Ok(vc) => vc, Err(err) => { t!("Cert is not valid under the policy, ignoring: {}", err); return None; } }; fn filter<'a>(userid: Option<&UserID>, ks: &RwLockReadGuard, sigs: impl Iterator) -> Vec { let mut most_recent: HashMap = Default::default(); // Only keep certifications from keys in our keyring. for sig in sigs { // We only consider certifications with an Issuer // Fingerprint subpacket. This automatically strips // very old certifications. for issuer in sig.issuer_fingerprints() { // Do we have an issuer? if let Some(_) = ks.by_fp(&issuer).nth(0) { // Do we already have a sig from this issuer? match most_recent.entry(issuer.clone()) { Entry::Occupied(mut e) => { // Take the newest one. if sig.signature_creation_time() > e.get().signature_creation_time() { *e.get_mut() = sig.clone(); } } Entry::Vacant(v) => { v.insert(sig.clone()); } } } } } let sigs: Vec = most_recent.into_iter() .map(|(_, sig)| sig.clone()) .collect(); if sigs.len() > THIRD_PARTY_SIG_THRESHOLD { // Still too many. if userid.is_none() { // No one really cares about user attributes, so // just drop any third party signatures. t!("After pruning from user attribute, \ still have {} certifications, dropping all.", sigs.len()); Vec::with_capacity(0) } else { // This could happen if the certifications are // forged. We could try and validate them. But, // we may not have access to the certificates from // this thread. t!("{:?}: Keeping {} certifications", userid.unwrap(), sigs.len()); sigs } } else { t!("{:?}: Keeping {} certifications", userid .map(|uid| String::from_utf8_lossy(uid.value())) .unwrap_or("User Attribute".into()), sigs.len()); sigs } } // We exclude third party signatures and revocations on // components except for UserIDs and User Attributes where we // filter them. let ks = self.ks.read().unwrap(); // Primary key and related signatures. let mut p: Vec = Vec::with_capacity(64); let pk = vc.primary_key(); p.push(pk.key().clone().into()); for s in pk.self_signatures() { p.push(s.clone().into()) } // for s in pk.certifications() { p.push(s.clone().into()) } for s in pk.self_revocations() { p.push(s.clone().into()) } for s in pk.other_revocations() { p.push(s.clone().into()) } // UserIDs and related signatures. for ua in vc.userids() { p.push(ua.userid().clone().into()); for s in ua.self_signatures() { p.push(s.clone().into()) } for s in filter(Some(ua.userid()), &ks, ua.certifications()) { p.push(s.clone().into()) } for s in ua.self_revocations() { p.push(s.clone().into()) } //for s in ua.other_revocations() { p.push(s.clone().into()) } } // UserAttributes and related signatures. for ua in vc.user_attributes() { p.push(ua.user_attribute().clone().into()); for s in ua.self_signatures() { p.push(s.clone().into()) } for s in filter(None, &ks, ua.certifications()) { p.push(s.clone().into()) } for s in ua.self_revocations() { p.push(s.clone().into()) } //for s in ua.other_revocations() { p.push(s.clone().into()) } } // Subkeys and related signatures. for ka in vc.keys().subkeys() { p.push(ka.key().clone().into()); for s in ka.self_signatures() { p.push(s.clone().into()) } //for s in ka.certifications() { p.push(s.clone().into()) } for s in ka.self_revocations() { p.push(s.clone().into()) } //for s in ka.other_revocations() { p.push(s.clone().into()) } } // We exclude unknown components. //for ua in vc.unknowns() { // p.push(ua.unknown().clone().into()); // for s in ua.self_signatures() { p.push(s.clone().into()) } // for s in ua.certifications() { p.push(s.clone().into()) } // for s in ua.self_revocations() { p.push(s.clone().into()) } // for s in ua.other_revocations() { p.push(s.clone().into()) } //} // We exclude bad signatures. //for s in cert.bad_signatures() { p.push(s.clone().into()) } // Finally, parse into Cert. Some(Cert::from_packets(p.into_iter()).expect("still valid")) } } #[derive(Clone)] enum Query { Handle(KeyHandle), Address(String), } impl fmt::Display for Query { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Query::Handle(h) => write!(f, "{}", h), Query::Address(a) => write!(f, "{}", a), } } } #[derive(Clone)] enum Method { KeyServer(String), WKD, #[allow(dead_code)] DANE, } impl fmt::Display for Method { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Method::KeyServer(url) => write!(f, "{}", url), Method::WKD => write!(f, "WKD"), Method::DANE => write!(f, "DANE"), } } } struct Response { query: Query, method: Method, results: openpgp::Result>>, } sequoia-octopus-librnp-1.11.1/src/recombine.rs000064400000000000000000000112041046102023000174270ustar 00000000000000//! Re-combines encryption and signing into one operation. //! //! TB is broken. It is using a [construct], which is [known to not work] //! for 20 years. We can fix this. When we encrypt a message, we can //! detect whether we just generated the signed part. If so, we //! transparently fix it. //! //! [construct]: https://bugzilla.mozilla.org/show_bug.cgi?id=1688863 //! [known to not work]: https://theworld.com/~dtd/sign_encrypt/sign_encrypt7.html //! //! Interestingly, [Section 6.2 of RFC3156] explicitly allows agents //! decrypting a combined message to rewrite it as a multipart/signed //! message on the fly. We're doing the converse. //! //! [Section 6.2 of RFC3156]: https://tools.ietf.org/html/rfc3156#section-6.2 use std::{ io::{ BufRead, BufReader, }, }; use sequoia_openpgp as openpgp; use openpgp::{ KeyHandle, Packet, packet::{ Signature, }, parse::{ Parse, PacketParserResult, PacketParser, }, }; use crate::{ RnpInput, }; #[derive(Default)] pub struct PlaintextCache { cache: Option<(RnpInput, Vec)>, } impl PlaintextCache { /// Stashes the (plaintext, signatures) pair. /// /// This is a best effort mechanism. If anything goes wrong, we /// don't cache. pub fn stash(&mut self, input: &RnpInput, signatures: &[u8]) { fn doit(input: &RnpInput, signatures: &[u8]) -> openpgp::Result<(RnpInput, Vec)> { let mut sigs = Vec::new(); let mut ppr = PacketParser::from_bytes(signatures)?; while let PacketParserResult::Some(pp) = ppr { let (packet, next_ppr) = pp.recurse()?; ppr = next_ppr; if let Packet::Signature(s) = packet { sigs.push(s); } else { return Err(anyhow::anyhow!("expected signature")); } } Ok((input.try_clone()?.to_owned(), sigs)) } self.cache = doit(input, signatures).ok(); } /// Gets the plaintext back given a MIME document containing a /// multipart/signed message. /// /// Extracts the signatures from the multipart/signed message, /// compares it to the cached signatures, and if they match, /// returns the original plaintext and a list of signing keys as /// identified by issuer. pub fn get(&mut self, signed: &RnpInput) -> openpgp::Result)>> { // Take the cache, if any. let cache = if let Some(c) = self.cache.take() { c } else { // Shortcut. return Ok(None); }; // First, try to clone the input. We cannot modify the input // in case the caller needs it to encrypt the message. let signed = signed.try_clone()?; // Now, extract the last signature. We do this without proper // MIME parsing to avoid depending on a MIME parsing library. // // (As the time of this writing, none of the available options // are particularly stable, and none supports incremental // parsing, i.e. only parsing the top level without descending // into the tree.) let mut in_sig = false; let mut acc = Vec::new(); for line in BufReader::new(signed).lines() { if let Ok(line) = line { if line == "-----BEGIN PGP SIGNATURE-----" { acc.clear(); acc.push(line); in_sig = true; } else if line == "-----END PGP SIGNATURE-----" { acc.push(line); in_sig = false; } else if in_sig { acc.push(line); } } } // Parse the accumulator into signatures. let signature = acc.join("\n"); let mut sigs = Vec::new(); let mut issuers = Vec::new(); let mut ppr = PacketParser::from_bytes(&signature)?; while let PacketParserResult::Some(pp) = ppr { let (packet, next_ppr) = pp.recurse()?; ppr = next_ppr; if let Packet::Signature(s) = packet { if let Some(issuer) = s.get_issuers().drain(..).nth(0) { issuers.push(issuer); } sigs.push(s); } else { return Err(anyhow::anyhow!("expected signature")); } } if sigs == cache.1 { // Hit. Ok(Some((cache.0, issuers))) } else { // Miss. Ok(None) } } } sequoia-octopus-librnp-1.11.1/src/security_rules.rs000064400000000000000000000255321046102023000205560ustar 00000000000000//! This is RNP's version of the Policy. //! //! XXX: Currently, we map rnp_get_security_rule and //! rnp_add_security_rule directly to our StandardPolicy. This //! mapping is somewhat lossy, and if we ever implement //! rnp_remove_security_rule, we need to explicitly track the rules //! and obey RNP_SECURITY_OVERRIDE. use std::{ convert::TryFrom, time::{Duration, UNIX_EPOCH}, }; use libc::{ c_char, }; use sequoia_openpgp as openpgp; use openpgp::{ policy::{AsymmetricAlgorithm, HashAlgoSecurity}, types::{ HashAlgorithm, SymmetricAlgorithm, }, }; use crate::{ RnpResult, RnpContext, conversions::{AsymmetricAlgorithmExt, FromRnpId}, error::*, flags::*, }; pub const RNP_FEATURE_SYMM_ALG: &'static str = "symmetric algorithm"; pub const RNP_FEATURE_AEAD_ALG: &'static str = "aead algorithm"; pub const RNP_FEATURE_PROT_MODE: &'static str = "protection mode"; pub const RNP_FEATURE_PK_ALG: &'static str = "public key algorithm"; pub const RNP_FEATURE_HASH_ALG: &'static str = "hash algorithm"; pub const RNP_FEATURE_COMP_ALG: &'static str = "compression algorithm"; pub const RNP_FEATURE_CURVE: &'static str = "elliptic curve"; #[no_mangle] pub unsafe extern "C" fn rnp_get_security_rule(ctx: *const RnpContext, typ: *const c_char, name: *const c_char, time: u64, flags_inout: *mut RnpSecurityFlags, from_out: *mut u64, level_out: *mut RnpSecurityLevel) -> RnpResult { rnp_function!(rnp_get_security_rule, crate::TRACE); let ctx = assert_ptr_ref!(ctx); let typ = assert_str!(typ); let name = assert_str!(name); arg!(time); arg!(flags_inout); arg!(from_out); let level_out = assert_ptr_mut!(level_out); let query_flags = if flags_inout.is_null() { None } else { Some(*flags_inout) }; // "If there is no matching rule, return defaults." let mut flags = 0; let mut from = 0; let mut level = RNP_SECURITY_DEFAULT; let time = UNIX_EPOCH + Duration::new(time, 0); t!("typ: {}, name: {}, time: {:?}, query flags: {:?}", typ, name, time, query_flags); match typ { RNP_FEATURE_PK_ALG => { match AsymmetricAlgorithm::from_rnp_id(name) { Ok(algo) => if let Some(cutoff) = ctx.policy().asymmetric_algo_cutoff(algo) { // RNP only returns rules that apply to `time`. if time > cutoff { from = cutoff.duration_since(UNIX_EPOCH) .expect("cutoff time is representable as epoch") .as_secs(); level = RNP_SECURITY_INSECURE; } }, Err(_) => { // We didn't recognize the algorithm. // Conservatively consider it prohibited. level = RNP_SECURITY_PROHIBITED; }, } }, RNP_FEATURE_SYMM_ALG => { match SymmetricAlgorithm::from_rnp_id(name) { Ok(algo) => if let Some(cutoff) = ctx.policy().symmetric_algo_cutoff(algo) { // RNP only returns rules that apply to `time`. if time > cutoff { from = cutoff.duration_since(UNIX_EPOCH) .expect("cutoff time is representable as epoch") .as_secs(); level = RNP_SECURITY_INSECURE; } }, Err(_) => { // We didn't recognize the algorithm. // Conservatively consider it prohibited. level = RNP_SECURITY_PROHIBITED; }, } }, RNP_FEATURE_HASH_ALG => { let verify_data = query_flags.map(|f| { f & RNP_SECURITY_VERIFY_KEY == 0 && f & RNP_SECURITY_VERIFY_DATA > 0 }).unwrap_or(false); let verify_key = query_flags.map(|f| { f & RNP_SECURITY_VERIFY_KEY > 0 && f & RNP_SECURITY_VERIFY_DATA == 0 }).unwrap_or(false); let hash_security = if verify_key { HashAlgoSecurity::SecondPreImageResistance } else { HashAlgoSecurity::CollisionResistance }; match HashAlgorithm::from_rnp_id(name) { Ok(hash) => if let Some(cutoff) = ctx.policy().hash_cutoff(hash, hash_security) { // RNP only returns rules that apply to `time`. if time > cutoff { from = cutoff.duration_since(UNIX_EPOCH) .expect("cutoff time is representable as epoch") .as_secs(); level = RNP_SECURITY_INSECURE; } flags |= if verify_data { RNP_SECURITY_VERIFY_DATA } else { 0 } | if verify_key { RNP_SECURITY_VERIFY_KEY } else { 0 }; }, Err(_) => { // We didn't recognize the algorithm. // Conservatively consider it prohibited. level = RNP_SECURITY_PROHIBITED; }, } }, // XXX: Implement more features if RNP does. _ => (), } t!("=> flags: {}, from: {}, level: {}", flags, from, level); if ! flags_inout.is_null() { *flags_inout = flags; } if ! from_out.is_null() { *from_out = from; } *level_out = level; rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_add_security_rule(ctx: *mut RnpContext, typ: *const c_char, name: *const c_char, flags: RnpSecurityFlags, time: u64, level: RnpSecurityLevel) -> RnpResult { rnp_function!(rnp_add_security_rule, crate::TRACE); let ctx = assert_ptr_mut!(ctx); let typ = assert_str!(typ); let name = assert_str!(name); arg!(flags); arg!(time); arg!(level); let time = UNIX_EPOCH + Duration::new(time, 0); t!("typ: {}, name: {}, flags: {:?}, time: {:?}, level: {:?}", typ, name, flags, time, level); match typ { RNP_FEATURE_PK_ALG => { match AsymmetricAlgorithm::all_from_rnp_id(name) { Ok(algos) => for algo in algos { if level == RNP_SECURITY_DEFAULT { // XXX: We don't model the time from when the // algorithm should be allowed, unlike RNP. ctx.policy.write().unwrap().accept_asymmetric_algo(algo); } else { // XXX: We lose the distinction between // RNP_SECURITY_INSECURE and // RNP_SECURITY_PROHIBITED here, but currently // RNP treats them the same. ctx.policy.write().unwrap().reject_asymmetric_algo_at(algo, time); } }, Err(_) => { // We didn't recognize the algorithm. // There is nothing we can do, really. }, } }, RNP_FEATURE_SYMM_ALG => { match SymmetricAlgorithm::from_rnp_id(name) { Ok(algo) => { if level == RNP_SECURITY_DEFAULT { // XXX: We don't model the time from when the // algorithm should be allowed, unlike RNP. ctx.policy.write().unwrap().accept_symmetric_algo(algo); } else { // XXX: We lose the distinction between // RNP_SECURITY_INSECURE and // RNP_SECURITY_PROHIBITED here, but currently // RNP treats them the same. ctx.policy.write().unwrap().reject_symmetric_algo_at(algo, time); } }, Err(_) => { // We didn't recognize the algorithm. // There is nothing we can do, really. }, } }, RNP_FEATURE_HASH_ALG => { // If the rule should *only* apply to keys, use the weaker // property. let hash_security = if flags & RNP_SECURITY_VERIFY_KEY > 0 && flags & RNP_SECURITY_VERIFY_DATA == 0 { HashAlgoSecurity::SecondPreImageResistance } else { HashAlgoSecurity::CollisionResistance }; match HashAlgorithm::from_rnp_id(name) { Ok(algo) => { if level == RNP_SECURITY_DEFAULT { // XXX: We don't model the time from when the // algorithm should be allowed, unlike RNP. // XXX: We don't have a accept_hash_property, // so we (ab)use reject_hash_property_at with // a time later than what is representable in // OpenPGP, which the StandardPolicy will // interpret as being goodlisted. // // See https://gitlab.com/sequoia-pgp/sequoia/-/issues/938. let the_future = UNIX_EPOCH.checked_add(Duration::new( (2161 - 1970) * 365 * 24 * 60 * 60, 0)) .expect("the year 2161 is representable"); assert!(openpgp::types::Timestamp::try_from(the_future) .is_err()); ctx.policy.write().unwrap().reject_hash_property_at(algo, hash_security, the_future); } else { // XXX: We lose the distinction between // RNP_SECURITY_INSECURE and // RNP_SECURITY_PROHIBITED here, but currently // RNP treats them the same. ctx.policy.write().unwrap().reject_hash_property_at(algo, hash_security, time); } }, Err(_) => { // We didn't recognize the algorithm. // There is nothing we can do, really. }, } }, // XXX: Implement more features if RNP does. _ => (), } rnp_success!() } sequoia-octopus-librnp-1.11.1/src/signature.rs000064400000000000000000000153401046102023000174720ustar 00000000000000use std::{ time::{ UNIX_EPOCH, }, }; use libc::{ c_char, }; use sequoia_openpgp as openpgp; use openpgp::{ KeyID, packet::{ Signature, }, }; use crate::{ RnpResult, RnpContext, RnpKey, str_to_rnp_buffer, conversions::ToRnpId, error::*, }; pub struct RnpSignature { ctx: *const RnpContext, sig: Signature, // Whether the signature is known to be good. If None, then we // don't know. valid: Option, } impl std::ops::Deref for RnpSignature { type Target = Signature; fn deref(&self) -> &Self::Target { &self.sig } } impl RnpSignature { pub fn new(ctx: *const RnpContext, sig: Signature, valid: Option) -> Self { Self { ctx, sig, valid, } } } #[no_mangle] pub unsafe extern "C" fn rnp_signature_handle_destroy(sig: *mut RnpSignature) -> RnpResult { rnp_function!(rnp_signature_handle_destroy, crate::TRACE); arg!(sig); if ! sig.is_null() { drop(Box::from_raw(sig)); } rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_signature_get_hash_alg(sig: *const RnpSignature, hash_alg: *mut *mut c_char) -> RnpResult { rnp_function!(rnp_signature_get_keyid, crate::TRACE); let sig = assert_ptr_ref!(sig); let hash_alg = assert_ptr_mut!(hash_alg); *hash_alg = str_to_rnp_buffer(sig.hash_algo().to_rnp_id()); rnp_success!() } // XXX: This interface is terrible. It retrieves issuer information // from the signature in an unsafe way. #[no_mangle] pub unsafe extern "C" fn rnp_signature_get_keyid(sig: *const RnpSignature, keyid: *mut *mut c_char) -> RnpResult { rnp_function!(rnp_signature_get_keyid, crate::TRACE); let sig = assert_ptr_ref!(sig); let keyid = assert_ptr_mut!(keyid); *keyid = if let Some(issuer) = (*sig).get_issuers().get(0) { str_to_rnp_buffer(format!("{:X}", KeyID::from(issuer))) } else { std::ptr::null_mut() }; rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_signature_get_creation(sig: *const RnpSignature, creation: *mut u32) -> RnpResult { rnp_function!(rnp_signature_get_creation, crate::TRACE); let sig = assert_ptr_ref!(sig); let creation = assert_ptr_mut!(creation); *creation = (*sig).signature_creation_time() .map(|t| t.duration_since(UNIX_EPOCH) .expect("creation time is representable as epoch") .as_secs() as u32) .unwrap_or(0); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_signature_get_signer(sig: *const RnpSignature, key: *mut *mut RnpKey) -> RnpResult { rnp_function!(rnp_signature_get_signer, crate::TRACE); let sig = assert_ptr_ref!(sig); let key = assert_ptr_mut!(key); for issuer in sig.get_issuers() { if let Some(cert) = (*sig.ctx).cert(&issuer.clone().into()) { let k = cert.keys().key_handle(issuer).nth(0) .expect("must be there").key().clone() .parts_into_unspecified(); *key = Box::into_raw(Box::new( RnpKey::new((*sig).ctx as *mut _, k, &cert))); rnp_success!(); } } *key = std::ptr::null_mut(); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_signature_is_valid(sig: *const RnpSignature, flags: u32) -> RnpResult { rnp_function!(rnp_signature_is_valid, crate::TRACE); let sig = assert_ptr_ref!(sig); arg!(flags); // According to the rnp documentation, flags must currently be // zero. if flags != 0 { rnp_return_status!(RNP_ERROR_BAD_PARAMETERS); } if let Some(valid) = sig.valid { if valid { // The signature could have expired in the meantime. (In // fact, the key that created the signature could have // expired, but we ignore that :/.) if sig.signature_alive(None, None).is_ok() { rnp_return_status!(RNP_SUCCESS); } else { rnp_return_status!(RNP_ERROR_SIGNATURE_EXPIRED); } } else { rnp_return_status!(RNP_ERROR_SIGNATURE_INVALID); } } // XXX: We need to check the signature, but we don't have enough // context. This only effects third-party certifications and // third-party revocations (right now, all other places where an // RnpSignature is created include set the signature's validity). // As Thunderbird doesn't appear to care about the validity of // these signatures, don't complicate the implementation... yet. rnp_return_status!(RNP_ERROR_SIGNATURE_INVALID) } #[no_mangle] pub unsafe extern "C" fn rnp_signature_get_features(sig: *const RnpSignature, features: *mut u32) -> RnpResult { rnp_function!(rnp_signature_get_features, crate::TRACE); let sig = assert_ptr_ref!(sig); let features = assert_ptr_mut!(features); // Note: as of now, RNP only considers the first 8 bits, which is // hopefully enough for the time being, but we consider the first // 32 bits, in the hope that we'll never use as many. *features = sig.sig.features() .map(|f| 0 | (*f.as_bitfield().as_ref().get(0).unwrap_or(&0) as u32) << 0 | (*f.as_bitfield().as_ref().get(1).unwrap_or(&0) as u32) << 8 | (*f.as_bitfield().as_ref().get(2).unwrap_or(&0) as u32) << 16 | (*f.as_bitfield().as_ref().get(3).unwrap_or(&0) as u32) << 24) .unwrap_or(0); rnp_success!() } /// Replacement for ComponentAmalgamation::signatures. /// /// This started out as a replacement for a function that was not /// available in sequoia-openpgp 1.0. However, we now mark /// self-signatures as valid, a functionality that is not available in /// the stock version of the function. pub fn ca_signatures<'a>(sigs: impl Iterator) -> impl Iterator)> { use openpgp::packet::signature::subpacket::SubpacketTag; sigs.map(|sig| { // If Sequoia verified a signature, it will set the // authenticated flag of subpackets to true. Thus, we know a // signature is "valid" iff the signature creation time has // been authenticated. let valid = sig.subpacket(SubpacketTag::SignatureCreationTime) .map(|sct| if sct.authenticated() { Some(true) } else { None }) .unwrap_or(None); (sig, valid) }) } sequoia-octopus-librnp-1.11.1/src/stubs.rs000064400000000000000000000015201046102023000166240ustar 00000000000000//! Contains stubbed out functions. //! //! This module contains functions that are bound in RNPlib.jsm but //! are not actually used. // These are bound in RNPlib.jsm but not actually used. macro_rules! unused { ($s: ident) => { /// This function is a stub. /// /// This function is bound in RNPlib.jsm but is not actually used. #[no_mangle] pub extern "C" fn $s() -> crate::RnpResult { global_warn!( "previously unused function is used: {}", stringify!($s)); crate::error::RNP_ERROR_NOT_IMPLEMENTED.quiet_epilogue() } }; } unused!(rnp_guess_contents); unused!(rnp_decrypt); unused!(rnp_symenc_get_aead_alg); unused!(rnp_symenc_get_cipher); unused!(rnp_symenc_get_hash_alg); unused!(rnp_symenc_get_s2k_iterations); unused!(rnp_symenc_get_s2k_type); sequoia-octopus-librnp-1.11.1/src/tbprofile.rs000064400000000000000000000403341046102023000174600ustar 00000000000000use std::path::PathBuf; use std::fs::File; use std::io::Read; use std::ops::Deref; use std::sync::Mutex; use std::sync::MutexGuard; use std::sync::OnceLock; use std::thread; use std::thread::ThreadId; use std::time::{SystemTime, Duration, UNIX_EPOCH}; use sequoia_openpgp as openpgp; /// Controls tracing in this module. const TRACE: bool = crate::TRACE; // https://docs.rs/app_dirs/1.2.1/app_dirs/ const PROFILES_INI: &[&'static str] = &[ // Linux, BSD, etc.: prefix with $HOME. #[cfg(not(any(windows, target_os = "macos")))] ".thunderbird", #[cfg(not(any(windows, target_os = "macos")))] ".mozilla-thunderbird", // macOS: prefix with $HOME. #[cfg(target_os = "macos")] "Library/Thunderbird/Profiles", #[cfg(target_os = "macos")] "Library/Application Support/Thunderbird/Profiles", // Windows: prefix with %APPDATA%. #[cfg(windows)] "Thunderbird", #[cfg(windows)] "Thunderbird\\Profiles", ]; #[cfg(windows)] const LOCK_FILE: &'static str = "parent.lock"; #[cfg(not(windows))] const LOCK_FILE: &'static str = "lock"; /// Like `std::env::args`, but more robust. /// /// Notably, Thunderbird changes `argv`, removing ``['-profile', /// 'XXX']` from the arguments. Curiously, `/proc/self/cmdline` is /// not affected, so we prefer that one if we can get at it. fn robust_args() -> Vec { if let Ok(mut v) = std::fs::read("/proc/self/cmdline") { // Trim the final terminating zero. if v.last().map(|a| *a == 0).unwrap_or(false) { v.pop(); } v.split(|d| *d == 0) .map(|a| String::from_utf8_lossy(a).to_string()) .collect() } else { std::env::args().collect() } } /// ThreadId is the thread initializing the TBProfile, if any. /// /// The locking protocol is: /// /// - Profile lock, then Thread lock /// /// It is okay to take the thread lock without first taking the /// profile lock, but if you hold the thread lock you MUST NOT /// block on the profile lock. fn tb_profile() -> &'static (Mutex>, Mutex>) { static TBPROFILE: OnceLock<(Mutex>, Mutex>)> = OnceLock::new(); TBPROFILE.get_or_init(|| (Mutex::new(None), Mutex::new(None))) } pub struct TBProfile { path: Option, } // Some information about profiles.ini: // // https://www.thunderbird-mail.de/lexicon/entry/123-profiles-ini/ impl TBProfile { /// Return the profile's path. /// /// If we couldn't find the profile, returns None. pub fn path() -> Option { // This function needs to be reentrant! TBProfile::find calls // other functions that may end up calling this function (in // particular, the tracing functionality). // Try the happy path. if let Ok(profile_guard) = tb_profile().1.try_lock() { // We got the profile lock! if let Some(profile) = profile_guard.deref() { // We have a profile! return profile.path.clone(); } else { // We haven't tried to initialize the profile yet. } } // Either the profile lock is contended or we need to // initialize profile. let thread_guard = tb_profile().0.lock().unwrap(); if thread_guard.deref() == &Some(thread::current().id()) { // We're in the middle of doing the initialization! return None; } drop(thread_guard); // We are not initializing the profile. We can block on the // profile lock. let profile_guard = tb_profile().1.lock().unwrap(); // Another thread might have initialized the profile in the // meantime. So, check again. if let Some(profile) = profile_guard.deref() { // We have a profile! return profile.path.clone(); } else { // We haven't tried to initialize the profile yet. } // We have the profile lock, and no one has tried to // initialize the profile yet. It is up to us to do it. // Register ourself. let mut thread_guard = tb_profile().0.lock().unwrap(); assert!(thread_guard.deref().is_none()); *thread_guard = Some(thread::current().id()); drop(thread_guard); // Timelime: // // - We got the profile lock. // - We got the thread lock. // - We registered ourselves as doing the initialization. // - We dropped the thread lock. // // If we end up calling this function again, then we'll check // the thread lock and realize that we are doing the // initialization and return None. // // Another thread may block on the profile lock, but it won't // have the thread lock. // // Deadlock free(tm). let r = Self::find(profile_guard); // Initialization is complete. Reset thread_guard to None. let mut thread_guard = tb_profile().0.lock().unwrap(); *thread_guard = None; if let Ok(r) = r { r } else { None } } /// Returns whether or not we find the encrypted passphrase file /// for locking secret keys managed by Thunderbird. pub fn has_encrypted_passphrase_txt() -> bool { const NAME: &str = "encrypted-openpgp-passphrase.txt"; Self::path().map(|p| p.join(NAME).exists()).unwrap_or(false) } fn find(mut tbprofile: MutexGuard>) -> openpgp::Result> { rnp_function!(TBProfile::path, TRACE); assert!(tbprofile.is_none()); let prefix = if cfg!(windows) { PathBuf::from(std::env::var("APPDATA")?) } else { #[allow(deprecated)] std::env::home_dir() .ok_or(anyhow::anyhow!("Failed to find home directory"))? }; let mut base: PathBuf = PathBuf::new(); base.push(&prefix); let mut profile_args = Vec::new(); let args = robust_args(); t!("Considering arguments: {}", args.iter() .map(|a| format!("{:?}", a)) .collect::>() .join(" ")); for args in args.windows(2) { if args[0] == "-profile" { t!("Found -profile argument: {:?}", args[1]); let path = PathBuf::from(&args[1]); if path.is_relative() { if let Ok(cwd) = std::env::current_dir() { let mut abspath = cwd; abspath.push(path); t!("Adding {} to search", abspath.display()); profile_args.push(abspath); } } else { t!("Adding {} to search", path.display()); profile_args.push(path); } } } let dirs: Vec<_> = PROFILES_INI.iter().map(PathBuf::from) .flat_map(|suffix| -> Box> { let mut profiles_ini = base.clone(); profiles_ini.push(&suffix); profiles_ini.push("profiles.ini"); t!("Considering {:?}", profiles_ini); let mut f = match File::open(&profiles_ini) { Ok(f) => f, Err(err) => { if err.kind() != std::io::ErrorKind::NotFound { warn!("Opening {:?}: {}", profiles_ini, err); } else { t!("Opening {:?}: {}", profiles_ini, err); } return Box::new(std::iter::empty()); } }; let mut content = String::new(); if let Err(err) = f.read_to_string(&mut content) { warn!("Reading {:?}: {}", profiles_ini, err); return Box::new(std::iter::empty()); } // Use Ini::new, all sections are keys are lowercased. let mut parsed = configparser::ini::Ini::new(); if let Err(err) = parsed.read(content) { warn!("Parsing {:?}: {}", profiles_ini, err); return Box::new(std::iter::empty()); } let base = base.clone(); let prefix = prefix.clone(); Box::new(parsed.sections() .into_iter() .filter_map(move |section| { if section.starts_with("profile") { parsed.get(§ion, "path") } else { None } }) .map(move |path| { let mut dir = PathBuf::new(); dir.push(&path); t!("Profile directory (raw): {:?}", dir); if dir.is_relative() { let mut abs = prefix.clone(); abs.push(&base); abs.push(&suffix); abs.push(dir); dir = abs; } t!("Profile directory (absolute path): {:?}", dir); dir })) }) .chain(profile_args) .filter(|dir| { // Make sure it exists. if ! dir.is_dir() { t!("{:?} does not exist", dir); false } else { true } }) .filter_map(|dir| { // And the lock file. let mut lock = dir.clone(); lock.push(LOCK_FILE); // On Linux, it is created as a symlink whose // value is of the form: 127.0.1.1:+12184. We // want the pid. We grab all of the digits at the // end and parse that as a number. #[cfg(not(windows))] let pid: Option = { let lock_value = lock .read_link() .map_err(|err| { t!("Opening lock file: {}", err); err }) .ok()?; t!("lock file: {:?} -> {:?}", lock, lock_value); let pid: String = lock_value .to_string_lossy() .chars() .rev() .take_while(|c| c.is_digit(10)) .collect(); let pid: String = pid.chars().rev().collect(); // Don't fail if we can't extract a pid. I wasn't // able to find documentation on the actual // format. let pid = pid.parse::().ok(); t!("pid: {:?}", pid); pid }; #[cfg(windows)] let pid: Option = None; // On Linux, a symlink is created. #[cfg(not(windows))] let ts: Option = { lock.symlink_metadata() .map_err(|err| { t!("Opening lock file: {}", err); err }) .ok()? .modified().ok() }; // On Windows, the lockfile is touched. #[cfg(windows)] let ts: Option = { lock.metadata().ok()?.modified().ok() }; Some((dir, pid, ts)) }) .collect::, Option)>>(); // We have zero or more candidate directories. *tbprofile = Some(TBProfile::select_profile(dirs)); Ok(tbprofile.as_ref().unwrap().path.clone()) } // This is a separate function to make it easier to test. fn select_profile(mut dirs: Vec<(PathBuf, Option, Option)>) -> Self { rnp_function!(TBProfile::select_profile, TRACE); // XXX: What's the format of the lock file on windows? #[cfg(windows)] let pid = isize::MAX; #[cfg(not(windows))] let pid = unsafe { libc::getpid() as isize }; t!("my pid is: {:?}", pid); let now = SystemTime::now(); let long_ago = Duration::new(10 * 365 * 24 * 60 * 60, 0); dirs.sort_by_key(|a| { // Prefer the lock file that has a matching pid. Then // fallback to the time since the lock file's creation // time. Finally, break ties using a lexographical sort. // // Recall None < Some(_). let lock_pid = a.1; let creation_time = a.2.unwrap_or(UNIX_EPOCH); let path = a.0.clone(); ( // PID. if lock_pid.is_some() && lock_pid.unwrap() == pid { 0 } else if lock_pid.is_some() { // Could extract a pid. 1 } else { // Got nothing. 2 }, // Absolute time from now. if creation_time > now { // If the timestamp is in the future, penalize it. (creation_time.duration_since(now)) .unwrap_or(long_ago.clone()) + Duration::new(60 * 60, 0) } else { now.duration_since(creation_time) .unwrap_or(long_ago.clone()) }, // Path. path) }); t!("candidate profile directories: {:?}", dirs); let profile = TBProfile { path: if dirs.is_empty() { None } else { Some(dirs[0].0.clone()) }, }; t!("Using Thunderbird Profile: {:?}", profile.path); profile } } #[cfg(test)] mod tests { use super::*; use std::time::SystemTime; use std::time::Duration; #[test] fn sort() { let now = SystemTime::now(); let tminus1 = now - Duration::new(1, 0); let tminus2 = now - Duration::new(2, 0); #[cfg(windows)] let pid = isize::MAX; #[cfg(not(windows))] let pid = unsafe { libc::getpid() as isize }; let other_pid = pid - 1; // Even though a's lock file is old, we prefer it, because the // pid matches ours. assert_eq!( TBProfile::select_profile(vec![ ("a".into(), Some(pid), Some(tminus2)), ("b".into(), Some(other_pid), Some(tminus1)), ]).path.as_ref().map(|p| p.to_string_lossy().into_owned()), Some("a".into())); // Neither pid matches. But "b"'s lock file is more recent. assert_eq!( TBProfile::select_profile(vec![ ("a".into(), Some(other_pid), Some(tminus2)), ("b".into(), Some(other_pid), Some(tminus1)), ]).path.as_ref().map(|p| p.to_string_lossy().into_owned()), Some("b".into())); // No pids. "b"'s lock file is more recent. assert_eq!( TBProfile::select_profile(vec![ ("a".into(), None, Some(tminus2)), ("b".into(), None, Some(tminus1)), ]).path.as_ref().map(|p| p.to_string_lossy().into_owned()), Some("b".into())); // We prefer a valid formed lock file (with a pid) to one // where we can't extract a pid. assert_eq!( TBProfile::select_profile(vec![ ("a".into(), Some(other_pid), Some(tminus2)), ("b".into(), None, Some(tminus1)), ]).path.as_ref().map(|p| p.to_string_lossy().into_owned()), Some("a".into())); } } sequoia-octopus-librnp-1.11.1/src/userid.rs000064400000000000000000000142171046102023000167660ustar 00000000000000use std::fmt; use libc::{ size_t, }; use sequoia_openpgp as openpgp; use openpgp::{ cert::{ Cert, amalgamation::{ ValidAmalgamation, ValidateAmalgamation, }, }, packet::{ UserID, }, }; use crate::{ RnpResult, RnpContext, RnpSignature, error::*, signature::ca_signatures, }; pub struct RnpUserID { ctx: *const RnpContext, uid: UserID, cert: Cert, nth_uid: usize, } impl fmt::Debug for RnpUserID { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_struct("RnpUserID") .field("ctx", &self.ctx) .field("uid", &String::from_utf8_lossy(self.uid.value())) .field("cert", &self.cert.fingerprint().to_hex()) .field("nth_uid", &self.nth_uid) .finish() } } impl RnpUserID { pub fn new(ctx: *const RnpContext, uid: UserID, cert: Cert) -> Option { cert.userids().position(|u| u.userid() == &uid) .map(|nth_uid| RnpUserID { ctx, uid, cert, nth_uid }) } /// Returns the User ID. pub fn userid(&self) -> &UserID { &self.uid } /// Returns whether the User ID is considered revoked. /// /// A User ID is considered revoked: /// - if it is valid under the standard policy and is revoked /// - it is not valid under the standard policy, but is valid under the /// null policy, and is revoked /// - is not valid under either the standard or null policy. fn revoked<'a>(&'a self) -> bool { let ctx = unsafe { &*self.ctx }; let userid = self.cert.userids().nth(self.nth_uid) .expect("we know it's there"); if let Ok(vu) = userid.clone() .with_policy(&*ctx.policy(), None) .or_else(|_| userid.with_policy(crate::NP, None)) { // The certificate is valid under a policy use openpgp::types::RevocationStatus::*; match vu.revocation_status() { Revoked(_) => true, CouldBe(_) => false, // XXX NotAsFarAsWeKnow => false, } } else { // The certificate is not even valid under the null // policy. Treat it as invalid. true } } } #[no_mangle] pub unsafe extern "C" fn rnp_uid_handle_destroy(uid: *mut RnpUserID) -> RnpResult { rnp_function!(rnp_uid_handle_destroy, crate::TRACE); arg!(uid); if ! uid.is_null() { drop(Box::from_raw(uid)); } rnp_success!() } // XXX: what signatures to consider? all of them? #[no_mangle] pub unsafe extern "C" fn rnp_uid_get_signature_at(uid: *const RnpUserID, idx: size_t, sig: *mut *mut RnpSignature) -> RnpResult { rnp_function!(rnp_uid_get_signature_at, crate::TRACE); let uid = assert_ptr_ref!(uid); arg!(idx); let sig = assert_ptr_mut!(sig); rnp_return_status!(if let Some((s, valid)) = ca_signatures( uid.cert.userids().nth(uid.nth_uid) .expect("we know it's there") .signatures() ).nth(idx) { *sig = Box::into_raw(Box::new( RnpSignature::new(uid.ctx, s.clone(), valid))); RNP_SUCCESS } else { RNP_ERROR_BAD_PARAMETERS }) } // XXX: what signatures to consider? all of them? #[no_mangle] pub unsafe extern "C" fn rnp_uid_get_signature_count(uid: *const RnpUserID, count: *mut size_t) -> RnpResult { rnp_function!(rnp_uid_get_signature_count, crate::TRACE); let uid = assert_ptr_ref!(uid); let count = assert_ptr_mut!(count); *count = ca_signatures( uid.cert.userids().nth(uid.nth_uid) .expect("we know it's there") .signatures() ).count(); rnp_success!() } // XXX: Revocation is a lot less binary than RNP would like it to be. // How should we handle third-party revocations here? #[no_mangle] pub unsafe extern "C" fn rnp_uid_is_revoked(uid: *const RnpUserID, result: *mut bool) -> RnpResult { rnp_function!(rnp_uid_is_revoked, crate::TRACE); let uid = assert_ptr_ref!(uid); let result = assert_ptr_mut!(result); *result = uid.revoked(); t!("{}:{:?}: is{} revoked", uid.cert.fingerprint(), String::from_utf8_lossy(uid.uid.value()), if *result { "" } else { " not" }); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_uid_is_valid(uid: *const RnpUserID, result: *mut bool) -> RnpResult { rnp_function!(rnp_uid_is_valid, crate::TRACE); let uid = assert_ptr_ref!(uid); let result = assert_ptr_mut!(result); let ctx = &*uid.ctx; let userid = uid.cert.userids().nth(uid.nth_uid) .expect("we know it's there"); // A User ID is safe to display if it is valid under the standard // policy or the null policy. *result = userid.clone() .with_policy(&*ctx.policy(), None) .or_else(|_| userid.with_policy(crate::NP, None)) .is_ok(); t!("{}:{:?}: is{} valid", uid.cert.fingerprint(), String::from_utf8_lossy(uid.uid.value()), if *result { "" } else { " not" }); rnp_success!() } #[no_mangle] pub unsafe extern "C" fn rnp_uid_is_primary(uid: *const RnpUserID, result: *mut bool) -> RnpResult { rnp_function!(rnp_uid_is_primary, crate::TRACE); let uid = assert_ptr_ref!(uid); let ctx = &*uid.ctx; let result = assert_ptr_mut!(result); if let Ok(vc) = uid.cert.with_policy(&*ctx.policy(), None) { if let Ok(ua) = vc.primary_userid() { *result = ua.userid() == &uid.uid } else { *result = false; } } else { // Not valid. *result = false; }; t!("{}:{:?}: is{} primary", uid.cert.fingerprint(), String::from_utf8_lossy(uid.uid.value()), if *result { "" } else { " not" }); rnp_success!() } sequoia-octopus-librnp-1.11.1/src/utils.rs000064400000000000000000000015371046102023000166340ustar 00000000000000use std::{ path::PathBuf, ffi::CStr, }; use libc::{ c_char, }; use crate::{ error::*, }; /// Converts a C string to a &str. /// /// If the given string is not encoded using UTF-8, this will fail /// with RNP_ERROR_BAD_PARAMETERS. pub fn cstr_to_str<'a>(path: *const c_char) -> Result<&'a str> { if let Ok(s) = unsafe { CStr::from_ptr(path) }.to_str() { Ok(s) } else { Err(RNP_ERROR_BAD_PARAMETERS) } } /// Converts a C string to a PathBuf. /// /// XXX: Currently, if the given string is not encoded using UTF-8, /// this will fail with RNP_ERROR_BAD_PARAMETERS. pub fn cstr_to_pathbuf(path: *const c_char) -> Result { if let Ok(p) = unsafe { CStr::from_ptr(path) }.to_str() { Ok(p.into()) } else { Err(RNP_ERROR_BAD_PARAMETERS) } } sequoia-octopus-librnp-1.11.1/src/version.rs000064400000000000000000000055601046102023000171610ustar 00000000000000use std::sync::OnceLock; use libc::{ c_char }; const CLAIMED_RNP_VERSION: [u32; 3] = [0, 17, 1]; const RNP_VERSION_COMPONENT_MASK: u32 = 0x3ff; const RNP_VERSION_MAJOR_SHIFT: u8 = 20; const RNP_VERSION_MINOR_SHIFT: u8 = 10; const RNP_VERSION_PATCH_SHIFT: u8 = 0; // An RNP version number is a 32bit number. 10 bit per version component, the // lowest 10 bit for the patch number, the middle 10 for the minor version, the highest // for the major version number. The most significant 2 bits are undefined. #[no_mangle] pub unsafe extern "C" fn rnp_version_for( major: u32, minor: u32, patch: u32, ) -> u32 { let r = rnp_version_for_internal(major, minor, patch); // Thunderbird invokes this function with the minimal supported // version, then compares the result with the result of // `rnp_version`. If it is lower, an error message is emitted to // the error console, and OpenPGP support is disabled. // // This error message can be hard to discover. Do the same // comparison, and emit an error message to stderr. if rnp_version() < r { log!("sequoia-octopus: Thunderbird requires a newer version of the Octopus."); log!("sequoia-octopus: We support the API {}.{}.{}, but {}.{}.{} is required.", CLAIMED_RNP_VERSION[0], CLAIMED_RNP_VERSION[1], CLAIMED_RNP_VERSION[2], major, minor, patch); log!("sequoia-octopus: Please update, or report this issue to your distribution."); } r } fn rnp_version_for_internal( major: u32, minor: u32, patch: u32, ) -> u32 { (major & RNP_VERSION_COMPONENT_MASK) << RNP_VERSION_MAJOR_SHIFT | (minor & RNP_VERSION_COMPONENT_MASK) << RNP_VERSION_MINOR_SHIFT | ((patch & RNP_VERSION_COMPONENT_MASK) << RNP_VERSION_PATCH_SHIFT) } #[no_mangle] pub unsafe extern "C" fn rnp_version( ) -> u32 { rnp_version_for_internal( CLAIMED_RNP_VERSION[0], CLAIMED_RNP_VERSION[1], CLAIMED_RNP_VERSION[2], ) } #[no_mangle] pub unsafe extern "C" fn rnp_version_string_full() -> *const c_char { static VERSION: OnceLock> = OnceLock::new(); VERSION.get_or_init( || { let mut b = match option_env!("VERGEN_GIT_SHA") { Some(git_sha) => { format!( "{}-{}+sequoia-openpgp-{}", env!("CARGO_PKG_VERSION"), git_sha, sequoia_openpgp::VERSION ).into_bytes() } None => { format!( "{}+sequoia-openpgp-{}", env!("CARGO_PKG_VERSION"), sequoia_openpgp::VERSION ).into_bytes() } }; b.push(0); // Sentinel. b }) .as_ptr() as *const _ } sequoia-octopus-librnp-1.11.1/src/wot.rs000064400000000000000000000541621046102023000163070ustar 00000000000000use std::collections::HashSet; use std::sync::{Arc, RwLock, mpsc}; use std::time::Duration; use std::time::SystemTime; use anyhow::Result; use rusqlite::{ Connection, types::ToSql, OptionalExtension, params, }; use sequoia_openpgp as openpgp; use openpgp::{Cert, Fingerprint}; use openpgp::packet::UserID; use sequoia_openpgp::policy::StandardPolicy; use sequoia_wot::{Root, Roots, store::Store}; use crate::gpg; use crate::gpg::Validity; use crate::keystore::KeystoreData; use crate::tbprofile::TBProfile; /// Controls tracing in this module. const TRACE: bool = crate::TRACE; enum WoTCommand { Recompute, } /// Handle for communication with the background thread. #[derive(Clone)] pub struct WoTWorkerHandle { sender: mpsc::Sender, } impl WoTWorkerHandle { /// Constructs a new handle. fn new() -> (Self, mpsc::Receiver) { let (sender, receiver) = mpsc::channel(); (Self { sender, }, receiver) } /// Wakes the background thread to do a WoT update. /// /// If the background thread is currently doing an update, it /// will, after completing the current update, immediately /// consider doing an update again, unless the result could not /// possibly have changed. pub fn notify(&self) { let _ = self.sender.send(WoTCommand::Recompute); } } pub struct WoT { /// Handle for communication with the background thread. receiver: mpsc::Receiver, ksd: Arc>, policy: Arc>>, conn: rusqlite::Connection, _gpg_ctx: gpg::Ctx, /// Known last_update stamp from the KeyStore. last_keystore_update: usize, /// Time at which our WoT calculation possibly go stale and need /// to be recomputed, provided the KeyStore wasn't modified. next_wot_change_at: Option, } impl WoT { /// Starts the Web-of-Trust background thread. /// /// If we don't have or don't find a Profile directory, or if the /// acceptance database doesn't match our expectations, we will /// emit a warning, ignore the error, and still return a /// `WoTWorkerHandle` which can be notified, but that will have no /// effect. pub fn background_thread_start(ksd: Arc>, policy: Arc>>) -> Result { let (handle, receiver) = WoTWorkerHandle::new(); match Self::new(receiver, ksd, policy) { Ok(mut wot) => { std::thread::Builder::new().name("sq wot".to_string()) .spawn(move || wot.background_thread())?; }, Err(e) => global_warn!("{}", e), } Ok(handle) } // Return a locked WoTData. // // If WoT hasn't been initialized, do it now. fn new(receiver: mpsc::Receiver, ksd: Arc>, policy: Arc>>) -> Result { let gpg_ctx = gpg::Ctx::new()?; let path = TBProfile::path() .ok_or(anyhow::anyhow!("TB Profile directory not found"))?; // The tables that TB creates: // // Note the unfortunate lack of a version number :/. // // CREATE TABLE acceptance_email // (fpr text not null, email text not null, unique(fpr, email)); // CREATE TABLE acceptance_decision // (fpr text not null, decision text not null, unique(fpr)); // // Make sure the DB meets our expectations DB before continuing. let mut conn = Connection::open(path.join("openpgp.sqlite"))?; conn.query_row("SELECT fpr, email FROM acceptance_email LIMIT 1", params![], |_| Ok(())) .optional() .map_err(|err| { let err: anyhow::Error = err.into(); let err = err.context( "openpgp.sqlite: \ Unexpected schema (querying acceptance_email)"); err })?; conn.query_row("SELECT fpr, decision FROM acceptance_decision LIMIT 1", params![], |_| Ok(())) .optional() .map_err(|err| { let err: anyhow::Error = err.into(); let err = err.context( "openpgp.sqlite:\ Unexpected schema (querying acceptance_decision)"); err })?; let tx = conn.transaction()?; // Be extra careful to not override user decisions. // // A given certificate can be in one of three states: // // 1. Not in acceptance_decision and thus not managed by the user // or us. // // 2. In acceptance_decision and not in managed_by_sequoia and // thus managed by the user. // // 3. In acceptance_decision and in managed_by_sequoia and // thus managed by us. // // Using triggers, we cause the managed_by_sequoia entry to be // deleted when the user (via TB) sets the acceptance. // // When updating the acceptance criteria, we don't touch // certificates that fall into category 2. Everything else is // fair game. tx.execute("CREATE TABLE IF NOT EXISTS managed_by_sequoia \ (fpr text not NULL, unique(fpr))", params![])?; tx.execute("CREATE INDEX IF NOT EXISTS managed_by_sequoia_i \ ON managed_by_sequoia (fpr)", params![])?; tx.execute("CREATE TRIGGER IF NOT EXISTS user_update \ UPDATE on acceptance_decision \ FOR EACH ROW \ BEGIN \ DELETE FROM managed_by_sequoia \ WHERE managed_by_sequoia.fpr = NEW.fpr; \ END", params![])?; tx.execute("CREATE TRIGGER IF NOT EXISTS user_insert \ INSERT on acceptance_decision \ FOR EACH ROW \ BEGIN \ DELETE FROM managed_by_sequoia \ WHERE managed_by_sequoia.fpr = NEW.fpr; \ END", params![])?; tx.commit()?; Ok(WoT { receiver, ksd, policy, _gpg_ctx: gpg_ctx, conn, last_keystore_update: 0, next_wot_change_at: None, }) } fn background_thread(&mut self) -> () { rnp_function!(WoT::background_thread, TRACE); loop { let now = SystemTime::now(); let t = self.next_wot_change_at.as_ref() .and_then(|t| t.duration_since(now).ok()) .unwrap_or(Duration::new(0, 0)); t!("Blocking for {:?}", t); match self.receiver.recv_timeout(t) { Ok(WoTCommand::Recompute) | Err(mpsc::RecvTimeoutError::Timeout) => { if let Err(e) = self.update(now) { global_warn!( "error in WoT background thread: {}", e); } }, // Keystore was destroyed. Err(_) => { t!("Keystore disconnected. Time to shutdown."); return; } } } } // Do an update of the WoT validities. // // Carefully update TB's acceptance DB with GPG's validity. fn update(&mut self, now: SystemTime) -> Result<()> { rnp_function!(WoT::update, TRACE); // Read lock the key store, do a scan across the certs, and // release it as soon as possible. let keystore_data = self.ksd.read().unwrap(); // Check the stored key store timestamp. let last_keystore_update = keystore_data.last_update(); if self.last_keystore_update == last_keystore_update && self.next_wot_change_at.map(|t| now < t).unwrap_or(false) { t!("Keystore has not changed, and no known cert component \ or signature could have possibly changed, skipping."); return Ok(()); } // We compute the next time at which a cert component or // signature is valid or invalid. let mut next_wot_change_at: SystemTime = openpgp::types::Timestamp::from(u32::MAX).into(); // We clone the certificates in the key store to be able to drop the // keystore lock as soon as possible. let certs: Vec = keystore_data.iter() .map(|c| { // Considers time `t`. let mut consider = |t| { if t > now && t < next_wot_change_at { next_wot_change_at = t; } }; // All possible certification (sub)keys. for skb in c.keys().filter(|k| k.key().pk_algo().for_signing()) { for sig in skb.signatures() { consider(sig.signature_creation_time().unwrap()); if let Some(t) = sig.signature_expiration_time() { consider(t); } if let Some(t) = sig.key_expiration_time(skb.key()) { consider(t); } } } // All possible certification user IDs. for sig in c.userids().flat_map(|u| u.signatures()) { consider(sig.signature_creation_time().unwrap()); if let Some(t) = sig.signature_expiration_time() { consider(t); } if let Some(t) = sig.key_expiration_time(c.primary_key().key()) { consider(t); } } // All possible certification user attributes. for sig in c.user_attributes().flat_map(|u| u.signatures()) { consider(sig.signature_creation_time().unwrap()); if let Some(t) = sig.signature_expiration_time() { consider(t); } if let Some(t) = sig.key_expiration_time(c.primary_key().key()) { consider(t); } } (*c).clone() }) .collect(); // Drop the lock. drop(keystore_data); t!("Checking for WoT updates..."); t!("Keystore size: {}", certs.len()); let reference_time = SystemTime::now(); // GnuPG ownertrust "Ultimate" let mut trust_roots: Vec = Vec::new(); // Possible roots: GnuPG ownertrust "Fully" and "Marginal" let mut possible_roots: Vec = Vec::new(); // Get trust roots from GnuPG. let ownertrust = gpg::export_ownertrust()?; for (fpr, ownertrust) in ownertrust { match ownertrust { gpg::OwnerTrust::Ultimate => { trust_roots.push( Root::new(fpr, sequoia_wot::FULLY_TRUSTED)); } gpg::OwnerTrust::Fully => { possible_roots.push( Root::new(fpr, sequoia_wot::FULLY_TRUSTED)); } gpg::OwnerTrust::Marginal => { possible_roots.push( Root::new(fpr, sequoia_wot::PARTIALLY_TRUSTED)); } _ => (), } } let roots = Roots::new(trust_roots.clone()); t!("Trust roots (ultimate ownertrust): {:?}", roots); let policy = (*self.policy.read().unwrap()).clone(); let mut network = sequoia_wot::Network::from_cert_refs( &certs, &policy, reference_time, roots).unwrap(); // Add GnuPG intermediate trust roots to 'trust_roots' let mut found_one = true; while found_one && !possible_roots.is_empty() { // For GnuPG to consider a non-ultimately trusted root as // valid, there must be a path from an ultimately trusted root // to the non-ultimately trusted root. If this is the case, // add those roots. t!("Checking if any of {} are reachable from the current {} roots", possible_roots.iter() .fold(String::new(), |mut s, pr| { if ! s.is_empty() { s.push_str(", "); } s.push_str(&pr.fingerprint().to_hex()); s }), trust_roots.len()); found_one = false; let pr = possible_roots; possible_roots = Vec::new(); 'root: for r in pr.into_iter() { let cert = match network.lookup_synopsis_by_fpr(&r.fingerprint()) { Err(_e) => { t!("Ignoring root {}: not in network.", r.fingerprint()); continue; } Ok(v) => v, }; for u in cert.userids() { if u.revocation_status().in_effect(reference_time) { t!("Ignoring root {}'s User ID {:?}: revoked.", r.fingerprint(), String::from_utf8_lossy(u.value())); continue; } let authenticated_amount = network.authenticate( u.userid(), r.fingerprint(), sequoia_wot::FULLY_TRUSTED) .amount(); if authenticated_amount >= sequoia_wot::FULLY_TRUSTED { // Authenticated! We'll keep it. t!("Non-ultimately trusted root <{}, {}> reachable, \ keeping at {}", r.fingerprint(), String::from_utf8_lossy(u.userid().value()), r.amount()); found_one = true; trust_roots.push(r); let roots = Roots::new(trust_roots.clone()); network = sequoia_wot::Network::from_cert_refs( &certs, &policy, reference_time, roots).unwrap(); continue 'root; } else { t!("Non-ultimately trusted binding <{}, {}> \ NOT fully trusted (amount: {})", r.fingerprint(), String::from_utf8_lossy(u.userid().value()), authenticated_amount); } } t!("Non-ultimately trusted root {} NOT fully trusted. Ignoring.", r.fingerprint()); possible_roots.push(r); } } let start = SystemTime::now(); let validity: Vec<(Fingerprint, Vec<(String, Validity)>)> = certs.iter().map(|c| { let fp = c.fingerprint(); let userids: Vec<(String, Validity)> = c.with_policy(&policy, reference_time).map(|vc| { vc.userids().filter_map(|uid| { if let Ok(s) = String::from_utf8(uid.userid().value().to_vec()) { let paths = network.authenticate( uid.userid(), &fp, sequoia_wot::FULLY_TRUSTED); if let Some(validity) = match paths.amount() { amount if amount >= sequoia_wot::FULLY_TRUSTED => Some(Validity::Fully), amount if amount > 0 => Some(Validity::Marginal), _ => None } { Some((s, validity)) } else { None } } else { None } }).collect::>() }).unwrap_or(vec![]); (fp, userids) }).collect(); if let Ok(elapsed) = start.elapsed() { t!("processed {} certs in {:?}, {:?} per cert", validity.len(), elapsed, elapsed.checked_div(validity.len() as u32).unwrap_or(Duration::new(0, 0))); } // The global authentication was hard to compute, make some // effort to actually write it to the database. Eagerly start // an immediate transaction (i.e. acquire the write lock right // now) so that we can do all the updates, or fail here, where // we can retry with exponential backoff. let mut tx = { let mut tries = 0; loop { match self.conn.transaction_with_behavior( rusqlite::TransactionBehavior::Immediate) { Ok(t) => break t, Err(e) => { // Sleep for 3ms, 30ms, 300ms, then fail. if tries > 2 { return Err(e.into()); } else { std::thread::sleep( Duration::new(0, 3 * 1000 * 10u32.pow(tries))); tries += 1; } }, } } }; // See comment in `WoT::new` regarding what exactly is // considered managed by sequoia. let managed_by_tb: HashSet = { let mut stmt = tx.prepare("SELECT fpr from acceptance_decision \ WHERE fpr NOT IN \ (SELECT fpr from managed_by_sequoia)")?; let rows = stmt .query_map(params![], |row| row.get::<_, String>(0).map(|fpr| { fpr.to_ascii_lowercase() })); match rows { Ok(iter) => iter.filter_map(|fpr| fpr.ok()).collect(), Err(err) => { warn!("Querying managed_by_sequoia: {}", err); Default::default() } } }; Self::import_validity(&mut tx, Some(managed_by_tb), validity)?; tx.commit()?; self.last_keystore_update = last_keystore_update; self.next_wot_change_at = Some(next_wot_change_at); t!("last_keystore_update: {}, next_wot_change_at: {:?}", self.last_keystore_update, self.next_wot_change_at); Ok(()) } // If managed_by_tb is None, then validity has already been // filtered to only contain keys managed by us. fn import_validity(tx: &mut rusqlite::Transaction, managed_by_tb: Option>, validity: Vec<(Fingerprint, Vec<(String, gpg::Validity)>)>) -> openpgp::Result<()> { rnp_function!(WoT::import_validity, TRACE); // First, remove all assertions managed by sequoia // (they will get re-added below, if they are still valid). match tx.execute( "DELETE FROM acceptance_decision WHERE fpr IN \ (SELECT fpr FROM managed_by_sequoia)", params![], ) { Ok(_) => t!("Removed sequoia-managed acceptance entries"), Err(e) => { warn!("Removing sequoia-managed acceptance failed: {}", e); } } for (fpr, key_validity) in validity { let key_validity: Vec<_> = key_validity .into_iter() .filter_map(|(uid, validity)| { match UserID::from(uid.clone()).email() { Ok(Some(email)) => { Some((email.to_ascii_lowercase(), validity)) }, Ok(None) => None, Err(err) => { warn!("Extracting email address from UserID: {:?}: {}", uid, err); None } } }) .collect(); if key_validity.is_empty() { continue; } // We can only have two judgements per certificate: "none" or // "something else". Unfortunately, the format isn't more // expressive. If any are maginal, then we treat all verified // User IDs as maginal. let validity = if key_validity.iter().any(|(_uid, validity)| { *validity == Validity::Marginal }) { "unverified" } else { "verified" }; let fpr = fpr.to_string().to_ascii_lowercase(); if managed_by_tb.as_ref().map(|h| h.contains(&fpr)).unwrap_or(true) { t!("Not updating validity for {} to {:?}: \ validity managed by user", fpr, validity); } else { t!("Setting {} to {}", fpr, validity); tx.execute( "INSERT OR REPLACE INTO acceptance_decision \ (fpr, decision) VALUES (?1, ?2)", &[&fpr as &dyn ToSql, &validity])?; tx.execute( "INSERT OR REPLACE INTO managed_by_sequoia \ (fpr) VALUES (?1)", &[&fpr as &dyn ToSql])?; for (email, _validity) in key_validity { t!("Setting {}, {} to {}", fpr, email, validity); tx.execute( "INSERT OR REPLACE INTO acceptance_email \ (fpr, email) VALUES (?1, ?2)", &[&fpr as &dyn ToSql, &email])?; } } } Ok(()) } }