From 0efd613513a9288cc8ffedf0e71e840eccdead00 Mon Sep 17 00:00:00 2001 From: Kunal Mehta Date: Thu, 16 Jun 2022 13:52:07 -0400 Subject: [PATCH] WIP: Use Sequoia PGP TODO: * get rid of remaining unwrap() * write tests for everything in Rust * use maturin to build a redwood wheel * import redwood and use it instead of gnupg library * store keys in source user database table, figure out where to put journalist key --- .circleci/config.yml | 4 +- Cargo.lock | 1117 ++++++++++++++++ Cargo.toml | 5 + redwood/Cargo.lock | 1155 +++++++++++++++++ redwood/Cargo.toml | 18 + redwood/pyproject.toml | 10 + redwood/redwood.pyi | 16 + redwood/src/decryption.rs | 67 + redwood/src/lib.rs | 233 ++++ rust-toolchain.toml | 2 + rustfmt.toml | 1 + .../versions/a5d61d9463ed_sequoia_pgp_keys.py | 62 + securedrop/bin/dev-deps | 6 +- .../app-code/etc/apparmor.d/usr.sbin.apache2 | 1 - .../dockerfiles/focal/python3/SlimDockerfile | 2 +- securedrop/encryption.py | 168 +-- securedrop/execution.py | 13 - securedrop/gpg_encryption.py | 137 ++ securedrop/journalist.py | 18 - securedrop/journalist_app/col.py | 13 +- securedrop/journalist_app/main.py | 2 +- securedrop/journalist_app/utils.py | 7 - securedrop/journalist_templates/col.html | 2 +- securedrop/loaddata.py | 5 +- securedrop/models.py | 34 +- securedrop/source_app/main.py | 6 +- securedrop/source_user.py | 13 +- securedrop/store.py | 3 +- securedrop/tests/conftest.py | 1 - securedrop/tests/functional/conftest.py | 2 - .../migrations/migration_a5d61d9463ed.py | 42 + securedrop/tests/test_encryption.py | 31 +- securedrop/tests/test_journalist.py | 23 - securedrop/tests/utils/db_helper.py | 3 +- 34 files changed, 2953 insertions(+), 269 deletions(-) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 redwood/Cargo.lock create mode 100644 redwood/Cargo.toml create mode 100644 redwood/pyproject.toml create mode 100644 redwood/redwood.pyi create mode 100644 redwood/src/decryption.rs create mode 100644 redwood/src/lib.rs create mode 100644 rust-toolchain.toml create mode 100644 rustfmt.toml create mode 100644 securedrop/alembic/versions/a5d61d9463ed_sequoia_pgp_keys.py delete mode 100644 securedrop/execution.py create mode 100644 securedrop/gpg_encryption.py create mode 100644 securedrop/tests/migrations/migration_a5d61d9463ed.py diff --git a/.circleci/config.yml b/.circleci/config.yml index 41fc6135461..66371a8fb49 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -97,13 +97,13 @@ jobs: rust: # Keep version in sync with rust-toolchain.toml docker: - - image: rust:1.59.0 + - image: rust:1.61.0 steps: - checkout - run: name: Install dependencies command: | - apt-get update && apt-get install make clang nettle-dev -y + apt-get update && apt-get install make libssl-dev -y rustup component add rustfmt rustup component add clippy - run: diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000000..1b6aeb97714 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1117 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" + +[[package]] +name = "ascii-canvas" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "buffered-reader" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0dd286184b392a1ce6b3deecd073f0330df194bf935b87f852147d50d0d2d18" +dependencies = [ + "bzip2", + "flate2", + "lazy_static", + "libc", +] + +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + +[[package]] +name = "bzip2" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "cc" +version = "1.0.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" + +[[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.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +dependencies = [ + "js-sys", + "num-integer", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[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-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dyn-clone" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9b0705efd4599c15a38151f4721f7bc388306f61084d3bfd50bd07fbca5cb60" + +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + +[[package]] +name = "ena" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3" +dependencies = [ + "log", +] + +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + +[[package]] +name = "fixedbitset" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" + +[[package]] +name = "flate2" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "indoc" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da2d6f23ffea9d7e76c53eee25dfb67bcd8fde7f1198b0855350698c9f07c780" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "js-sys" +version = "0.3.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lalrpop" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b30455341b0e18f276fa64540aff54deafb54c589de6aca68659c63dd2d5d823" +dependencies = [ + "ascii-canvas", + "atty", + "bit-set", + "diff", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", +] + +[[package]] +name = "lalrpop-util" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf796c978e9b4d983414f4caedc9273aa33ee214c5b887bd55fde84c85d2dc4" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memsec" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ac78937f19a0c7807e45a931eac41f766f210173ec664ec046d58e6d388a5cb" + +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" + +[[package]] +name = "openssl" +version = "0.10.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-sys" +version = "0.9.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1ef8814b5c993410bb3adfad7a5ed269563e4a2f90c41f5d85be7fb47133bf" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "petgraph" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[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.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pyo3" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd4149c8c3975099622b4e1962dac27565cf5663b76452c3e2b66e0b6824277" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "memoffset", + "parking_lot", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cd09fe469834db21ee60e0051030339e5d361293d8cb5ec02facf7fdcf52dbf" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c427c9a96b9c5b12156dbc11f76b14f49e9aae8905ca783ea87c249044ef137" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b822bbba9d60630a44d2109bc410489bb2f439b33e3a14ddeb8a40b378a7c4" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84ae898104f7c99db06231160770f3e40dad6eb9021daddc0fedfa3e41dff10a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom 0.2.8", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "redwood" +version = "0.1.0" +dependencies = [ + "anyhow", + "pyo3", + "sequoia-openpgp", + "tempfile", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "rustversion" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sequoia-openpgp" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70af2f594bf5261eb87be5060db1e40d6445686d2bb3ec41c014cf53701130a2" +dependencies = [ + "anyhow", + "base64", + "buffered-reader", + "bzip2", + "chrono", + "dyn-clone", + "flate2", + "foreign-types-shared", + "getrandom 0.2.8", + "idna", + "lalrpop", + "lalrpop-util", + "lazy_static", + "libc", + "memsec", + "openssl", + "openssl-sys", + "rand", + "regex", + "regex-syntax", + "sha1collisiondetection", + "thiserror", + "xxhash-rust", +] + +[[package]] +name = "sha1collisiondetection" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c66558a774ef5044cb4a834db5f5c7f95e139d2341d7f502fe6034afa7082461" +dependencies = [ + "digest", + "generic-array", +] + +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "string_cache" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213494b7a2b503146286049378ce02b482200519accc31872ee8be91fa820a08" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-lexicon" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9410d0f6853b1d94f0e519fb95df60f29d2c1eff2d921ffdf01a4c8a3b54f12d" + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[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 = "thiserror" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "unicode-bidi" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "unindent" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" + +[[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-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" + +[[package]] +name = "xxhash-rust" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "735a71d46c4d68d71d4b24d03fdc2b98e38cea81730595801db779c04fe80d70" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000000..a58d4141969 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,5 @@ +[workspace] + +members = [ + "redwood", +] diff --git a/redwood/Cargo.lock b/redwood/Cargo.lock new file mode 100644 index 00000000000..77d5cee7848 --- /dev/null +++ b/redwood/Cargo.lock @@ -0,0 +1,1155 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f9b8508dccb7687a1d6c4ce66b2b0ecef467c94667de27d8d7fe1f8d2a9cdc" + +[[package]] +name = "ascii-canvas" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6" +dependencies = [ + "term", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bindgen" +version = "0.57.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd4865004a46a0aafb2a0a5eb19d3c9fc46ee5f063a6cfc605c69ac9ecf5263d" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", +] + +[[package]] +name = "bit-set" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "buffered-reader" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4212de05390ccc7d0e0511d0560c91f278fa407c9153c60aa94fa513b8ec6c9" +dependencies = [ + "bzip2", + "flate2", + "libc", +] + +[[package]] +name = "bumpalo" +version = "3.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37ccbd214614c6783386c1af30caf03192f17891059cecc394b4fb119e363de3" + +[[package]] +name = "bzip2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6afcd980b5f3a45017c57e57a2fcccbb351cc43a356ce117ef760ef8052b89b0" +dependencies = [ + "bzip2-sys", + "libc", +] + +[[package]] +name = "bzip2-sys" +version = "0.1.11+1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + +[[package]] +name = "cexpr" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4aedb84272dbe89af497cf81375129abda4fc0a9e7c5d317498c15cc30c0d27" +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.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" +dependencies = [ + "js-sys", + "num-integer", + "num-traits", + "wasm-bindgen", +] + +[[package]] +name = "clang-sys" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a050e2153c5be08febd6734e29298e844fdb0fa21aeddd63b4eb7baa106c69b" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "diff" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[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-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dyn-clone" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e50f3adc76d6a43f5ed73b698a87d0760ca74617f60f7c3b879003536fdd28" + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "ena" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7402b94a93c24e742487327a7cd839dc9d36fec9de9fb25b09f2dae459f36c3" +dependencies = [ + "log", +] + +[[package]] +name = "fastrand" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" +dependencies = [ + "instant", +] + +[[package]] +name = "fixedbitset" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "279fb028e20b3c4c320317955b77c5e0c9701f05a1d309905d6fc702cdc5053e" + +[[package]] +name = "flate2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "idna" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" +dependencies = [ + "matches", + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6012d540c5baa3589337a98ce73408de9b5a25ec9fc2c6fd6be8f0d39e0ca5a" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "indoc" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05a0bd019339e5d968b37855180087b7b9d512c5046fbd244cf8c95687927d6e" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + +[[package]] +name = "js-sys" +version = "0.3.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3fac17f7123a73ca62df411b1bf727ccc805daa070338fda671c86dac1bdc27" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "lalrpop" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b30455341b0e18f276fa64540aff54deafb54c589de6aca68659c63dd2d5d823" +dependencies = [ + "ascii-canvas", + "atty", + "bit-set", + "diff", + "ena", + "itertools", + "lalrpop-util", + "petgraph", + "regex", + "regex-syntax", + "string_cache", + "term", + "tiny-keccak", + "unicode-xid", +] + +[[package]] +name = "lalrpop-util" +version = "0.19.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf796c978e9b4d983414f4caedc9273aa33ee214c5b887bd55fde84c85d2dc4" + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "libloading" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "lock_api" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "matches" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memsec" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ac78937f19a0c7807e45a931eac41f766f210173ec664ec046d58e6d388a5cb" + +[[package]] +name = "miniz_oxide" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +dependencies = [ + "adler", +] + +[[package]] +name = "nettle" +version = "7.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5d193a809310369c5d16e45bc0a88cb27935edd5d3375bcfc2371b167694035" +dependencies = [ + "getrandom 0.2.7", + "libc", + "nettle-sys", + "thiserror", +] + +[[package]] +name = "nettle-sys" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b13b685c7883e3a32196ccf3ce594947ec37ace43d74e157de7ca03d3fe62d17" +dependencies = [ + "bindgen", + "cc", + "libc", + "pkg-config", + "tempfile", + "vcpkg", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + +[[package]] +name = "nom" +version = "5.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af" +dependencies = [ + "memchr", + "version_check", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys", +] + +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "petgraph" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +dependencies = [ + "fixedbitset", + "indexmap", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[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.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "pyo3" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6302e85060011447471887705bb7838f14aba43fcb06957d823739a496b3dc" +dependencies = [ + "cfg-if", + "indoc", + "libc", + "parking_lot", + "pyo3-build-config", + "pyo3-ffi", + "pyo3-macros", + "unindent", +] + +[[package]] +name = "pyo3-build-config" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b65b546c35d8a3b1b2f0ddbac7c6a569d759f357f2b9df884f5d6b719152c8" +dependencies = [ + "once_cell", + "target-lexicon", +] + +[[package]] +name = "pyo3-ffi" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c275a07127c1aca33031a563e384ffdd485aee34ef131116fcd58e3430d1742b" +dependencies = [ + "libc", + "pyo3-build-config", +] + +[[package]] +name = "pyo3-macros" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "284fc4485bfbcc9850a6d661d627783f18d19c2ab55880b021671c4ba83e90f7" +dependencies = [ + "proc-macro2", + "pyo3-macros-backend", + "quote", + "syn", +] + +[[package]] +name = "pyo3-macros-backend" +version = "0.16.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53bda0f58f73f5c5429693c96ed57f7abdb38fdfc28ae06da4101a257adb7faf" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "quote" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom 0.2.7", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "redwood" +version = "0.1.0" +dependencies = [ + "pyo3", + "sequoia-openpgp", +] + +[[package]] +name = "regex" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustversion" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "sequoia-openpgp" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89645c8405a097289bd8ba4aa548b4100e532aad81b2d06d148285a2473cebd" +dependencies = [ + "anyhow", + "base64", + "buffered-reader", + "bzip2", + "chrono", + "dyn-clone", + "flate2", + "getrandom 0.2.7", + "idna", + "lalrpop", + "lalrpop-util", + "lazy_static", + "libc", + "memsec", + "nettle", + "rand", + "regex", + "regex-syntax", + "sha1collisiondetection", + "thiserror", + "xxhash-rust", +] + +[[package]] +name = "sha1collisiondetection" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31bf4e9fe5cd8cea8e0887e2e4eb1b4d736ff11b776c8537bf0912a4b381285" +dependencies = [ + "digest", + "generic-array", +] + +[[package]] +name = "shlex" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fdf1b9db47230893d76faad238fd6097fd6d6a9245cd7a4d90dbd639536bbd2" + +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] +name = "string_cache" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213494b7a2b503146286049378ce02b482200519accc31872ee8be91fa820a08" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "syn" +version = "1.0.96" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "target-lexicon" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1" + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[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 = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicode-bidi" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" + +[[package]] +name = "unicode-ident" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" + +[[package]] +name = "unindent" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52fee519a3e570f7df377a06a1a7775cdbfb7aa460be7e08de2b1f0e69973a44" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c53b543413a17a202f4be280a7e5c62a1c69345f5de525ee64f8cfdbc954994" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5491a68ab4500fa6b4d726bd67408630c3dbe9c4fe7bda16d5c82a1fd8c7340a" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c441e177922bc58f1e12c022624b6216378e5febc2f0533e41ba443d505b80aa" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d94ac45fcf608c1f45ef53e748d35660f168490c10b23704c7779ab8f5c3048" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a89911bd99e5f3659ec4acf9c4d93b0a90fe4a2a11f15328472058edc5261be" + +[[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-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +dependencies = [ + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" + +[[package]] +name = "windows_i686_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" + +[[package]] +name = "windows_i686_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" + +[[package]] +name = "xxhash-rust" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "074914ea4eec286eb8d1fd745768504f420a1f7b7919185682a4a267bed7d2e7" diff --git a/redwood/Cargo.toml b/redwood/Cargo.toml new file mode 100644 index 00000000000..8bf469cd8fe --- /dev/null +++ b/redwood/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "redwood" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +name = "redwood" +crate-type = ["cdylib"] + +[dependencies] +anyhow = "1.0" +pyo3 = { version = "0.18.0", features = ["extension-module"] } +sequoia-openpgp = { version = "1.13.0", default-features = false, features = ["crypto-openssl", "compression"]} +thiserror = "1.0.31" + +[dev-dependencies] +tempfile = "3.3.0" diff --git a/redwood/pyproject.toml b/redwood/pyproject.toml new file mode 100644 index 00000000000..4bcdd6cb3c5 --- /dev/null +++ b/redwood/pyproject.toml @@ -0,0 +1,10 @@ +[build-system] +requires = ["maturin>=0.13,<0.14"] +build-backend = "maturin" + +[project] +name = "redwood" +requires-python = ">=3.8" +classifiers = [ + "Programming Language :: Rust", +] diff --git a/redwood/redwood.pyi b/redwood/redwood.pyi new file mode 100644 index 00000000000..108073f57fb --- /dev/null +++ b/redwood/redwood.pyi @@ -0,0 +1,16 @@ +# type stub for redwood module +# see https://pyo3.rs/v0.16.4/python_typing_hints.html +from pathlib import Path +from typing import List + +def generate_source_key_pair(passphrase: str, email: str) -> (str, str, str): + pass + +def encrypt_message(recipients: List[str], plaintext: str, destination: Path) -> None: + pass + +def encrypt_file(recipients: List[str], plaintext: Path, destination: Path) -> None: + pass + +def decrypt(ciphertext: bytes, secret_key: str, passphrase: str) -> str: + pass diff --git a/redwood/src/decryption.rs b/redwood/src/decryption.rs new file mode 100644 index 00000000000..5b5891ba3ec --- /dev/null +++ b/redwood/src/decryption.rs @@ -0,0 +1,67 @@ +//! Decryption is much more complicated than encryption, +//! This code is mostly lifted from https://docs.sequoia-pgp.org/sequoia_guide/chapter_02/index.html + +use sequoia_openpgp::crypto::{Password, SessionKey}; +use sequoia_openpgp::parse::stream::*; +use sequoia_openpgp::policy::Policy; +use sequoia_openpgp::types::SymmetricAlgorithm; + +pub(crate) struct Helper<'a> { + pub(crate) policy: &'a dyn Policy, + pub(crate) secret: &'a sequoia_openpgp::Cert, + pub(crate) passphrase: &'a Password, +} + +impl<'a> VerificationHelper for Helper<'a> { + fn get_certs( + &mut self, + _ids: &[sequoia_openpgp::KeyHandle], + ) -> sequoia_openpgp::Result> { + // Return public keys for signature verification here. + Ok(Vec::new()) + } + + fn check( + &mut self, + _structure: MessageStructure, + ) -> sequoia_openpgp::Result<()> { + // Implement your signature verification policy here. + Ok(()) + } +} + +impl<'a> DecryptionHelper for Helper<'a> { + fn decrypt( + &mut self, + pkesks: &[sequoia_openpgp::packet::PKESK], + _skesks: &[sequoia_openpgp::packet::SKESK], + sym_algo: Option, + mut decrypt: D, + ) -> sequoia_openpgp::Result> + where + D: FnMut(SymmetricAlgorithm, &SessionKey) -> bool, + { + // The encryption key is the first and only subkey. + let key = self + .secret + .keys() + .secret() + .with_policy(self.policy, None) + .next() + // FIXME: unwrap() + .unwrap() + .key() + .clone(); + + // Decrypt the secret key with the specified passphrase. + let mut pair = key.decrypt_secret(self.passphrase)?.into_keypair()?; + + pkesks[0] + .decrypt(&mut pair, sym_algo) + .map(|(algo, session_key)| decrypt(algo, &session_key)); + + // XXX: In production code, return the Fingerprint of the + // recipient's Cert here + Ok(None) + } +} diff --git a/redwood/src/lib.rs b/redwood/src/lib.rs new file mode 100644 index 00000000000..28b5d1fd683 --- /dev/null +++ b/redwood/src/lib.rs @@ -0,0 +1,233 @@ +#![deny(clippy::all)] + +use pyo3::create_exception; +use pyo3::exceptions::PyException; +use pyo3::prelude::*; +use sequoia_openpgp::cert::{CertBuilder, CipherSuite}; +use sequoia_openpgp::crypto::Password; +use sequoia_openpgp::parse::{stream::DecryptorBuilder, Parse}; +use sequoia_openpgp::policy::StandardPolicy; +use sequoia_openpgp::serialize::{ + stream::{Armorer, Encryptor, LiteralWriter, Message}, + SerializeInto, +}; +use sequoia_openpgp::Cert; +use std::fs::File; +use std::io::{self, Read}; +use std::path::{Path, PathBuf}; +use std::str::FromStr; +use std::string::FromUtf8Error; +use std::time::{Duration, SystemTime}; + +/// Alias to make it easier for Python readers +type Bytes = Vec; + +mod decryption; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("OpenPGP error: {0}")] + OpenPgp(#[from] anyhow::Error), + #[error("IO error: {0}")] + Io(#[from] io::Error), + #[error("Unexpected non-UTF-8 text: {0}")] + NotUnicode(#[from] FromUtf8Error), +} + +create_exception!(redwood, RedwoodError, PyException); + +impl From for PyErr { + fn from(original: Error) -> Self { + // TODO: make sure we're not losing the stacktrace here + RedwoodError::new_err(original.to_string()) + } +} + +type Result = std::result::Result; + +/// A Python module implemented in Rust. +#[pymodule] +fn redwood(py: Python, m: &PyModule) -> PyResult<()> { + m.add_function(wrap_pyfunction!(generate_source_key_pair, m)?)?; + m.add_function(wrap_pyfunction!(encrypt_message, m)?)?; + m.add_function(wrap_pyfunction!(encrypt_file, m)?)?; + m.add_function(wrap_pyfunction!(decrypt, m)?)?; + m.add("RedwoodError", py.get_type::())?; + Ok(()) +} + +/// Generate a new PGP key pair using the given email (user ID) and protected +/// with the specified passphrase. +/// Returns the public key, private key, and 40-character fingerprint +#[pyfunction] +pub fn generate_source_key_pair( + passphrase: &str, + email: &str, +) -> Result<(String, String, String)> { + let (cert, _revocation) = CertBuilder::new() + .set_cipher_suite(CipherSuite::RSA4k) + .add_userid(format!("Source Key <{}>", email)) + .set_creation_time( + // All reply keypairs will be "created" on the same day SecureDrop (then + // Strongbox) was publicly released for the first time. + // https://www.newyorker.com/news/news-desk/strongbox-and-aaron-swartz + SystemTime::UNIX_EPOCH + // Equivalent to 2013-05-14 + .checked_add(Duration::from_secs(1368507600)) + // unwrap: Safe because the value is fixed and we know it won't overflow + .unwrap(), + ) + .add_storage_encryption_subkey() + .set_password(Some(passphrase.into())) + .generate()?; + let public_key = String::from_utf8(cert.armored().to_vec()?)?; + let secret_key = String::from_utf8(cert.as_tsk().armored().to_vec()?)?; + Ok((public_key, secret_key, format!("{}", cert.fingerprint()))) +} + +/// Encrypt a message (text) for the specified recipients. The list of +/// recipients is a set of PGP public keys. The encrypted message will +/// be written to `destination`. +#[pyfunction] +pub fn encrypt_message( + recipients: Vec, + plaintext: String, + destination: PathBuf, +) -> Result<()> { + let plaintext = plaintext.as_bytes(); + encrypt(&recipients, plaintext, &destination) +} + +/// Encrypt a file that's already on disk for the specified recipients. +/// The list of recipients is a set of PGP public keys. The encrypted file +/// will be written to `destination`. +#[pyfunction] +pub fn encrypt_file( + recipients: Vec, + plaintext: PathBuf, + destination: PathBuf, +) -> Result<()> { + let plaintext = File::open(plaintext)?; + encrypt(&recipients, plaintext, &destination) +} + +/// Helper function to encrypt readable things. +fn encrypt( + recipients: &[String], + mut plaintext: impl Read, + destination: &Path, +) -> Result<()> { + let p = &StandardPolicy::new(); + let mut certs = vec![]; + let mut recipient_keys = vec![]; + for recipient in recipients { + certs.push(Cert::from_str(recipient)?); + } + for cert in certs.iter() { + for key in cert + .keys() + .with_policy(p, None) + .supported() + .alive() + .revoked(false) + { + recipient_keys.push(key); + } + } + + let mut sink = File::create(destination)?; + let message = Message::new(&mut sink); + let message = Armorer::new(message).build()?; + let message = Encryptor::for_recipients(message, recipient_keys).build()?; + let mut message = LiteralWriter::new(message).build()?; + + io::copy(&mut plaintext, &mut message)?; + + message.finalize()?; + + Ok(()) +} + +/// Decrypt the given ciphertext using the specified private key that is locked +/// using the provided passphrase. It is assumed that the plaintext is UTF-8. +#[pyfunction] +pub fn decrypt( + ciphertext: Bytes, + secret_key: String, + passphrase: String, +) -> Result { + let recipient = Cert::from_str(&secret_key)?; + let policy = &StandardPolicy::new(); + let passphrase: Password = passphrase.into(); + let helper = decryption::Helper { + policy, + secret: &recipient, + passphrase: &passphrase, + }; + + // Now, create a decryptor with a helper using the given Certs. + let mut decryptor = DecryptorBuilder::from_bytes(&ciphertext)? + .with_policy(policy, None, helper)?; + + // Decrypt the data. + let mut buffer: Bytes = vec![]; + io::copy(&mut decryptor, &mut buffer)?; + let plaintext = String::from_utf8(buffer)?; + Ok(plaintext) +} + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::NamedTempFile; + + #[test] + fn test_generate_source_key_pair() { + let (public_key, secret_key, fingerprint) = generate_source_key_pair( + "correcthorsebatterystaple", + "foo@example.org", + ) + .unwrap(); + assert_eq!(fingerprint.len(), 40); + println!("{}", public_key); + assert!(public_key.starts_with("-----BEGIN PGP PUBLIC KEY BLOCK-----")); + assert!(public_key.contains("Comment: Source Key ")); + let cert = Cert::from_str(&public_key).unwrap(); + assert_eq!(format!("{}", cert.fingerprint()), fingerprint); + println!("{}", secret_key); + assert!(secret_key.starts_with("-----BEGIN PGP PRIVATE KEY BLOCK-----")); + assert!(secret_key.contains("Comment: Source Key ")); + let cert = Cert::from_str(&secret_key).unwrap(); + assert_eq!(format!("{}", cert.fingerprint()), fingerprint); + } + + #[test] + fn test_encryption_decryption() { + // Generate a new key + let (public_key, secret_key, _fingerprint) = generate_source_key_pair( + "correcthorsebatterystaple", + "foo@example.org", + ) + .unwrap(); + let tmp = NamedTempFile::new().unwrap(); + // Encrypt a message + encrypt_message( + vec![public_key], + "Rust is great 🦀".to_string(), + tmp.path().to_path_buf(), + ) + .unwrap(); + let ciphertext = std::fs::read_to_string(tmp.path()).unwrap(); + // Verify ciphertext looks like an encrypted message + assert!(ciphertext.starts_with("-----BEGIN PGP MESSAGE-----\n")); + // Try to decrypt the message + let plaintext = decrypt( + ciphertext.into_bytes(), + secret_key, + "correcthorsebatterystaple".to_string(), + ) + .unwrap(); + // Verify message is what we put in originally + assert_eq!("Rust is great 🦀", &plaintext); + } +} diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 00000000000..ead027184a0 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,2 @@ +[toolchain] +channel = "1.61.0" diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 index 00000000000..df99c69198f --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1 @@ +max_width = 80 diff --git a/securedrop/alembic/versions/a5d61d9463ed_sequoia_pgp_keys.py b/securedrop/alembic/versions/a5d61d9463ed_sequoia_pgp_keys.py new file mode 100644 index 00000000000..b4e84639e42 --- /dev/null +++ b/securedrop/alembic/versions/a5d61d9463ed_sequoia_pgp_keys.py @@ -0,0 +1,62 @@ +"""sequoia_pgp_keys + +Revision ID: a5d61d9463ed +Revises: b7f98cfd6a70 +Create Date: 2022-08-29 23:28:06.000520 + +""" +import sqlalchemy as sa +from alembic import op +from gpg_encryption import GpgEncryptionManager, GpgKeyNotFoundError + +# revision identifiers, used by Alembic. +revision = "a5d61d9463ed" +down_revision = "b7f98cfd6a70" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + with op.batch_alter_table("sources", schema=None) as batch_op: + batch_op.add_column(sa.Column("fingerprint", sa.String(length=40), nullable=True)) + batch_op.add_column(sa.Column("private_key", sa.Text(), nullable=True)) + batch_op.add_column(sa.Column("public_key", sa.Text(), nullable=True)) + + # First we export the journalist public key + gpg = GpgEncryptionManager.get_default() + (gpg.gpg_key_dir / "journalist.pub").write_text(gpg.get_journalist_public_key()) + + # Now we migrate all the sources + conn = op.get_bind() + results = conn.execute("SELECT filesystem_id FROM sources") + for row in results: + filesystem_id = row[0] + # Fetch key out of the keyring if possible + try: + fingerprint = gpg.get_source_key_fingerprint(filesystem_id) + public_key = gpg.get_source_public_key(filesystem_id, fingerprint) + except GpgKeyNotFoundError: + continue + + # Save to database... + conn.execute( + sa.text( + """\ + UPDATE sources + SET fingerprint=:fingerprint AND public_key=:public_key: + WHERE filesystem_id=:filesystem_id: + """ + ).bindparams( + fingerprint=fingerprint, public_key=public_key, filesystem_id=filesystem_id + ) + ) + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table("sources", schema=None) as batch_op: + batch_op.drop_column("public_key") + batch_op.drop_column("private_key") + batch_op.drop_column("fingerprint") + + # ### end Alembic commands ### diff --git a/securedrop/bin/dev-deps b/securedrop/bin/dev-deps index ce90dc7178a..9814591e28e 100755 --- a/securedrop/bin/dev-deps +++ b/securedrop/bin/dev-deps @@ -112,8 +112,8 @@ function reset_demo() { # Set up GPG keys directory structure. sudo mkdir -p /var/lib/securedrop/{store,keys,tmp} sudo chown -R "$(id -u)" /var/lib/securedrop - cp ./tests/files/test_journalist_key.pub /var/lib/securedrop/keys - gpg2 --homedir /var/lib/securedrop/keys --import /var/lib/securedrop/keys/test_journalist_key.pub >& /tmp/gpg.out || cat /tmp/gpg.out + cp ./tests/files/test_journalist_key.pub /var/lib/securedrop/keys/journalist.pub + gpg2 --homedir /var/lib/securedrop/keys --import /var/lib/securedrop/keys/journalist.pub >& /tmp/gpg.out || cat /tmp/gpg.out # Create gpg-agent.conf echo allow-loopback-pinentry > /var/lib/securedrop/keys/gpg-agent.conf @@ -144,7 +144,7 @@ function reset_demo() { ./manage.py reset - gpg2 --homedir /var/lib/securedrop/keys --no-default-keyring --keyring /var/lib/securedrop/keys/pubring.gpg --import /var/lib/securedrop/keys/test_journalist_key.pub + gpg2 --homedir /var/lib/securedrop/keys --no-default-keyring --keyring /var/lib/securedrop/keys/pubring.gpg --import /var/lib/securedrop/keys/journalist.pub # Can't pass an array environment variable with "docker --env", so # break up the string we can pass. diff --git a/securedrop/debian/app-code/etc/apparmor.d/usr.sbin.apache2 b/securedrop/debian/app-code/etc/apparmor.d/usr.sbin.apache2 index 8471789aa72..e3300f49992 100644 --- a/securedrop/debian/app-code/etc/apparmor.d/usr.sbin.apache2 +++ b/securedrop/debian/app-code/etc/apparmor.d/usr.sbin.apache2 @@ -165,7 +165,6 @@ /var/www/securedrop/dictionaries/adjectives.txt r, /var/www/securedrop/dictionaries/nouns.txt r, /var/www/securedrop/encryption.py r, - /var/www/securedrop/execution.py r, /var/www/securedrop/i18n.py r, /var/www/securedrop/journalist.py r, /var/www/securedrop/journalist_app/ r, diff --git a/securedrop/dockerfiles/focal/python3/SlimDockerfile b/securedrop/dockerfiles/focal/python3/SlimDockerfile index 21d30410f85..69ea66d8e1a 100644 --- a/securedrop/dockerfiles/focal/python3/SlimDockerfile +++ b/securedrop/dockerfiles/focal/python3/SlimDockerfile @@ -9,7 +9,7 @@ RUN apt-get update && DEBIAN_FRONTEND=noninteractive TZ=Etc/UTC apt-get install apache2-dev coreutils vim \ python3-pip python3-all python3-venv virtualenv python3-dev libssl-dev \ gnupg2 redis-server git curl wget \ - enchant libffi-dev sqlite3 gettext sudo tor basez cargo + enchant libffi-dev sqlite3 gettext sudo tor basez cargo pkg-config COPY requirements requirements RUN python3 -m venv /opt/venvs/securedrop-build && \ diff --git a/securedrop/encryption.py b/securedrop/encryption.py index 494a60bfd9c..418991d49e5 100644 --- a/securedrop/encryption.py +++ b/securedrop/encryption.py @@ -3,15 +3,17 @@ import typing from datetime import date from distutils.version import StrictVersion -from io import BytesIO, StringIO from pathlib import Path -from typing import Dict, List, Optional +from typing import Optional import pretty_bad_protocol as gnupg from redis import Redis from sdconfig import SecureDropConfig +import redwood + if typing.TYPE_CHECKING: + from models import Source from source_user import SourceUser @@ -51,18 +53,6 @@ def _setup_monkey_patches_for_gnupg() -> None: _setup_monkey_patches_for_gnupg() -class GpgKeyNotFoundError(Exception): - pass - - -class GpgEncryptError(Exception): - pass - - -class GpgDecryptError(Exception): - pass - - _default_encryption_mgr: Optional["EncryptionManager"] = None @@ -80,16 +70,11 @@ class EncryptionManager: # to set an expiration date. DEFAULT_KEY_EXPIRATION_DATE = "0" - REDIS_FINGERPRINT_HASH = "sd/crypto-util/fingerprints" - REDIS_KEY_HASH = "sd/crypto-util/keys" - SOURCE_KEY_NAME = "Source Key" SOURCE_KEY_UID_RE = re.compile(r"(Source|Autogenerated) Key <[-A-Za-z0-9+/=_]+>") - def __init__(self, gpg_key_dir: Path, journalist_key_fingerprint: str) -> None: + def __init__(self, gpg_key_dir: Path) -> None: self._gpg_key_dir = gpg_key_dir - self._journalist_key_fingerprint = journalist_key_fingerprint - self._redis = Redis(decode_responses=True) # Instantiate the "main" GPG binary gpg = gnupg.GPG( @@ -112,15 +97,6 @@ def __init__(self, gpg_key_dir: Path, journalist_key_fingerprint: str) -> None: binary="gpg2", homedir=str(self._gpg_key_dir), options=["--yes", "--trust-model direct"] ) - # Ensure that the journalist public key has been previously imported in GPG - try: - self.get_journalist_public_key() - except GpgKeyNotFoundError: - raise OSError( - f"The journalist public key with fingerprint {journalist_key_fingerprint}" - f" has not been imported into GPG." - ) - @classmethod def get_default(cls) -> "EncryptionManager": global _default_encryption_mgr @@ -128,132 +104,42 @@ def get_default(cls) -> "EncryptionManager": config = SecureDropConfig.get_current() _default_encryption_mgr = cls( gpg_key_dir=config.GPG_KEY_DIR, - journalist_key_fingerprint=config.JOURNALIST_KEY, ) return _default_encryption_mgr - def generate_source_key_pair(self, source_user: "SourceUser") -> None: - gen_key_input = self._gpg.gen_key_input( - passphrase=source_user.gpg_secret, - name_email=source_user.filesystem_id, - key_type=self.GPG_KEY_TYPE, - key_length=self.GPG_KEY_LENGTH, - name_real=self.SOURCE_KEY_NAME, - creation_date=self.DEFAULT_KEY_CREATION_DATE.isoformat(), - expire_date=self.DEFAULT_KEY_EXPIRATION_DATE, - ) - new_key = self._gpg.gen_key(gen_key_input) - - # Store the newly-created key's fingerprint in Redis for faster lookups - self._save_key_fingerprint_to_redis(source_user.filesystem_id, str(new_key)) - - def delete_source_key_pair(self, source_filesystem_id: str) -> None: - source_key_fingerprint = self.get_source_key_fingerprint(source_filesystem_id) - - # The subkeys keyword argument deletes both secret and public keys - self._gpg_for_key_deletion.delete_keys(source_key_fingerprint, secret=True, subkeys=True) - - self._redis.hdel(self.REDIS_KEY_HASH, source_key_fingerprint) - self._redis.hdel(self.REDIS_FINGERPRINT_HASH, source_filesystem_id) - def get_journalist_public_key(self) -> str: - return self._get_public_key(self._journalist_key_fingerprint) - - def get_source_public_key(self, source_filesystem_id: str) -> str: - source_key_fingerprint = self.get_source_key_fingerprint(source_filesystem_id) - return self._get_public_key(source_key_fingerprint) - - def get_source_key_fingerprint(self, source_filesystem_id: str) -> str: - source_key_fingerprint = self._redis.hget(self.REDIS_FINGERPRINT_HASH, source_filesystem_id) - if source_key_fingerprint: - return source_key_fingerprint - - # If the fingerprint was not in Redis, get it directly from GPG - source_key_details = self._get_source_key_details(source_filesystem_id) - source_key_fingerprint = source_key_details["fingerprint"] - self._save_key_fingerprint_to_redis(source_filesystem_id, source_key_fingerprint) - return source_key_fingerprint + return (self._gpg_key_dir / "journalist.pub").read_text() def encrypt_source_message(self, message_in: str, encrypted_message_path_out: Path) -> None: - message_as_stream = StringIO(message_in) - self._encrypt( - # A submission is only encrypted for the journalist key - using_keys_with_fingerprints=[self._journalist_key_fingerprint], - plaintext_in=message_as_stream, - ciphertext_path_out=encrypted_message_path_out, + redwood.encrypt_message( + recipients=[self.get_journalist_public_key()], + plaintext=message_in, + destination=encrypted_message_path_out, ) - def encrypt_source_file(self, file_in: typing.IO, encrypted_file_path_out: Path) -> None: - self._encrypt( + def encrypt_source_file(self, filename: Path, encrypted_file_path_out: Path) -> None: + redwood.encrypt_file( # A submission is only encrypted for the journalist key - using_keys_with_fingerprints=[self._journalist_key_fingerprint], - plaintext_in=file_in, - ciphertext_path_out=encrypted_file_path_out, + recipients=[self.get_journalist_public_key()], + plaintext=filename, + destination=encrypted_file_path_out, ) def encrypt_journalist_reply( - self, for_source_with_filesystem_id: str, reply_in: str, encrypted_reply_path_out: Path + self, source: "Source", reply_in: str, encrypted_reply_path_out: Path ) -> None: - source_key_fingerprint = self.get_source_key_fingerprint(for_source_with_filesystem_id) - reply_as_stream = StringIO(reply_in) - self._encrypt( + redwood.encrypt_message( # A reply is encrypted for both the journalist key and the source key - using_keys_with_fingerprints=[source_key_fingerprint, self._journalist_key_fingerprint], - plaintext_in=reply_as_stream, - ciphertext_path_out=encrypted_reply_path_out, + recipients=[source.public_key, self.get_journalist_public_key()], + plaintext=reply_in, + destination=encrypted_reply_path_out, ) - def decrypt_journalist_reply(self, for_source_user: "SourceUser", ciphertext_in: bytes) -> str: - ciphertext_as_stream = BytesIO(ciphertext_in) - out = self._gpg.decrypt_file(ciphertext_as_stream, passphrase=for_source_user.gpg_secret) - if not out.ok: - raise GpgDecryptError(out.stderr) - - return out.data.decode("utf-8") - - def _encrypt( - self, - using_keys_with_fingerprints: List[str], - plaintext_in: typing.IO, - ciphertext_path_out: Path, - ) -> None: - # Remove any spaces from provided fingerprints GPG outputs fingerprints - # with spaces for readability, but requires the spaces to be removed - # when using fingerprints to specify recipients. - sanitized_key_fingerprints = [fpr.replace(" ", "") for fpr in using_keys_with_fingerprints] - - out = self._gpg.encrypt( - plaintext_in, - *sanitized_key_fingerprints, - output=str(ciphertext_path_out), - always_trust=True, - armor=False, + def decrypt_journalist_reply( + self, source_user: "SourceUser", source: "Source", ciphertext_in: bytes + ) -> str: + return redwood.decrypt( + ciphertext=ciphertext_in, + secret_key=source.private_key, + passphrase=source_user.gpg_secret, ) - if not out.ok: - raise GpgEncryptError(out.stderr) - - def _get_source_key_details(self, source_filesystem_id: str) -> Dict[str, str]: - for key in self._gpg.list_keys(): - for uid in key["uids"]: - if source_filesystem_id in uid and self.SOURCE_KEY_UID_RE.match(uid): - return key - raise GpgKeyNotFoundError() - - def _save_key_fingerprint_to_redis( - self, source_filesystem_id: str, source_key_fingerprint: str - ) -> None: - self._redis.hset(self.REDIS_FINGERPRINT_HASH, source_filesystem_id, source_key_fingerprint) - - def _get_public_key(self, key_fingerprint: str) -> str: - # First try to fetch the public key from Redis - public_key = self._redis.hget(self.REDIS_KEY_HASH, key_fingerprint) - if public_key: - return public_key - - # Then directly from GPG - public_key = self._gpg.export_keys(key_fingerprint) - if not public_key: - raise GpgKeyNotFoundError() - - self._redis.hset(self.REDIS_KEY_HASH, key_fingerprint, public_key) - return public_key diff --git a/securedrop/execution.py b/securedrop/execution.py deleted file mode 100644 index e9915ac7edb..00000000000 --- a/securedrop/execution.py +++ /dev/null @@ -1,13 +0,0 @@ -from threading import Thread - - -def asynchronous(f): # type: ignore - """ - Wraps a - """ - - def wrapper(*args, **kwargs): # type: ignore - thread = Thread(target=f, args=args, kwargs=kwargs) - thread.start() - - return wrapper diff --git a/securedrop/gpg_encryption.py b/securedrop/gpg_encryption.py new file mode 100644 index 00000000000..cfb3e7e2d53 --- /dev/null +++ b/securedrop/gpg_encryption.py @@ -0,0 +1,137 @@ +import os +import re +from distutils.version import StrictVersion +from pathlib import Path +from typing import Dict, Optional + +import pretty_bad_protocol as gnupg + + +def _monkey_patch_username_in_env() -> None: + # To fix https://github.com/freedomofpress/securedrop/issues/78 + os.environ["USERNAME"] = "www-data" + + +def _monkey_patch_unknown_status_message() -> None: + # To fix https://github.com/isislovecruft/python-gnupg/issues/250 with Focal gnupg + gnupg._parsers.Verify.TRUST_LEVELS["DECRYPTION_COMPLIANCE_MODE"] = 23 + + +def _monkey_patch_delete_handle_status() -> None: + # To fix https://github.com/freedomofpress/securedrop/issues/4294 + def _updated_handle_status(self: gnupg._parsers.DeleteResult, key: str, value: str) -> None: + """ + Parse a status code from the attached GnuPG process. + :raises: :exc:`~exceptions.ValueError` if the status message is unknown. + """ + if key in ("DELETE_PROBLEM", "KEY_CONSIDERED"): + self.status = self.problem_reason.get(value, "Unknown error: %r" % value) + elif key in ("PINENTRY_LAUNCHED"): + self.status = key.replace("_", " ").lower() + else: + raise ValueError("Unknown status message: %r" % key) + + gnupg._parsers.DeleteResult._handle_status = _updated_handle_status + + +def _setup_monkey_patches_for_gnupg() -> None: + _monkey_patch_username_in_env() + _monkey_patch_unknown_status_message() + _monkey_patch_delete_handle_status() + + +_setup_monkey_patches_for_gnupg() + + +class GpgKeyNotFoundError(Exception): + pass + + +class GpgEncryptError(Exception): + pass + + +class GpgDecryptError(Exception): + pass + + +_default_encryption_mgr: Optional["GpgEncryptionManager"] = None + + +class GpgEncryptionManager: + """Legacy gpg-based EncryptionManager used for migrating to Sequoia""" + + SOURCE_KEY_NAME = "Source Key" + SOURCE_KEY_UID_RE = re.compile(r"(Source|Autogenerated) Key <[-A-Za-z0-9+/=_]+>") + + def __init__(self, gpg_key_dir: Path, journalist_key_fingerprint: str) -> None: + self.gpg_key_dir = gpg_key_dir + self._journalist_key_fingerprint = journalist_key_fingerprint + + # Instantiate the "main" GPG binary + gpg = gnupg.GPG( + binary="gpg2", homedir=str(self.gpg_key_dir), options=["--trust-model direct"] + ) + if StrictVersion(gpg.binary_version) >= StrictVersion("2.1"): + # --pinentry-mode, required for SecureDrop on GPG 2.1.x+, was added in GPG 2.1. + self._gpg = gnupg.GPG( + binary="gpg2", + homedir=str(gpg_key_dir), + options=["--pinentry-mode loopback", "--trust-model direct"], + ) + else: + self._gpg = gpg + + # Instantiate the GPG binary to be used for key deletion: always delete keys without + # invoking pinentry-mode=loopback + # see: https://lists.gnupg.org/pipermail/gnupg-users/2016-May/055965.html + self._gpg_for_key_deletion = gnupg.GPG( + binary="gpg2", homedir=str(self.gpg_key_dir), options=["--yes", "--trust-model direct"] + ) + + @classmethod + def get_default(cls) -> "GpgEncryptionManager": + # Late import so the module can be used without a config.py in the parent folder + from sdconfig import config + + global _default_encryption_mgr + if _default_encryption_mgr is None: + _default_encryption_mgr = cls( + gpg_key_dir=Path(config.GPG_KEY_DIR), + journalist_key_fingerprint=config.JOURNALIST_KEY, + ) + return _default_encryption_mgr + + def delete_source_key_pair(self, source_filesystem_id: str) -> None: + source_key_fingerprint = self.get_source_key_fingerprint(source_filesystem_id) + + # The subkeys keyword argument deletes both secret and public keys + self._gpg_for_key_deletion.delete_keys(source_key_fingerprint, secret=True, subkeys=True) + + def get_journalist_public_key(self) -> str: + return self._get_public_key(self._journalist_key_fingerprint) + + def get_source_public_key( + self, source_filesystem_id: str, fingerprint: Optional[str] = None + ) -> str: + if fingerprint is None: + fingerprint = self.get_source_key_fingerprint(source_filesystem_id) + return self._get_public_key(fingerprint) + + def get_source_key_fingerprint(self, source_filesystem_id: str) -> str: + source_key_details = self._get_source_key_details(source_filesystem_id) + source_key_fingerprint = source_key_details["fingerprint"] + return source_key_fingerprint + + def _get_source_key_details(self, source_filesystem_id: str) -> Dict[str, str]: + for key in self._gpg.list_keys(): + for uid in key["uids"]: + if source_filesystem_id in uid and self.SOURCE_KEY_UID_RE.match(uid): + return key + raise GpgKeyNotFoundError() + + def _get_public_key(self, key_fingerprint: str) -> str: + public_key = self._gpg.export_keys(key_fingerprint) + if not public_key: + raise GpgKeyNotFoundError() + return public_key diff --git a/securedrop/journalist.py b/securedrop/journalist.py index 07757a2976d..0565b9b6410 100644 --- a/securedrop/journalist.py +++ b/securedrop/journalist.py @@ -1,7 +1,4 @@ -from encryption import EncryptionManager, GpgKeyNotFoundError -from execution import asynchronous from journalist_app import create_app -from models import Source from sdconfig import SecureDropConfig config = SecureDropConfig.get_current() @@ -9,21 +6,6 @@ app = create_app(config) -@asynchronous -def prime_keycache() -> None: - """Pre-load the source public keys into Redis.""" - with app.app_context(): - encryption_mgr = EncryptionManager.get_default() - for source in Source.query.filter_by(pending=False, deleted_at=None).all(): - try: - encryption_mgr.get_source_public_key(source.filesystem_id) - except GpgKeyNotFoundError: - pass - - -prime_keycache() - - if __name__ == "__main__": # pragma: no cover debug = getattr(config, "env", "prod") != "prod" # nosemgrep: python.flask.security.audit.app-run-param-config.avoid_app_run_with_bad_host diff --git a/securedrop/journalist_app/col.py b/securedrop/journalist_app/col.py index 773eb9a5802..c0a7f6c73f9 100644 --- a/securedrop/journalist_app/col.py +++ b/securedrop/journalist_app/col.py @@ -2,7 +2,6 @@ import werkzeug from db import db -from encryption import EncryptionManager, GpgKeyNotFoundError from flask import ( Blueprint, Markup, @@ -56,23 +55,13 @@ def remove_star(filesystem_id: str) -> werkzeug.Response: def col(filesystem_id: str) -> str: form = ReplyForm() source = get_source(filesystem_id) - try: - EncryptionManager.get_default().get_source_public_key(filesystem_id) - source.has_key = True - except GpgKeyNotFoundError: - source.has_key = False - return render_template("col.html", filesystem_id=filesystem_id, source=source, form=form) @view.route("/delete/", methods=("POST",)) def delete_single(filesystem_id: str) -> werkzeug.Response: """deleting a single collection from its /col page""" source = get_source(filesystem_id) - try: - delete_collection(filesystem_id) - except GpgKeyNotFoundError as e: - current_app.logger.error("error deleting collection: %s", e) - abort(500) + delete_collection(filesystem_id) flash( Markup( diff --git a/securedrop/journalist_app/main.py b/securedrop/journalist_app/main.py index 58976bf98a6..8b2d90f0770 100644 --- a/securedrop/journalist_app/main.py +++ b/securedrop/journalist_app/main.py @@ -144,7 +144,7 @@ def reply() -> werkzeug.Response: g.source.interaction_count, g.source.journalist_filename ) EncryptionManager.get_default().encrypt_journalist_reply( - for_source_with_filesystem_id=g.filesystem_id, + source=g.source, reply_in=form.message.data, encrypted_reply_path_out=Path(Storage.get_default().path(g.filesystem_id, filename)), ) diff --git a/securedrop/journalist_app/utils.py b/securedrop/journalist_app/utils.py index 24989c57751..23d9519742c 100644 --- a/securedrop/journalist_app/utils.py +++ b/securedrop/journalist_app/utils.py @@ -6,7 +6,6 @@ import flask import werkzeug from db import db -from encryption import EncryptionManager, GpgKeyNotFoundError from flask import Markup, abort, current_app, escape, flash, redirect, send_file, url_for from flask_babel import gettext, ngettext from journalist_app.sessions import session @@ -397,12 +396,6 @@ def delete_collection(filesystem_id: str) -> None: if os.path.exists(path): Storage.get_default().move_to_shredder(path) - # Delete the source's reply keypair, if it exists - try: - EncryptionManager.get_default().delete_source_key_pair(filesystem_id) - except GpgKeyNotFoundError: - pass - # Delete their entry in the db source = get_source(filesystem_id, include_deleted=True) db.session.delete(source) diff --git a/securedrop/journalist_templates/col.html b/securedrop/journalist_templates/col.html index 327d00a067d..8710be7dc6f 100644 --- a/securedrop/journalist_templates/col.html +++ b/securedrop/journalist_templates/col.html @@ -119,7 +119,7 @@

{{ source.journalist_designation }}

{{ gettext('Reply') }}

- {% if source.has_key %} + {% if source.public_key is not None %}

{{ gettext('You can write a secure reply to the person who submitted these messages and/or files:') }}

diff --git a/securedrop/loaddata.py b/securedrop/loaddata.py index c44af15e545..b9d13d64ed9 100755 --- a/securedrop/loaddata.py +++ b/securedrop/loaddata.py @@ -221,7 +221,7 @@ def add_reply( record_source_interaction(source) fname = f"{source.interaction_count}-{source.journalist_filename}-reply.gpg" EncryptionManager.get_default().encrypt_journalist_reply( - for_source_with_filesystem_id=source.filesystem_id, + source=source, reply_in=next(replies), encrypted_reply_path_out=Path(Storage.get_default().path(source.filesystem_id, fname)), ) @@ -252,9 +252,6 @@ def add_source() -> Tuple[Source, str]: source = source_user.get_db_record() db.session.commit() - # Generate source key - EncryptionManager.get_default().generate_source_key_pair(source_user) - return source, codename diff --git a/securedrop/models.py b/securedrop/models.py index cd4f0c15e1d..149056ebdd6 100644 --- a/securedrop/models.py +++ b/securedrop/models.py @@ -17,12 +17,11 @@ from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.kdf import scrypt from db import db -from encryption import EncryptionManager, GpgKeyNotFoundError from flask import url_for from flask_babel import gettext, ngettext from markupsafe import Markup from passphrases import PassphraseGenerator -from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, LargeBinary, String +from sqlalchemy import Boolean, Column, DateTime, ForeignKey, Integer, LargeBinary, String, Text from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import Query, backref, relationship from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound @@ -71,9 +70,24 @@ class Source(db.Model): # when deletion of the source was requested deleted_at = Column(DateTime) - def __init__(self, filesystem_id: str, journalist_designation: str) -> None: + # PGP key material + public_key = Column(Text, nullable=True) + private_key = Column(Text, nullable=True) + fingerprint = Column(String(40), nullable=True) + + def __init__( + self, + filesystem_id: str, + journalist_designation: str, + public_key: str, + private_key: str, + fingerprint: str, + ) -> None: self.filesystem_id = filesystem_id self.journalist_designation = journalist_designation + self.public_key = public_key + self.private_key = private_key + self.fingerprint = fingerprint self.uuid = str(uuid.uuid4()) def __repr__(self) -> str: @@ -105,20 +119,6 @@ def collection(self) -> "List[Union[Submission, Reply]]": collection.sort(key=lambda x: int(x.filename.split("-")[0])) return collection - @property - def fingerprint(self) -> "Optional[str]": - try: - return EncryptionManager.get_default().get_source_key_fingerprint(self.filesystem_id) - except GpgKeyNotFoundError: - return None - - @property - def public_key(self) -> "Optional[str]": - try: - return EncryptionManager.get_default().get_source_public_key(self.filesystem_id) - except GpgKeyNotFoundError: - return None - def to_json(self) -> "Dict[str, object]": docs_msg_count = self.documents_messages_count() diff --git a/securedrop/source_app/main.py b/securedrop/source_app/main.py index 2ddc490b4ed..a72555c46da 100644 --- a/securedrop/source_app/main.py +++ b/securedrop/source_app/main.py @@ -7,7 +7,7 @@ import store import werkzeug from db import db -from encryption import EncryptionManager, GpgKeyNotFoundError +from encryption import EncryptionManager from flask import ( Blueprint, abort, @@ -161,7 +161,9 @@ def lookup(logged_in_source: SourceUser) -> str: with open(reply_path, "rb") as f: contents = f.read() decrypted_reply = EncryptionManager.get_default().decrypt_journalist_reply( - for_source_user=logged_in_source, ciphertext_in=contents + source_user=logged_in_source, + source=logged_in_source_in_db, + ciphertext_in=contents, ) reply.decrypted = decrypted_reply except UnicodeDecodeError: diff --git a/securedrop/source_user.py b/securedrop/source_user.py index 7987d2bb8ab..c9bc935ff0a 100644 --- a/securedrop/source_user.py +++ b/securedrop/source_user.py @@ -12,6 +12,8 @@ from sqlalchemy.exc import IntegrityError from sqlalchemy.orm import Session +import redwood + if TYPE_CHECKING: from passphrases import DicewarePassphrase from store import Storage @@ -99,9 +101,18 @@ def create_source_user( # Could not generate a designation that is not already used raise SourceDesignationCollisionError() + # Generate PGP keys + public_key, private_key, fingerprint = redwood.generate_source_key_pair( + gpg_secret, filesystem_id + ) + # Store the source in the DB source_db_record = models.Source( - filesystem_id=filesystem_id, journalist_designation=valid_designation + filesystem_id=filesystem_id, + journalist_designation=valid_designation, + public_key=public_key, + private_key=private_key, + fingerprint=fingerprint, ) db_session.add(source_db_record) try: diff --git a/securedrop/store.py b/securedrop/store.py index 1ee015fd30e..c2322ae7699 100644 --- a/securedrop/store.py +++ b/securedrop/store.py @@ -346,9 +346,8 @@ def save_file_submission( if not buf: break gzf.write(buf) - EncryptionManager.get_default().encrypt_source_file( - file_in=stf, + filename=Path(stf.filepath), encrypted_file_path_out=Path(encrypted_file_path), ) diff --git a/securedrop/tests/conftest.py b/securedrop/tests/conftest.py index cf505068819..670b2020990 100644 --- a/securedrop/tests/conftest.py +++ b/securedrop/tests/conftest.py @@ -237,7 +237,6 @@ def test_source(journalist_app: Flask, app_storage: Storage) -> Dict[str, Any]: source_passphrase=passphrase, source_app_storage=app_storage, ) - EncryptionManager.get_default().generate_source_key_pair(source_user) source = source_user.get_db_record() return { "source_user": source_user, diff --git a/securedrop/tests/functional/conftest.py b/securedrop/tests/functional/conftest.py index d42d208a522..3e6cfbff16c 100644 --- a/securedrop/tests/functional/conftest.py +++ b/securedrop/tests/functional/conftest.py @@ -275,7 +275,6 @@ def create_source_and_submission(config_in_use: SecureDropConfig) -> Tuple[Sourc """ # This function will be called in a separate Process that runs the app # Hence the late imports - from encryption import EncryptionManager from models import Submission from passphrases import PassphraseGenerator from source_user import create_source_user @@ -291,7 +290,6 @@ def create_source_and_submission(config_in_use: SecureDropConfig) -> Tuple[Sourc source_app_storage=Storage.get_default(), ) source_db_record = source_user.get_db_record() - EncryptionManager.get_default().generate_source_key_pair(source_user) # Create a file submission from this source source_db_record.interaction_count += 1 diff --git a/securedrop/tests/migrations/migration_a5d61d9463ed.py b/securedrop/tests/migrations/migration_a5d61d9463ed.py new file mode 100644 index 00000000000..dec754cdf09 --- /dev/null +++ b/securedrop/tests/migrations/migration_a5d61d9463ed.py @@ -0,0 +1,42 @@ +class UpgradeTester: + def __init__(self, config): + """This function MUST accept an argument named `config`. + You will likely want to save a reference to the config in your + class so you can access the database later. + """ + self.config = config + + def load_data(self): + """This function loads data into the database and filesystem. It is + executed before the upgrade. + """ + pass + + def check_upgrade(self): + """This function is run after the upgrade and verifies the state + of the database or filesystem. It MUST raise an exception if the + check fails. + """ + pass + + +class DowngradeTester: + def __init__(self, config): + """This function MUST accept an argument named `config`. + You will likely want to save a reference to the config in your + class so you can access the database later. + """ + self.config = config + + def load_data(self): + """This function loads data into the database and filesystem. It is + executed before the downgrade. + """ + pass + + def check_downgrade(self): + """This function is run after the downgrade and verifies the state + of the database or filesystem. It MUST raise an exception if the + check fails. + """ + pass diff --git a/securedrop/tests/test_encryption.py b/securedrop/tests/test_encryption.py index 6d7bc36b374..1651be6d0f7 100644 --- a/securedrop/tests/test_encryption.py +++ b/securedrop/tests/test_encryption.py @@ -203,19 +203,17 @@ def test_encrypt_source_file(self, setup_journalist_key_and_gpg_folder, tmp_path # And a file to be submitted by a source - we use this python file file_to_encrypt_path = Path(__file__) - with file_to_encrypt_path.open() as file_to_encrypt: - - # When the source tries to encrypt the file - # It succeeds - encrypted_file_path = tmp_path / "file.gpg" - encryption_mgr.encrypt_source_file( - file_in=file_to_encrypt, - encrypted_file_path_out=encrypted_file_path, - ) + # When the source tries to encrypt the file + # It succeeds + encrypted_file_path = tmp_path / "file.gpg" + encryption_mgr.encrypt_source_file( + filename=file_to_encrypt_path, + encrypted_file_path_out=encrypted_file_path, + ) - # And the output file contains the encrypted data - encrypted_file = encrypted_file_path.read_bytes() - assert encrypted_file + # And the output file contains the encrypted data + encrypted_file = encrypted_file_path.read_bytes() + assert encrypted_file # And the journalist is able to decrypt the file with import_journalist_private_key(encryption_mgr): @@ -231,6 +229,7 @@ def test_encrypt_and_decrypt_journalist_reply( ): # Given a source user with a key pair in the default encryption manager source_user1 = test_source["source_user"] + source1 = test_source["source"] encryption_mgr = EncryptionManager.get_default() # And another source with a key pair in the default encryption manager @@ -247,7 +246,7 @@ def test_encrypt_and_decrypt_journalist_reply( journalist_reply = "s3cr3t message" encrypted_reply_path = tmp_path / "reply.gpg" encryption_mgr.encrypt_journalist_reply( - for_source_with_filesystem_id=source_user1.filesystem_id, + source=source1, reply_in=journalist_reply, encrypted_reply_path_out=encrypted_reply_path, ) @@ -258,7 +257,7 @@ def test_encrypt_and_decrypt_journalist_reply( # And source1 is able to decrypt the reply decrypted_reply = encryption_mgr.decrypt_journalist_reply( - for_source_user=source_user1, ciphertext_in=encrypted_reply + source_user=source_user1, source=source1, ciphertext_in=encrypted_reply ) assert decrypted_reply assert decrypted_reply == journalist_reply @@ -266,7 +265,9 @@ def test_encrypt_and_decrypt_journalist_reply( # And source2 is NOT able to decrypt the reply with pytest.raises(GpgDecryptError): encryption_mgr.decrypt_journalist_reply( - for_source_user=source_user2, ciphertext_in=encrypted_reply + source_user=source_user2, + source=source_user2.get_db_record(), + ciphertext_in=encrypted_reply, ) # Amd the reply can't be decrypted without providing the source1's gpg secret diff --git a/securedrop/tests/test_journalist.py b/securedrop/tests/test_journalist.py index 5501a342929..7e13dc6be40 100644 --- a/securedrop/tests/test_journalist.py +++ b/securedrop/tests/test_journalist.py @@ -15,7 +15,6 @@ import journalist_app as journalist_app_module import pytest from db import db -from encryption import EncryptionManager, GpgKeyNotFoundError from flaky import flaky from flask import escape, g, url_for from flask_babel import gettext, ngettext @@ -2868,28 +2867,6 @@ def test_delete_collection_updates_db(journalist_app, test_journo, test_source, assert not seen_reply -def test_delete_source_deletes_source_key(journalist_app, test_source, test_journo, app_storage): - """Verify that when a source is deleted, the PGP key that corresponds - to them is also deleted.""" - - with journalist_app.app_context(): - source = Source.query.get(test_source["id"]) - journo = Journalist.query.get(test_journo["id"]) - - utils.db_helper.submit(app_storage, source, 2) - utils.db_helper.reply(app_storage, journo, source, 2) - - # Source key exists - encryption_mgr = EncryptionManager.get_default() - assert encryption_mgr.get_source_key_fingerprint(test_source["filesystem_id"]) - - journalist_app_module.utils.delete_collection(test_source["filesystem_id"]) - - # Source key no longer exists - with pytest.raises(GpgKeyNotFoundError): - encryption_mgr.get_source_key_fingerprint(test_source["filesystem_id"]) - - def test_delete_source_deletes_docs_on_disk( journalist_app, test_source, test_journo, config, app_storage ): diff --git a/securedrop/tests/utils/db_helper.py b/securedrop/tests/utils/db_helper.py index 55998f037df..111f4a416b0 100644 --- a/securedrop/tests/utils/db_helper.py +++ b/securedrop/tests/utils/db_helper.py @@ -74,7 +74,7 @@ def reply(storage, journalist, source, num_replies): fname = f"{source.interaction_count}-{source.journalist_filename}-reply.gpg" EncryptionManager.get_default().encrypt_journalist_reply( - for_source_with_filesystem_id=source.filesystem_id, + source=source, reply_in=str(os.urandom(1)), encrypted_reply_path_out=storage.path(source.filesystem_id, fname), ) @@ -104,7 +104,6 @@ def init_source(storage): source_passphrase=passphrase, source_app_storage=storage, ) - EncryptionManager.get_default().generate_source_key_pair(source_user) return source_user.get_db_record(), passphrase