rpgpie-0.5.4/.cargo_vcs_info.json0000644000000001360000000000100123300ustar { "git": { "sha1": "f1cc0d8c634a4d8af363d1500b187449e791bd49" }, "path_in_vcs": "" }rpgpie-0.5.4/.gitignore000064400000000000000000000001731046102023000131110ustar 00000000000000# SPDX-FileCopyrightText: Heiko Schaefer # SPDX-License-Identifier: CC0-1.0 Cargo.lock target .idea rpgpie-0.5.4/Cargo.lock0000644000001205750000000000100103150ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 4 [[package]] name = "adler2" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" [[package]] name = "aead" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ "crypto-common", "generic-array", ] [[package]] name = "aes" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" dependencies = [ "cfg-if", "cipher", "cpufeatures", ] [[package]] name = "aes-gcm" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" dependencies = [ "aead", "aes", "cipher", "ctr", "ghash", "subtle", ] [[package]] name = "aes-kw" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69fa2b352dcefb5f7f3a5fb840e02665d311d878955380515e4fd50095dd3d8c" dependencies = [ "aes", ] [[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "argon2" version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" dependencies = [ "base64ct", "blake2", "cpufeatures", "password-hash", "zeroize", ] [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "base16ct" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] name = "bitfield" version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f798d2d157e547aa99aab0967df39edd0b70307312b6f8bd2848e6abe40896e0" [[package]] name = "blake2" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ "digest", ] [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "block-padding" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8894febbff9f758034a5b8e12d87918f56dfc64a8e1fe757d65e29041538d93" dependencies = [ "generic-array", ] [[package]] name = "blowfish" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e412e2cd0f2b2d93e02543ceae7917b3c70331573df19ee046bcbc35e45e87d7" dependencies = [ "byteorder", "cipher", ] [[package]] name = "bstr" version = "1.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" dependencies = [ "memchr", "serde", ] [[package]] name = "buffer-redux" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e8acf87c5b9f5897cd3ebb9a327f420e0cae9dd4e5c1d2e36f2c84c571a58f1" dependencies = [ "memchr", ] [[package]] name = "bumpalo" version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "camellia" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3264e2574e9ef2b53ce6f536dea83a69ac0bc600b762d1523ff83fe07230ce30" dependencies = [ "byteorder", "cipher", ] [[package]] name = "cast5" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26b07d673db1ccf000e90f54b819db9e75a8348d6eb056e9b8ab53231b7a9911" dependencies = [ "cipher", ] [[package]] name = "cc" version = "1.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" dependencies = [ "shlex", ] [[package]] name = "cfb-mode" version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "738b8d467867f80a71351933f70461f5b56f24d5c93e0cf216e59229c968d330" dependencies = [ "cipher", ] [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" version = "0.4.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "wasm-bindgen", "windows-targets", ] [[package]] name = "cipher" version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", ] [[package]] name = "cmac" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8543454e3c3f5126effff9cd44d562af4e31fb8ce1cc0d3dcd8f084515dbc1aa" dependencies = [ "cipher", "dbl", "digest", ] [[package]] name = "const-oid" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crc24" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd121741cf3eb82c08dd3023eb55bf2665e5f60ec20f89760cf836ae4562e6a0" [[package]] name = "crc32fast" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "crypto-bigint" version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ "generic-array", "rand_core 0.6.4", "subtle", "zeroize", ] [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "rand_core 0.6.4", "typenum", ] [[package]] name = "ctr" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ "cipher", ] [[package]] name = "curve25519-dalek" version = "4.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" dependencies = [ "cfg-if", "cpufeatures", "curve25519-dalek-derive", "digest", "fiat-crypto 0.2.9", "rustc_version", "subtle", "zeroize", ] [[package]] name = "curve25519-dalek-derive" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "darling" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" dependencies = [ "darling_core", "darling_macro", ] [[package]] name = "darling_core" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", "syn", ] [[package]] name = "darling_macro" version = "0.20.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", "syn", ] [[package]] name = "dbl" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd2735a791158376708f9347fe8faba9667589d82427ef3aed6794a8981de3d9" dependencies = [ "generic-array", ] [[package]] name = "der" version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" dependencies = [ "const-oid", "pem-rfc7468", "zeroize", ] [[package]] name = "derive_builder" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947" dependencies = [ "derive_builder_macro", ] [[package]] name = "derive_builder_core" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8" dependencies = [ "darling", "proc-macro2", "quote", "syn", ] [[package]] name = "derive_builder_macro" version = "0.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c" dependencies = [ "derive_builder_core", "syn", ] [[package]] name = "derive_more" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" dependencies = [ "proc-macro2", "quote", "syn", "unicode-xid", ] [[package]] name = "des" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffdd80ce8ce993de27e9f063a444a4d53ce8e8db4c1f00cc03af5ad5a9867a1e" dependencies = [ "cipher", ] [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "const-oid", "crypto-common", "subtle", ] [[package]] name = "dsa" version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48bc224a9084ad760195584ce5abb3c2c34a225fa312a128ad245a6b412b7689" dependencies = [ "digest", "num-bigint-dig", "num-traits", "pkcs8", "rfc6979", "sha2", "signature", "zeroize", ] [[package]] name = "eax" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9954fabd903b82b9d7a68f65f97dc96dd9ad368e40ccc907a7c19d53e6bfac28" dependencies = [ "aead", "cipher", "cmac", "ctr", "subtle", ] [[package]] name = "ecdsa" version = "0.16.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" dependencies = [ "der", "digest", "elliptic-curve", "rfc6979", "signature", "spki", ] [[package]] name = "ed25519" version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ "pkcs8", "signature", ] [[package]] name = "ed25519-dalek" version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" dependencies = [ "curve25519-dalek", "ed25519", "serde", "sha2", "subtle", "zeroize", ] [[package]] name = "ed448-goldilocks" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87b5fa9e9e3dd5fe1369f380acd3dcdfa766dbd0a1cd5b048fb40e38a6a78e79" dependencies = [ "fiat-crypto 0.1.20", "hex", "subtle", ] [[package]] name = "elliptic-curve" version = "0.13.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" dependencies = [ "base16ct", "crypto-bigint", "digest", "ff", "generic-array", "group", "hkdf", "pem-rfc7468", "pkcs8", "rand_core 0.6.4", "sec1", "subtle", "zeroize", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "ff" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" dependencies = [ "rand_core 0.6.4", "subtle", ] [[package]] name = "fiat-crypto" version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" [[package]] name = "fiat-crypto" version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" [[package]] name = "flate2" version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c936bfdafb507ebbf50b8074c54fa31c5be9a1e7e5f467dd659697041407d07c" dependencies = [ "crc32fast", "miniz_oxide", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", "zeroize", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "ghash" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" dependencies = [ "opaque-debug", "polyval", ] [[package]] name = "group" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", "rand_core 0.6.4", "subtle", ] [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" [[package]] name = "hkdf" version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ "hmac", ] [[package]] name = "hmac" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ "digest", ] [[package]] name = "iana-time-zone" version = "0.1.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", "windows-core", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "idea" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "075557004419d7f2031b8bb7f44bb43e55a83ca7b63076a8fb8fe75753836477" dependencies = [ "cipher", ] [[package]] name = "ident_case" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "indexmap" version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "inout" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" dependencies = [ "generic-array", ] [[package]] name = "iter-read" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071ed4cc1afd86650602c7b11aa2e1ce30762a1c27193201cb5cee9c6ebb1294" [[package]] name = "js-sys" version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ "once_cell", "wasm-bindgen", ] [[package]] name = "k256" version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6e3919bbaa2945715f0bb6d3934a173d1e9a59ac23767fbaaef277265a7411b" dependencies = [ "cfg-if", "ecdsa", "elliptic-curve", "once_cell", "sha2", "signature", ] [[package]] name = "keccak" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ "cpufeatures", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ "spin", ] [[package]] name = "libc" version = "0.2.169" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a" [[package]] name = "libm" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" [[package]] name = "log" version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" [[package]] name = "md-5" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" dependencies = [ "cfg-if", "digest", ] [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "minimal-lexical" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924" dependencies = [ "adler2", ] [[package]] name = "nom" version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", ] [[package]] name = "num-bigint-dig" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" dependencies = [ "byteorder", "lazy_static", "libm", "num-integer", "num-iter", "num-traits", "rand", "serde", "smallvec", "zeroize", ] [[package]] name = "num-integer" version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ "num-traits", ] [[package]] name = "num-iter" version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", "libm", ] [[package]] name = "num_enum" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" dependencies = [ "num_enum_derive", ] [[package]] name = "num_enum_derive" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", "syn", ] [[package]] name = "ocb3" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c196e0276c471c843dd5777e7543a36a298a4be942a2a688d8111cd43390dedb" dependencies = [ "aead", "cipher", "ctr", "subtle", ] [[package]] name = "once_cell" version = "1.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "opaque-debug" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" [[package]] name = "p256" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9863ad85fa8f4460f9c48cb909d38a0d689dba1f6f6988a5e3e0d31071bcd4b" dependencies = [ "ecdsa", "elliptic-curve", "primeorder", "sha2", ] [[package]] name = "p384" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" dependencies = [ "ecdsa", "elliptic-curve", "primeorder", "sha2", ] [[package]] name = "p521" version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fc9e2161f1f215afdfce23677034ae137bbd45016a880c2eb3ba8eb95f085b2" dependencies = [ "base16ct", "ecdsa", "elliptic-curve", "primeorder", "rand_core 0.6.4", "sha2", ] [[package]] name = "password-hash" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" dependencies = [ "base64ct", "rand_core 0.6.4", "subtle", ] [[package]] name = "pem-rfc7468" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" dependencies = [ "base64ct", ] [[package]] name = "pgp" version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30249ac8a98b356b473b04bc5358c75a260aa96a295d0743ce752fe7b173f235" dependencies = [ "aes", "aes-gcm", "aes-kw", "argon2", "base64", "bitfield", "block-padding", "blowfish", "bstr", "buffer-redux", "byteorder", "camellia", "cast5", "cfb-mode", "chrono", "cipher", "const-oid", "crc24", "curve25519-dalek", "derive_builder", "derive_more", "des", "digest", "dsa", "eax", "ecdsa", "ed25519-dalek", "elliptic-curve", "flate2", "generic-array", "hex", "hkdf", "idea", "iter-read", "k256", "log", "md-5", "nom", "num-bigint-dig", "num-traits", "num_enum", "ocb3", "p256", "p384", "p521", "rand", "ripemd", "rsa", "sha1", "sha1-checked", "sha2", "sha3", "signature", "smallvec", "thiserror", "twofish", "x25519-dalek", "x448", "zeroize", ] [[package]] name = "pkcs1" version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" dependencies = [ "der", "pkcs8", "spki", ] [[package]] name = "pkcs8" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" dependencies = [ "der", "spki", ] [[package]] name = "polyval" version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" dependencies = [ "cfg-if", "cpufeatures", "opaque-debug", "universal-hash", ] [[package]] name = "ppv-lite86" version = "0.2.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" dependencies = [ "zerocopy", ] [[package]] name = "primeorder" version = "0.13.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" dependencies = [ "elliptic-curve", ] [[package]] name = "proc-macro-crate" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" version = "1.0.93" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" dependencies = [ "unicode-ident", ] [[package]] name = "quote" version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core 0.6.4", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core 0.6.4", ] [[package]] name = "rand_core" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "rfc6979" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ "hmac", "subtle", ] [[package]] name = "ripemd" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" dependencies = [ "digest", ] [[package]] name = "rpgpie" version = "0.5.4" dependencies = [ "chrono", "log", "pgp", "rand", "rand_core 0.6.4", "thiserror", "zeroize", ] [[package]] name = "rsa" version = "0.9.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47c75d7c5c6b673e58bf54d8544a9f432e3a925b0e80f7cd3602ab5c50c55519" dependencies = [ "const-oid", "digest", "num-bigint-dig", "num-integer", "num-traits", "pkcs1", "pkcs8", "rand_core 0.6.4", "signature", "spki", "subtle", "zeroize", ] [[package]] name = "rustc_version" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] [[package]] name = "rustversion" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "sec1" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", "der", "generic-array", "pkcs8", "subtle", "zeroize", ] [[package]] name = "semver" version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03" [[package]] name = "serde" version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.217" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "sha1" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "sha1-checked" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423" dependencies = [ "digest", "sha1", "zeroize", ] [[package]] name = "sha2" version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "sha3" version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ "digest", "keccak", ] [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signature" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" dependencies = [ "digest", "rand_core 0.6.4", ] [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "spki" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" dependencies = [ "base64ct", "der", ] [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" version = "2.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "thiserror" version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d452f284b73e6d76dd36758a0c8684b1d5be31f92b89d07fd5822175732206fc" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26afc1baea8a989337eeb52b6e72a039780ce45c3edfcc9c5b9d112feeb173c2" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" version = "0.22.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", "toml_datetime", "winnow", ] [[package]] name = "twofish" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a78e83a30223c757c3947cd144a31014ff04298d8719ae10d03c31c0448c8013" dependencies = [ "cipher", ] [[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" [[package]] name = "unicode-ident" version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" [[package]] name = "unicode-xid" version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" [[package]] name = "universal-hash" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ "crypto-common", "subtle", ] [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[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.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" dependencies = [ "unicode-ident", ] [[package]] name = "windows-core" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" version = "0.6.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ad699df48212c6cc6eb4435f35500ac6fd3b9913324f938aea302022ce19d310" dependencies = [ "memchr", ] [[package]] name = "x25519-dalek" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" dependencies = [ "curve25519-dalek", "rand_core 0.6.4", "serde", "zeroize", ] [[package]] name = "x448" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4cd07d4fae29e07089dbcacf7077cd52dce7760125ca9a4dd5a35ca603ffebb" dependencies = [ "ed448-goldilocks", "hex", "rand_core 0.5.1", ] [[package]] name = "zerocopy" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" dependencies = [ "byteorder", "zerocopy-derive", ] [[package]] name = "zerocopy-derive" version = "0.7.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", "syn", ] rpgpie-0.5.4/Cargo.toml0000644000000024170000000000100103320ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "rpgpie" version = "0.5.4" authors = ["Heiko Schaefer "] build = false autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "Experimental high level API for rPGP" readme = "README.md" license = "MIT OR Apache-2.0" repository = "https://codeberg.org/heiko/rpgpie" [lib] name = "rpgpie" path = "src/lib.rs" [dependencies.chrono] version = "0.4" [dependencies.log] version = "0.4" [dependencies.pgp] version = "0.15" [dependencies.rand] version = "0.8" [dependencies.rand_core] version = "0.6" [dependencies.thiserror] version = "2" [dependencies.zeroize] version = "1" [features] unstable-curve448 = ["pgp/unstable-curve448"] [lints.clippy] expect_used = "warn" panic = "warn" todo = "warn" unwrap_used = "warn" rpgpie-0.5.4/Cargo.toml.orig000064400000000000000000000012331046102023000140060ustar 00000000000000# SPDX-FileCopyrightText: Heiko Schaefer # SPDX-License-Identifier: CC0-1.0 [package] name = "rpgpie" description = "Experimental high level API for rPGP" license = "MIT OR Apache-2.0" version = "0.5.4" authors = ["Heiko Schaefer "] edition = "2021" repository = "https://codeberg.org/heiko/rpgpie" [lib] [dependencies] chrono = "0.4" log = "0.4" pgp = "0.15" rand = "0.8" rand_core = "0.6" thiserror = "2" zeroize = "1" [lints.clippy] #unimplemented = "warn" panic = "warn" expect_used = "warn" todo = "warn" unwrap_used = "warn" [features] # Enables x448 support in rpgp unstable-curve448 = ["pgp/unstable-curve448"] rpgpie-0.5.4/LICENSES/Apache-2.0.txt000064400000000000000000000240451046102023000145510ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS Copyright 2021 Heiko Schäfer Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. rpgpie-0.5.4/LICENSES/CC0-1.0.txt000064400000000000000000000154041046102023000137330ustar 00000000000000Creative Commons Legal Code CC0 1.0 Universal CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER. Statement of Purpose The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work"). Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others. For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights. 1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: i. the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work; ii. moral rights retained by the original author(s) and/or performer(s); iii. publicity and privacy rights pertaining to a person's image or likeness depicted in a Work; iv. rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below; v. rights protecting the extraction, dissemination, use and reuse of data in a Work; vi. database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and vii. other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof. 2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose. 3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose. 4. Limitations and Disclaimers. a. No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document. b. Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law. c. Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work. d. Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work. rpgpie-0.5.4/LICENSES/MIT.txt000064400000000000000000000020701046102023000135160ustar 00000000000000The MIT License (MIT) Copyright (c) 2021 Heiko Schäfer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. rpgpie-0.5.4/README.md000064400000000000000000000044441046102023000124050ustar 00000000000000 # rpgpie 🦀️🔐🥧 A higher-level [OpenPGP](https://openpgp.dev/book/) API based on [rPGP](https://github.com/rpgp/rpgp/). rpgpie is an experimental wrapping API on top of the rPGP library. Main goals of rpgpie include simplicity, collaboration and fun 🥳. ## Objectives of rpgpie More concretely, rpgpie currently has the following objectives: - Expose an API to implement [stateless OpenPGP (SOP)](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/) functionality (see [rsop](https://crates.io/crates/rsop)). - Experiment with applying the terminology and conceptual framing of the ["OpenPGP for application developers"](https://openpgp.dev/) documentation. - Experiment with high level API design for OpenPGP. - Add a basic policy to rPGP (e.g. to limit accepted algorithms). ## Limitations and non-objectives rpgpie does not currently process messages in an efficient, streaming manner. Messages that are too large to be conveniently processed in RAM can currently not be handled with rpgpie. The rpgpie API currently limits itself to using certificates (also known as "OpenPGP public keys") and TSKs (also known as "OpenPGP secret/private keys") as they are. Updating or altering certificates or TSKs is currently out of scope. For the current phase of exploration, proper error handling is not a goal of rpgpie. The code may panic in all kinds of circumstances. API stability is not a goal in the current phase of development. ## Technical Details Rpgpie implements some higher-order OpenPGP facilities: - **Certificate-level**: - A policy on cryptographic primitives (to reject use of weak algorithms). - Asserting [validity](https://openpgp.dev/book/verification.html) of individual OpenPGP signature packets. - Evaluating stacks of OpenPGP signatures (which make statements about the same component) over time. - OpenPGP semantics guarantees on the certificate level (evaluating self-signature graphs). - **Message-level**: - Generate/validate data signatures, - Encrypt/decrypt data. - Apply a policy for acceptable cryptographic mechanisms. ## Warning, early-stage project! The code in this project is NOT currently intended for production use. rpgpie-0.5.4/rustfmt.toml000064400000000000000000000002511046102023000135170ustar 00000000000000# SPDX-FileCopyrightText: Heiko Schaefer # SPDX-License-Identifier: CC0-1.0 group_imports = "StdExternalCrate" format_code_in_doc_comments = true rpgpie-0.5.4/src/certificate.rs000064400000000000000000001112511046102023000145400ustar 00000000000000// SPDX-FileCopyrightText: Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Wrapper types and logic for certificates, aka OpenPGP public keys ("transferable public keys"). use std::io; use std::ops::Add; use chrono::{DateTime, Duration, Utc}; use pgp::cleartext::CleartextSignedMessage; use pgp::crypto::aead::AeadAlgorithm; use pgp::crypto::hash::HashAlgorithm; use pgp::crypto::sym::SymmetricKeyAlgorithm; use pgp::packet::{SubpacketData, SubpacketType, UserId}; use pgp::ser::Serialize; use pgp::types::{Fingerprint, KeyId, PublicKeyTrait, SignedUser, SignedUserAttribute, Tag}; use pgp::{ armor, ArmorOptions, PublicOrSecret, Signature, SignedKeyDetails, SignedPublicKey, SignedPublicSubKey, StandaloneSignature, }; use crate::key::{ComponentKeyPub, SignedComponentKey, SignedComponentKeyPub}; use crate::signature::SigStack; use crate::tsk::Tsk; use crate::{policy, signature, Error}; /// A "certificate," also known as an "OpenPGP public key." #[derive(Debug, Clone)] pub struct Certificate { spk: SignedPublicKey, } impl From for Certificate { fn from(spk: SignedPublicKey) -> Self { Self { spk } } } impl TryFrom<&[u8]> for Certificate { type Error = Error; fn try_from(input: &[u8]) -> Result { use pgp::Deserializable; let (spk, _) = SignedPublicKey::from_reader_single(input)?; Ok(Self { spk }) } } impl TryFrom<&Certificate> for Vec { type Error = Error; fn try_from(value: &Certificate) -> Result { Ok(value.spk().to_bytes()?) } } impl From<&Tsk> for Certificate { fn from(value: &Tsk) -> Self { Certificate::from(SignedPublicKey::from(value.key().clone())) } } /// pick the user id we consider primary fn pick_primary_user_id<'a>(users: &'_ [&'a SignedUser]) -> Option<&'a SignedUser> { match users.len() { 0 => None, 1 => Some(users[0]), _ => { // FIXME: pick the user id with the most recent self-signature? // (ignoring revoked user ids, unless all are revoked) let mut best = users[0]; let now = Utc::now(); // FIXME? let stack = SigStack::from_iter(&best.signatures); let mut best_is_revoked = stack.revoked_at(&now); // let mut best_latest_sig = stack.active_at(Some(&now)); for user in users { let stack = SigStack::from_iter(&user.signatures); let this_is_revoked = stack.revoked_at(&now); // let this_latest_sig = stack.active_at(Some(&now)); if best_is_revoked && !this_is_revoked { // replace best = user; best_is_revoked = this_is_revoked; // best_latest_sig = this_latest_sig; } } Some(best) } } } /// Return primary User ID: /// /// We prefer the User ID with the most recent self-signature. /// /// Either from among all User IDs that are marked "primary". /// Or from among all User IDs if none are marked "primary". /// /// (Note: we don't implement looking up the primary User ID at a reference time!) fn primary_user_id(details: &SignedKeyDetails) -> Option<&SignedUser> { // Find all User IDs that are marked "primary" let primaries: Vec<_> = details.users.iter().filter(|u| u.is_primary()).collect(); if !primaries.is_empty() { // choose between all that are marked "primary" pick_primary_user_id(&primaries) } else { // No User ID was marked "primary", we choose among all User IDs. let users: Vec<_> = details.users.iter().collect(); pick_primary_user_id(&users) } } /// Returns the latest signature of the primary User ID. /// None, if no primary User ID or binding signature for it are found. pub(crate) fn primary_user_id_binding_at<'a>( details: &'a SignedKeyDetails, reference: &'a DateTime, ) -> Option<&'a Signature> { primary_user_id(details) .and_then(|uid| SigStack::from_iter(uid.signatures.iter()).active_at(Some(reference))) } impl Certificate { /// The fingerprint of this certificate (i.e. the fingerprint of its primary key) pub fn fingerprint(&self) -> Fingerprint { self.spk.primary_key.fingerprint() } /// Load a set of `Certificate`s from a source. /// /// The source data may be armored or binary. pub fn load(source: &mut R) -> Result, Error> { let mut certs = vec![]; let (parsed, _headers) = pgp::composed::signed_key::from_reader_many(source)?; for res in parsed { match res { Ok(pos) => { let cert = match pos { PublicOrSecret::Public(spk) => spk.into(), PublicOrSecret::Secret(_ssk) => { return Err(Error::Message( "Expected Certificate(s), got TSK".to_string(), )); } }; certs.push(cert); } Err(_) => eprintln!("Bad data {:?}", res), } } if certs.is_empty() { Err(Error::Message("No certificates found".to_string())) } else { Ok(certs) } } /// Save this Certificate to a writer pub fn save(&self, armored: bool, sink: &mut dyn io::Write) -> Result<(), Error> { Self::save_all([self], armored, sink) } /// Save a set of Certificates to a writer pub fn save_all<'a>( certs: impl IntoIterator, armored: bool, mut sink: &mut dyn io::Write, ) -> Result<(), Error> { if armored { let spks: Vec<_> = certs.into_iter().map(|c| c.spk()).collect(); let opts = ArmorOptions::default(); armor::write( &spks, armor::BlockType::PublicKey, &mut sink, opts.headers, opts.include_checksum, )?; } else { for c in certs { c.spk().to_writer(&mut sink)?; } } Ok(()) } pub(crate) fn spk(&self) -> &SignedPublicKey { &self.spk } fn component_keys(&self) -> Vec { let x = SignedComponentKeyPub::Primary(( self.spk.clone(), self.spk.details.direct_signatures.clone(), )); let mut v = vec![SignedComponentKey::Pub(x)]; self.spk.public_subkeys.iter().for_each(|spsk| { let dks = self.spk.details.direct_signatures.clone(); let x = SignedComponentKeyPub::Subkey((spsk.clone(), dks)); v.push(SignedComponentKey::Pub(x)); }); v } /// Get list of all decryption-capable component keys. /// /// This fn is intended to signal potential use *decryption*, not encryption! /// It is *very* lenient in what it lists. It only checks for key flags, but doesn't check /// for validity in any way (e.g. it doesn't check for a correct subkey binding signature). pub fn decryption_capable_component_keys( &self, ) -> impl Iterator + '_ { let now: DateTime = chrono::offset::Utc::now(); // FIXME: filter out unknown notations self.component_keys() .into_iter() .filter(move |sck| sck.is_encryption_capable(&now)) } } /// Verify data signatures #[derive(Debug)] pub struct SignatureVerifier { ckey: ComponentKeyPub, primary_creation: DateTime, } impl SignatureVerifier { pub(crate) fn new(ckey: ComponentKeyPub, primary_creation: DateTime) -> Self { Self { ckey, primary_creation, } } /// Verify a data signature /// /// Rejects the signature if its creation predates either the verifier or its primary pub fn verify(&self, signature: &Signature, data: &[u8]) -> Result<(), Error> { let Some(created) = signature.created() else { // this should not happen unimplemented!("FIXME"); }; // reject if signature timestamp is before ckey or primary creation time if created < &self.primary_creation || created < self.ckey.created_at() { return Err(Error::Message( "Data signature predates key creation".to_string(), )); } self.ckey.verify(signature, data) } /// Verify a cleartext data signature pub fn verify_csf(&self, csf: &CleartextSignedMessage) -> Result { // FIXME: reject if signature timestamp is before ckey or primary creation time // FIXME: the csf can contain many signatures, how does that fit with this interface? match &self.ckey { ComponentKeyPub::Primary(pk) => Ok(csf.verify(pk)?), ComponentKeyPub::Subkey(psk) => Ok(csf.verify(psk)?), } .cloned() } /// Return the underlying component public key for this verifier pub fn as_componentkey(&self) -> &ComponentKeyPub { &self.ckey } } /// A layer on top of [Certificate] that filters out any signatures that we don't consider valid. /// /// A `Checked` certificate only contains Signatures that have been verified to be cryptographically /// correct. /// See /// /// This is a precondition for signatures being considered fully valid, but by itself insufficient. /// /// Note: This function currently removes all third-party signatures /// (because they can't be cryptographically checked in the context of looking at an individual /// certificate). #[derive(Debug, Clone, PartialEq, Eq)] pub struct Checked { original: SignedPublicKey, cspk: SignedPublicKey, primary_fingerprint: Fingerprint, // cached value for efficient third-party fp checks primary_key_id: KeyId, // cached value for efficient third-party fp checks } impl Checked { /// Produce a `Checked` view onto a `Certificate`. /// /// This operation checks all self-signatures for cryptographic validity, as well as /// conformance to policy. /// /// Third party signatures are not currently handled. pub fn new(cert: &Certificate) -> Self { // Make a copy of all parts of the SignedPublicKey and drop all signatures that: // - are not cryptographically correct // - don't pass our policy checks (`signature_acceptable`) // TODO: store bad self-sigs and third-party sigs in a separate place // TODO: go look for any (valid/correct) hard revocations for the full cert, // and encode the result in a simple "cert is hard revoked" flag: // // -> any user id binding that claims to be primary // -> or direct signatures on the primary // TODO: Which parts of validity checking can/should be moved to [SigStack]? let spk = &cert.spk; // primary let primary = spk.primary_key.clone(); let primary_created = primary.created_at(); // details let revocation_signatures: Vec<_> = spk .details .revocation_signatures .iter() .filter(|s| signature::signature_acceptable(s)) // FIXME: could this filter out any important revocations? .filter(|s| signature::not_older_than(s, primary_created)) .filter(|s| s.verify_key(&spk.primary_key).is_ok()) .cloned() .collect(); let direct_signatures: Vec<_> = spk .details .direct_signatures .iter() .filter(|s| signature::signature_acceptable(s)) .filter(|s| signature::not_older_than(s, primary_created)) .filter(|s| s.verify_key(&spk.primary_key).is_ok()) .cloned() .collect(); let users: Vec = spk .details .users .iter() .map(|su| { let id = su.id.clone(); let signatures = su .signatures .iter() .filter(|s| signature::signature_acceptable(s)) .filter(|s| signature::not_older_than(s, primary_created)) .filter(|s| s.verify_certification(&primary, Tag::UserId, &id).is_ok()) .cloned() .collect(); SignedUser { id, signatures } }) .collect(); let user_attributes: Vec = spk .details .user_attributes .iter() .map(|sua| { let attr = sua.attr.clone(); let signatures = sua .signatures .iter() .filter(|s| signature::signature_acceptable(s)) .filter(|s| signature::not_older_than(s, primary_created)) .filter(|s| { s.verify_certification(&primary, Tag::UserAttribute, &attr) .is_ok() }) .cloned() .collect(); SignedUserAttribute { attr, signatures } }) .collect(); let details = SignedKeyDetails::new( revocation_signatures, direct_signatures, users, user_attributes, ); // subkeys let subkeys = spk .public_subkeys .iter() .map(|sk| { let key = sk.key.clone(); let signatures = sk .signatures .iter() .filter(|s| signature::signature_acceptable(s)) .filter(|s| signature::not_older_than(s, primary_created)) .filter(|s| { if let Err(e) = s.verify_key_binding(&spk.primary_key, &key) { log::warn!("Ignoring bad key binding signature {:#?}: {:?}", s, e); return false; } // If there is an embedded back signature, we reject the entire signature if the back signature is cryptographically invalid for embedded in s .config .hashed_subpackets .iter() .filter(|sp| sp.typ() == SubpacketType::EmbeddedSignature) { match &embedded.data { SubpacketData::EmbeddedSignature(backsig) => { if !signature::signature_acceptable(backsig) { log::warn!( "Ignoring signature because of unacceptable embedded signature {:#?}", backsig ); return false; } // FIXME: reject if signature creation time is before subkey creation time if let Err(e) = backsig.verify_backwards_key_binding( &key, &spk.primary_key, ) { log::warn!( "Ignoring signature because embedded signature doesn't verify as correct {:#?}: {:?}", backsig, e ); return false; } } _ => unreachable!("no other subpacket types should come up, here"), } } true }) .cloned() .collect(); SignedPublicSubKey { key, signatures } }) .collect(); // combine let cspk = SignedPublicKey::new(primary, details, subkeys); log::debug!("Checked cert: {:#?}", cspk); Self { original: spk.clone(), cspk, primary_key_id: spk.primary_key.key_id(), primary_fingerprint: spk.primary_key.fingerprint(), } } pub fn primary_key(&self) -> SignedComponentKeyPub { let mut sigs: Vec<_> = self.cspk.details.revocation_signatures.clone(); sigs.append(&mut self.cspk.details.direct_signatures.clone()); SignedComponentKeyPub::Primary((self.cspk.clone(), sigs)) } /// An iterator over all subkeys of this Certificate pub fn subkeys(&self) -> impl Iterator + '_ { // note: used in rpgpie-cert-store self.cspk.public_subkeys.iter().map(|spsk| { SignedComponentKeyPub::Subkey(( spsk.clone(), self.cspk.details.direct_signatures.clone(), )) }) } /// Return the primary User ID pub fn primary_user_id(&self) -> Option<&SignedUser> { primary_user_id(&self.cspk.details) } /// List all User IDs pub fn user_ids(&self) -> &[SignedUser] { &self.cspk.details.users } /// Was this signature potentially issued by a third party? fn maybe_third_party(&self, s: &Signature) -> bool { let mut maybe_third_party = false; let issuers = s.issuer(); if !issuers.is_empty() && issuers.into_iter().any(|x| x != &self.primary_key_id) { maybe_third_party = true }; let issuer_fps = s.issuer_fingerprint(); if !issuer_fps.is_empty() && issuer_fps .into_iter() .any(|x| x != &self.primary_fingerprint) { maybe_third_party = true }; maybe_third_party } /// Get all third-party certifications for all User IDs pub fn user_id_third_party_certifications(&self) -> Vec<(UserId, Vec<&Signature>)> { let mut res = vec![]; for su in &self.original.details.users { let id = su.id.clone(); let sigs = su .signatures .iter() .filter(|s| self.maybe_third_party(s)) .collect(); res.push((id, sigs)); } res } /// List all User Attributes pub fn user_attributes(&self) -> &[SignedUserAttribute] { &self.cspk.details.user_attributes } /// List all valid component keys that are encryption capable. /// /// The resulting set of [ComponentKeyPub] can be used to encrypt data. pub fn valid_encryption_capable_component_keys(&self) -> Vec { // FIXME: First drop all bad signatures (bad algorithms) let now: DateTime = chrono::offset::Utc::now(); match self.primary_valid_at(&now) { // If the primary key is invalid now, there are no valid component keys Err(_) | Ok(false) => return vec![], Ok(true) => {} } // Filter based on component key validity self.encryption_capable_component_keys(&now) .filter(|sckp| sckp.is_component_subkey_valid_at(&now)) .filter(|sckp| sckp.valid_encryption_algo()) .filter(|sckp| policy::acceptable_pk_algorithm(sckp.public_params(), &now)) .map(Into::into) .collect() } /// Get a set of [SignatureVerifier] objects, one for each valid component key that is /// appropriate to use for data signatures. /// /// This includes a check that the binding signature must have an embedded back signature /// (we have a `Checked`, so we can assume that if a back signature exists at all, it is acceptable). pub fn valid_signing_capable_component_keys_at( &self, reference: &DateTime, ) -> Vec { log::debug!("valid_signing_capable_component_keys_at {:?}", reference); // We assume all bad signatures (bad algorithms, missing backsig, ..) have been dropped already, // because this is a `Checked` match self.primary_valid_at(reference) { // If the primary key is invalid, there are no valid component keys Err(_) | Ok(false) => { log::debug!(" primary invalid"); return vec![]; } Ok(true) => { log::debug!(" primary is valid"); } } // Filter based on component key validity let keys = self .signing_capable_component_keys(reference) .filter(|sckp| sckp.is_component_subkey_valid_at(reference)) .filter(|sckp| sckp.has_valid_backsig_at(reference)) .filter(|sckp| policy::acceptable_pk_algorithm(sckp.public_params(), reference)) .map(ComponentKeyPub::from) .map(|ckey| SignatureVerifier::new(ckey, *self.cspk.primary_key.created_at())) .collect(); log::debug!(" -> {:#?}", keys); keys } /// Get a set of [ComponentKeyPub] objects, one for each valid component key that is /// appropriate to use for authentication. pub fn valid_authentication_capable_component_keys( &self, reference: &DateTime, ) -> Vec { // FIXME: First drop all bad signatures (bad algorithms) match self.primary_valid_at(reference) { // If the primary key is invalid, there are no valid component keys Err(_) | Ok(false) => return vec![], Ok(true) => {} } // Filter based on component key validity self.authentication_capable_component_keys(reference) .filter(|sckp| sckp.is_component_subkey_valid_at(reference)) .filter(|sckp| sckp.valid_encryption_algo()) .filter(|sckp| policy::acceptable_pk_algorithm(sckp.public_params(), reference)) .map(Into::into) .collect() } fn component_keys(&self) -> impl Iterator + '_ { // Collect all signatures that are bound directly to the primary let pri = vec![self.primary_key()]; pri.into_iter().chain(self.subkeys()) } /// Get list of all encryption capable component keys fn encryption_capable_component_keys<'a>( &'a self, reference: &'a DateTime, ) -> impl Iterator + 'a { self.component_keys().filter(move |ckp| { SignedComponentKey::Pub(ckp.clone()).is_encryption_capable(reference) }) } /// Get list of all component keys with "signing" key flag. fn signing_capable_component_keys<'a>( &'a self, reference: &'a DateTime, ) -> impl Iterator + 'a { self.component_keys() .filter(move |ckp| SignedComponentKey::Pub(ckp.clone()).is_signing_capable(reference)) } /// Get list of all authentication capable component keys fn authentication_capable_component_keys<'a>( &'a self, reference: &'a DateTime, ) -> impl Iterator + 'a { self.component_keys().filter(move |ckp| { SignedComponentKey::Pub(ckp.clone()).is_authentication_capable(reference) }) } /// Get latest binding signature for the primary user id. fn primary_user_id_binding<'a>( &'a self, reference: &'a DateTime, ) -> Option<&'a Signature> { primary_user_id_binding_at(&self.cspk.details, reference) } fn dks(&self, reference: &'_ DateTime) -> Option<&Signature> { // combine direct_signatures+revocation_signatures SigStack::from_iter( self.cspk .details .direct_signatures .iter() .chain(self.cspk.details.revocation_signatures.iter()), ) .active_at(Some(reference)) } pub fn direct_third_party_certifications(&self) -> Vec<&Signature> { self.original .details .direct_signatures .iter() .chain(&self.original.details.revocation_signatures) .filter(|s| self.maybe_third_party(s)) .collect() } /// Get the creation time of the certificate (the creation time of the primary key) pub fn primary_creation_time(&self) -> &DateTime { self.cspk.primary_key.created_at() } // Return expiration time as a duration, in seconds fn primary_expiration_time(&self, reference: &DateTime) -> Result, Error> { // Pick the more defensive expiration value, if both are set. // - None: no expiration // - 0: no expiration // - any other value: expiration in "seconds after creation" fn shorter(s1: Option, s2: Option) -> Option { match (s1, s2) { (None, None) => None, (Some(s), None) | (None, Some(s)) => Some(s), // "0" means indefinite -> return the other value, which might be shorter (Some(0), Some(s)) | (Some(s), Some(0)) => Some(s), (Some(s1), Some(s2)) => { // Both are not zero -> return the smaller number Some(u32::min(s1, s2)) } } } let pri_binding = self.primary_user_id_binding(reference); let dks = self.dks(reference); let exp = match (dks, pri_binding) { (None, None) => { // found neither direct key binding, nor primary user id binding signature return Err(Error::NoPrimaryBinding); } (Some(s), None) | (None, Some(s)) => { crate::util::duration_to_seconds(s.key_expiration_time()) } (Some(s1), Some(s2)) => shorter( crate::util::duration_to_seconds(s1.key_expiration_time()), crate::util::duration_to_seconds(s2.key_expiration_time()), ), }; Ok(exp) } /// Is the primary key (and thus the certificate as a whole) valid at `reference` time? /// /// The primary is considered invalid if it is expired, revoked, or has no acceptable binding /// self-signature. pub fn primary_valid_at(&self, reference: &DateTime) -> Result { let creation = self.primary_creation_time(); let expiration = self.primary_expiration_time(reference)?; // creation time is in the future, relative to the reference time if creation > reference { return Ok(false); } // If there is an expiration time, and it is not the value 0, the key is invalid if "creation+expiration" is prior to the reference time if let Some(expiration) = expiration { if expiration != 0 { let expires = self.cspk.primary_key.created_at().add( #[allow(clippy::expect_used)] Duration::try_seconds(expiration as i64).expect("should never fail for an u32"), ); if expires < *reference { log::debug!(" primary key expires {:?}", expires); return Ok(false); } } } // If the primary is using an insufficiently strong public key algorithm (at the reference time), we reject its use if !policy::acceptable_pk_algorithm(self.cspk.primary_key.public_params(), reference) { return Ok(false); } // Check that a valid primary user id binding or dks exist at the reference time. // (This includes checking that the binding signatures use an acceptable public key algorithm.) if !Self::primary_has_valid_binding_at(self, reference, creation) { log::debug!(" no valid primary key binding"); return Ok(false); } // the primary is revoked at the reference time if self.revoked_at(reference) { return Ok(false); } Ok(true) } /// Check for either a valid dks at reference, or a valid primary key binding at reference. fn primary_has_valid_binding_at( &self, reference: &DateTime, creation: &DateTime, ) -> bool { // Does a valid dks exist, at "reference"? let dks_stack: SigStack = SigStack::from_iter(self.cspk.details.direct_signatures.iter()); if dks_stack.has_valid_binding_at(reference, creation) { return true; } // Is the primary user id binding valid, at "reference"? if let Some(user) = primary_user_id(&self.cspk.details) { let stack = SigStack::from_iter(user.signatures.iter()); if stack.has_valid_binding_at(reference, creation) { return true; } } false } /// Is the full certificate revoked at `reference` time? /// /// This takes into account the semantics of hard and soft revocation. pub fn revoked_at(&self, reference: &DateTime) -> bool { log::debug!("revoked_at {:?}", reference); let stack = SigStack::from_iter( self.cspk .details .revocation_signatures .iter() .chain(self.cspk.details.direct_signatures.iter()), ); if stack.revoked_at(reference) { log::debug!(" revoked via a direct revocation"); return true; } // is there a valid direct key binding at `reference`? let valid_dks = stack.has_valid_binding_at(reference, self.cspk.primary_key.created_at()); if !valid_dks { // Consider if the primary user id is revoked // // (Note: this approach considers the *current* primary User ID. // Alternatively, the primary User ID at `reference` could be found and checked.) if let Some(primary_uid) = primary_user_id(&self.cspk.details) { let stack = SigStack::from_iter(primary_uid.signatures.iter()); if stack.revoked_at(reference) { log::debug!(" revoked via primary User ID"); return true; } } } false } /// The preferred `SymmetricKeyAlgorithm` setting for this certificate pub fn preferred_symmetric_key_algo<'a>( &'a self, reference: &'a DateTime, ) -> Option<&'a [SymmetricKeyAlgorithm]> { let prim_bind = self.primary_user_id_binding(reference); if let Some(prim_bind) = prim_bind { return Some(prim_bind.preferred_symmetric_algs()); } // FIXME: handle dks None } /// The preferred `AeadAlgorithm` setting for this certificate pub fn preferred_aead_algo<'a>( &'a self, reference: &'a DateTime, ) -> Option<&'a [(SymmetricKeyAlgorithm, AeadAlgorithm)]> { let prim_bind = self.primary_user_id_binding(reference); if let Some(prim_bind) = prim_bind { return Some(prim_bind.preferred_aead_algs()); } // FIXME: handle dks None } /// The preferred `HashAlgorithm` setting for this certificate pub fn preferred_hash_algorithms<'a>( &'a self, reference: &'a DateTime, ) -> Option<&'a [HashAlgorithm]> { let prim_bind = self.primary_user_id_binding(reference); if let Some(prim_bind) = prim_bind { return Some(prim_bind.preferred_hash_algs()); } // FIXME: handle dks None } /// The `Features` setting of this Certificate pub fn features<'a>(&'a self, reference: &'a DateTime) -> Option { let prim_bind = self.primary_user_id_binding(reference); if let Some(prim_bind) = prim_bind { let feat = prim_bind.features(); if feat.len() == 1 { return Some(feat[0]); } } // get dks at reference time if let Some(dks) = self.dks(reference) { let feat = dks.features(); if feat.len() == 1 { return Some(feat[0]); } } None } /// The fingerprint of this Certificate pub fn fingerprint(&self) -> Fingerprint { // FIXME: return reference, don't clone self.primary_fingerprint.clone() } /// The key id of this Certificate pub fn key_id(&self) -> KeyId { // FIXME: return reference, don't clone self.primary_key_id.clone() } } impl From<&Certificate> for Checked { fn from(value: &Certificate) -> Self { Checked::new(value) } } impl From for Certificate { fn from(value: Checked) -> Self { value.cspk.into() } } #[cfg(test)] mod tests { #![allow(clippy::expect_used)] use chrono::{DateTime, TimeDelta, Utc}; use crate::certificate::Checked; fn load_cert(filename: &str) -> Checked { let mut read = std::fs::File::open(filename).expect("open"); let c = crate::certificate::Certificate::load(&mut read).expect("load"); assert_eq!(c.len(), 1); Checked::from(&c[0]) } fn expiration_at(c: &Checked, at: &str) -> DateTime { use std::ops::Add; let created = c.primary_creation_time(); let rfc3339 = DateTime::parse_from_rfc3339(at).expect("parse at"); let validity = c .primary_expiration_time(&rfc3339.into()) .expect("got expiration") .expect("some"); created.add(TimeDelta::seconds(validity as i64)) } #[test] fn test_dsa() { // This certificate uses a DSA primary (which is used for all self-signatures). // // Note that we consider DSA invalid from February 3, 2023 forward (see [policy]). // // Thus, self-signatures that are newer than this cutoff are ignored - // so the self-signature on the primary User ID from 2024-06-03 is considered invalid, // and the key is considered expired at 2024-06-07T19:57:03Z. let checked = load_cert("tests/608B00ABE1DAA3501C5FF91AE58271326F9F4937"); // --- validity of the primary --- // key is not revoked assert!(!checked.revoked_at( &DateTime::parse_from_rfc3339("2025-01-01T23:59:00Z") .expect("parse date") .into() )); // valid at 2020-01-01T23:59:00Z assert!(checked .primary_valid_at( &DateTime::parse_from_rfc3339("2020-01-01T23:59:00Z") .expect("parse date") .into() ) .expect("primary_valid_at")); let exp = expiration_at(&checked, "2020-01-01T23:59:00Z"); assert_eq!( exp, DateTime::parse_from_rfc3339("2020-08-03T13:32:24Z").expect("parse date") ); let exp = expiration_at(&checked, "2021-01-01T23:59:00Z"); assert_eq!( exp, DateTime::parse_from_rfc3339("2022-07-06T17:06:34Z").expect("parse date") ); let exp = expiration_at(&checked, "2024-01-01T23:59:00Z"); assert_eq!( exp, DateTime::parse_from_rfc3339("2024-06-07T19:57:03Z").expect("parse date") ); // not valid at 2024-07-01T23:59:00Z assert!(!checked .primary_valid_at( &DateTime::parse_from_rfc3339("2024-07-01T23:59:00Z") .expect("parse date") .into() ) .expect("primary_valid_at")); // --- encryption subkey --- let enc = checked.valid_encryption_capable_component_keys(); assert_eq!(enc.len(), 0); // TODO: if we had a [valid_encryption_capable_component_keys_at] fn, we could test for // - historical validity of the old ElGamal encryption key, and // - the switch to the RSA encryption key on 2017-08-02. // --- signature component key (the primary key) --- let sig = checked.valid_signing_capable_component_keys_at( &DateTime::parse_from_rfc3339("2022-07-01T23:59:00Z") .expect("parse date") .into(), ); assert_eq!(sig.len(), 1); let sig = checked.valid_signing_capable_component_keys_at( &DateTime::parse_from_rfc3339("2024-07-01T23:59:00Z") .expect("parse date") .into(), ); assert_eq!(sig.len(), 0); } } rpgpie-0.5.4/src/key.rs000064400000000000000000000731361046102023000130570ustar 00000000000000// SPDX-FileCopyrightText: Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Handling of component keys. A component key is an individual "primary key" or "subkey." //! //! NOTE: This module is particularly experimental, the API may change drastically. //! (This current implementation does a lot of cloning and is limited to read-only operations.) use std::fmt::{Debug, Formatter}; use std::io::Write; use chrono::{DateTime, Utc}; use pgp::crypto::hash::HashAlgorithm; use pgp::crypto::public_key::PublicKeyAlgorithm; use pgp::crypto::sym::SymmetricKeyAlgorithm; use pgp::decrypt_session_key; use pgp::packet::{KeyFlags, PublicKeyEncryptedSessionKey, SubpacketData}; use pgp::types::{ EskType, Fingerprint, KeyId, KeyVersion, PkeskBytes, PkeskVersion, PublicKeyTrait, PublicParams, SignatureBytes, }; use pgp::{ packet, Message, Signature, SignedPublicKey, SignedPublicSubKey, SignedSecretKey, SignedSecretSubKey, }; use rand::{thread_rng, Rng}; use rand_core::CryptoRng; use crate::certificate::primary_user_id_binding_at; use crate::signature::SigStack; use crate::{certificate, signature, util, Error}; /// Any kind of component key: public or secret, primary or subkey. /// This enum serves as a convenience wrapper around all four cases. /// /// The component key is stored combined with the context of its binding signature(s) pub enum SignedComponentKey { Pub(SignedComponentKeyPub), Sec(SignedComponentKeySec), } impl Debug for SignedComponentKey { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { SignedComponentKey::Pub(p) => p.fmt(f), SignedComponentKey::Sec(s) => s.fmt(f), } } } impl PublicKeyTrait for SignedComponentKey { fn version(&self) -> KeyVersion { match self { SignedComponentKey::Pub(p) => p.version(), SignedComponentKey::Sec(s) => s.version(), } } fn fingerprint(&self) -> Fingerprint { match self { SignedComponentKey::Pub(p) => p.fingerprint(), SignedComponentKey::Sec(s) => s.fingerprint(), } } fn key_id(&self) -> KeyId { match self { SignedComponentKey::Pub(p) => p.key_id(), SignedComponentKey::Sec(s) => s.key_id(), } } fn algorithm(&self) -> PublicKeyAlgorithm { match self { SignedComponentKey::Pub(p) => p.algorithm(), SignedComponentKey::Sec(s) => s.algorithm(), } } fn created_at(&self) -> &DateTime { match self { SignedComponentKey::Pub(p) => p.created_at(), SignedComponentKey::Sec(s) => s.created_at(), } } fn expiration(&self) -> Option { match self { SignedComponentKey::Pub(p) => p.expiration(), SignedComponentKey::Sec(s) => s.expiration(), } } fn verify_signature( &self, hash: HashAlgorithm, data: &[u8], sig: &SignatureBytes, ) -> pgp::errors::Result<()> { match self { SignedComponentKey::Pub(p) => p.verify_signature(hash, data, sig), SignedComponentKey::Sec(s) => s.verify_signature(hash, data, sig), } } fn encrypt( &self, rng: R, plain: &[u8], typ: EskType, ) -> pgp::errors::Result { match self { SignedComponentKey::Pub(p) => p.encrypt(rng, plain, typ), SignedComponentKey::Sec(s) => s.encrypt(rng, plain, typ), } } fn serialize_for_hashing(&self, writer: &mut impl Write) -> pgp::errors::Result<()> { match self { SignedComponentKey::Pub(p) => p.serialize_for_hashing(writer), SignedComponentKey::Sec(s) => s.serialize_for_hashing(writer), } } fn public_params(&self) -> &PublicParams { match self { SignedComponentKey::Pub(p) => p.public_params(), SignedComponentKey::Sec(s) => s.public_params(), } } } enum SignedComponentPrimary { Pub(SignedPublicKey), Sec(SignedSecretKey), } impl SignedComponentPrimary { fn direct_key_signature_at(&self, reference: &DateTime) -> Option<&Signature> { match self { SignedComponentPrimary::Pub(spk) => { SigStack::from_iter(spk.details.direct_signatures.iter()).active_at(Some(reference)) } SignedComponentPrimary::Sec(ssk) => { SigStack::from_iter(ssk.details.direct_signatures.iter()).active_at(Some(reference)) } } } fn primary_user_id_binding_at<'a>( &'a self, reference: &'a DateTime, ) -> Option<&'a Signature> { match self { SignedComponentPrimary::Pub(p) => { certificate::primary_user_id_binding_at(&p.details, reference) } SignedComponentPrimary::Sec(s) => { certificate::primary_user_id_binding_at(&s.details, reference) } } } } impl SignedComponentSubkey { // FIXME: should probably also keep track of primary user id binding signature fn direct_key_signature(&self, reference: Option<&DateTime>) -> Option<&Signature> { match self { SignedComponentSubkey::Pub((_, dks)) => SigStack::from_iter(dks).active_at(reference), SignedComponentSubkey::Sec((_, dks)) => dks.as_ref(), } } fn subkey_binding_at(&self, reference: Option<&DateTime>) -> Option<&Signature> { match self { SignedComponentSubkey::Pub((spsk, _)) => { SigStack::from_iter(spsk.signatures.iter()).active_at(reference) } SignedComponentSubkey::Sec((sssk, _)) => { SigStack::from_iter(sssk.signatures.iter()).active_at(reference) } } } } #[derive(Debug, Clone)] #[allow(clippy::large_enum_variant)] // FIXME enum SignedComponentSubkey { Pub((SignedPublicSubKey, Vec)), // key, dks Sec((SignedSecretSubKey, Option)), // key, dks } /// Signed component key, either a primary or a subkey enum PrimaryOrSubkey { Primary(SignedComponentPrimary), Subkey(SignedComponentSubkey), } impl From<&SignedComponentKey> for PrimaryOrSubkey { fn from(value: &SignedComponentKey) -> Self { // FIXME: don't clone match value { SignedComponentKey::Pub(SignedComponentKeyPub::Primary((k, _))) => { PrimaryOrSubkey::Primary(SignedComponentPrimary::Pub(k.clone())) } SignedComponentKey::Pub(SignedComponentKeyPub::Subkey(pair)) => { PrimaryOrSubkey::Subkey(SignedComponentSubkey::Pub(pair.clone())) } SignedComponentKey::Sec(SignedComponentKeySec::Primary(k)) => { PrimaryOrSubkey::Primary(SignedComponentPrimary::Sec(k.clone())) } SignedComponentKey::Sec(SignedComponentKeySec::Subkey(pair)) => { PrimaryOrSubkey::Subkey(SignedComponentSubkey::Sec(pair.clone())) } } } } impl SignedComponentKey { #[allow(dead_code)] pub(crate) fn sigs(&self) -> SigStack { match self { SignedComponentKey::Pub(SignedComponentKeyPub::Primary((_spk, sigs))) => { SigStack::from_iter(sigs.iter()) } SignedComponentKey::Pub(SignedComponentKeyPub::Subkey((spsk, _))) => { SigStack::from_iter(spsk.signatures.iter()) } SignedComponentKey::Sec(SignedComponentKeySec::Primary(_ssk)) => { // FIXME: we don't want to support this kind of call at all (we're not handling DKS here) // TSKs should probably be represented as Certificate + "secret key packets" unimplemented!("unsupported case [primary secret]") } SignedComponentKey::Sec(SignedComponentKeySec::Subkey((sssk, _))) => { SigStack::from_iter(sssk.signatures.iter()) } } } /// Find KeyFlags subpacket in hashed area, return first occurrence (if any) fn key_flag_subpacket(sig: &Signature) -> Option { sig.config .hashed_subpackets .iter() .find_map(|p| match &p.data { SubpacketData::KeyFlags(d) => Some(d[..].into()), _ => None, }) } /// Get key flags. /// /// This convenience fn takes into consideration the different places that key flags can be stored in: /// - For primary component keys: dks or primary user id binding /// - For component subkeys: subkey binding or dks pub(crate) fn key_flags_at(&self, reference: &DateTime) -> Option { match PrimaryOrSubkey::from(self) { PrimaryOrSubkey::Primary(scp) => { // We favor the dsk for the primary if let Some(dks) = &scp.direct_key_signature_at(reference) { if let Some(kf) = Self::key_flag_subpacket(dks) { return Some(kf); } } else if let Some(prim_bind) = scp.primary_user_id_binding_at(reference) { if let Some(kf) = Self::key_flag_subpacket(prim_bind) { return Some(kf); } } Some(KeyFlags::default()) } PrimaryOrSubkey::Subkey(scsk) => { let Some(binding) = scsk.subkey_binding_at(Some(reference)) else { eprintln!("no subkey binding"); return None; }; // We favor the subkey binding for subkeys if let Some(kf) = Self::key_flag_subpacket(binding) { return Some(kf); } else if let Some(dks) = scsk.direct_key_signature(Some(reference)) { // FIXME: should we also consider the primary user id binding here? // (or should we just find the primary key's flags and use those?!) if let Some(kf) = Self::key_flag_subpacket(dks) { return Some(kf); } } Some(KeyFlags::default()) } } } /// Check if *this* component key is revoked at `reference`. /// (Note that for subkeys, the revocation status of the primary is not considered!) pub fn revoked(&self, reference: &DateTime) -> bool { let sigs = match self { SignedComponentKey::Pub(SignedComponentKeyPub::Primary((k, _))) => { // FIXME: check for primary user id based revocations k.details.revocation_signatures.as_slice() } SignedComponentKey::Pub(SignedComponentKeyPub::Subkey((spsk, _))) => { spsk.signatures.as_slice() } SignedComponentKey::Sec(SignedComponentKeySec::Primary(k)) => { // FIXME: check for primary user id based revocations k.details.revocation_signatures.as_slice() } SignedComponentKey::Sec(SignedComponentKeySec::Subkey((sssk, _))) => { sssk.signatures.as_slice() } }; SigStack::from_iter(sigs.iter()).revoked_at(reference) } pub(crate) fn is_encryption_capable(&self, reference: &DateTime) -> bool { let Some(kf) = self.key_flags_at(reference) else { return false; }; kf.encrypt_comms() || kf.encrypt_storage() } pub(crate) fn is_signing_capable(&self, reference: &DateTime) -> bool { log::debug!( "is_signing_capable {:?} at {:?}?", self.fingerprint(), reference ); let Some(kf) = self.key_flags_at(reference) else { return false; }; kf.sign() } pub(crate) fn is_authentication_capable(&self, reference: &DateTime) -> bool { let Some(kf) = self.key_flags_at(reference) else { return false; }; kf.authentication() } } /// A public component key (either a public primary key, or a public subkey). /// /// NOTE: This component key representation consists of only the raw public key packet data, /// without the context of binding signatures #[derive(Debug, Clone)] pub enum ComponentKeyPub { Primary(packet::PublicKey), Subkey(packet::PublicSubkey), } #[derive(Debug, Clone)] pub enum SignedComponentKeyPub { // We store a copy of all primary-related signatures, to have an owned copy that can be borrowed as a SigStack. // FIXME: do this better? Primary((SignedPublicKey, Vec)), // key, dks stack Subkey((SignedPublicSubKey, Vec)), } impl PublicKeyTrait for SignedComponentKeyPub { fn version(&self) -> KeyVersion { match self { SignedComponentKeyPub::Primary((p, _)) => p.version(), SignedComponentKeyPub::Subkey((s, _)) => s.version(), } } fn fingerprint(&self) -> Fingerprint { match self { SignedComponentKeyPub::Primary((p, _)) => p.fingerprint(), SignedComponentKeyPub::Subkey((s, _)) => s.fingerprint(), } } fn key_id(&self) -> KeyId { match self { SignedComponentKeyPub::Primary((p, _)) => p.key_id(), SignedComponentKeyPub::Subkey((s, _)) => s.key_id(), } } fn algorithm(&self) -> PublicKeyAlgorithm { match self { SignedComponentKeyPub::Primary((p, _)) => p.algorithm(), SignedComponentKeyPub::Subkey((s, _)) => s.algorithm(), } } fn created_at(&self) -> &DateTime { match self { SignedComponentKeyPub::Primary((p, _)) => p.created_at(), SignedComponentKeyPub::Subkey((s, _)) => s.created_at(), } } fn expiration(&self) -> Option { match self { SignedComponentKeyPub::Primary((p, _)) => p.expiration(), SignedComponentKeyPub::Subkey((s, _)) => s.expiration(), } } fn verify_signature( &self, hash: HashAlgorithm, data: &[u8], sig: &SignatureBytes, ) -> pgp::errors::Result<()> { match self { SignedComponentKeyPub::Primary((p, _)) => p.verify_signature(hash, data, sig), SignedComponentKeyPub::Subkey((s, _)) => s.verify_signature(hash, data, sig), } } fn encrypt( &self, rng: R, plain: &[u8], typ: EskType, ) -> pgp::errors::Result { match self { SignedComponentKeyPub::Primary((p, _)) => p.encrypt(rng, plain, typ), SignedComponentKeyPub::Subkey((s, _)) => s.encrypt(rng, plain, typ), } } fn serialize_for_hashing(&self, writer: &mut impl Write) -> pgp::errors::Result<()> { match self { SignedComponentKeyPub::Primary((p, _)) => p.serialize_for_hashing(writer), SignedComponentKeyPub::Subkey((s, _)) => s.serialize_for_hashing(writer), } } fn public_params(&self) -> &PublicParams { match self { SignedComponentKeyPub::Primary((p, _)) => p.public_params(), SignedComponentKeyPub::Subkey((s, _)) => s.public_params(), } } } impl From for ComponentKeyPub { fn from(value: SignedComponentKeyPub) -> ComponentKeyPub { match value { SignedComponentKeyPub::Subkey(sk) => ComponentKeyPub::Subkey(sk.0.key), SignedComponentKeyPub::Primary((pk, _)) => { ComponentKeyPub::Primary(pk.primary_key.clone()) } } } } #[allow(dead_code)] pub enum ComponentKeySec { Primary(packet::SecretKey), Subkey(packet::SecretSubkey), } impl ComponentKeySec { pub fn sign_msg( &self, msg: Message, key_pw: F, hash_algo: HashAlgorithm, ) -> Result where F: FnOnce() -> String, { match self { ComponentKeySec::Primary(sk) => { Ok(msg.sign(&mut thread_rng(), &sk, key_pw, hash_algo)?) } ComponentKeySec::Subkey(ssk) => { Ok(msg.sign(&mut thread_rng(), &ssk, key_pw, hash_algo)?) } } } pub fn key_id(&self) -> KeyId { match self { ComponentKeySec::Primary(pri) => pri.key_id(), ComponentKeySec::Subkey(sub) => sub.key_id(), } } pub fn fingerprint(&self) -> Fingerprint { match self { ComponentKeySec::Primary(pri) => pri.fingerprint(), ComponentKeySec::Subkey(sub) => sub.fingerprint(), } } pub fn created_at(&self) -> &DateTime { match self { ComponentKeySec::Primary(pri) => pri.created_at(), ComponentKeySec::Subkey(sub) => sub.created_at(), } } pub fn algorithm(&self) -> PublicKeyAlgorithm { match self { ComponentKeySec::Primary(pri) => pri.algorithm(), ComponentKeySec::Subkey(sub) => sub.algorithm(), } } pub fn public_params(&self) -> &PublicParams { match self { ComponentKeySec::Primary(pri) => pri.public_params(), ComponentKeySec::Subkey(sub) => sub.public_params(), } } } /// Either a secret primary key, or a secret subkey. /// This enum serves as a convenience wrapper around both cases. /// /// The component key is stored combined with the context of its binding signature(s) pub enum SignedComponentKeySec { Primary(SignedSecretKey), Subkey((SignedSecretSubKey, Option)), // key, dks } impl Debug for SignedComponentKeySec { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { SignedComponentKeySec::Primary(p) => p.fmt(f), SignedComponentKeySec::Subkey(s) => s.fmt(f), } } } impl PublicKeyTrait for SignedComponentKeySec { fn version(&self) -> KeyVersion { match self { SignedComponentKeySec::Primary(p) => p.version(), SignedComponentKeySec::Subkey((s, _)) => s.version(), } } fn fingerprint(&self) -> Fingerprint { match self { SignedComponentKeySec::Primary(p) => p.fingerprint(), SignedComponentKeySec::Subkey((s, _)) => s.fingerprint(), } } fn key_id(&self) -> KeyId { match self { SignedComponentKeySec::Primary(p) => p.key_id(), SignedComponentKeySec::Subkey((s, _)) => s.key_id(), } } fn algorithm(&self) -> PublicKeyAlgorithm { match self { SignedComponentKeySec::Primary(p) => p.algorithm(), SignedComponentKeySec::Subkey((s, _)) => s.algorithm(), } } fn created_at(&self) -> &DateTime { match self { SignedComponentKeySec::Primary(p) => p.created_at(), SignedComponentKeySec::Subkey((s, _)) => s.created_at(), } } fn expiration(&self) -> Option { match self { SignedComponentKeySec::Primary(p) => p.expiration(), SignedComponentKeySec::Subkey((s, _)) => s.expiration(), } } fn verify_signature( &self, hash: HashAlgorithm, data: &[u8], sig: &SignatureBytes, ) -> pgp::errors::Result<()> { match self { SignedComponentKeySec::Primary(p) => p.verify_signature(hash, data, sig), SignedComponentKeySec::Subkey((s, _)) => s.verify_signature(hash, data, sig), } } fn encrypt( &self, rng: R, plain: &[u8], typ: EskType, ) -> pgp::errors::Result { match self { SignedComponentKeySec::Primary(p) => p.encrypt(rng, plain, typ), SignedComponentKeySec::Subkey((s, _)) => s.encrypt(rng, plain, typ), } } fn serialize_for_hashing(&self, writer: &mut impl Write) -> pgp::errors::Result<()> { match self { SignedComponentKeySec::Primary(p) => p.serialize_for_hashing(writer), SignedComponentKeySec::Subkey((s, _)) => s.serialize_for_hashing(writer), } } fn public_params(&self) -> &PublicParams { match self { SignedComponentKeySec::Primary(p) => p.public_params(), SignedComponentKeySec::Subkey((s, _)) => s.public_params(), } } } pub(crate) enum KeyFlagMatch { Sign, Enc, Auth, } impl SignedComponentKeySec { pub fn key_id(&self) -> KeyId { match self { SignedComponentKeySec::Primary(ssk) => ssk.key_id(), SignedComponentKeySec::Subkey((sssk, _)) => sssk.key_id(), } } fn match_flag(sig: &Signature, check: KeyFlagMatch) -> bool { let flags = sig.key_flags(); match check { KeyFlagMatch::Sign => flags.sign(), KeyFlagMatch::Auth => flags.authentication(), KeyFlagMatch::Enc => flags.encrypt_comms() | flags.encrypt_storage(), } } pub(crate) fn has_key_flag(&self, check: KeyFlagMatch) -> bool { match self { SignedComponentKeySec::Primary(ssk) => { let x = &ssk.details; // FIXME: also consider direct signatures if let Some(binding) = primary_user_id_binding_at(x, &Utc::now()) { Self::match_flag(binding, check) } else { false } } SignedComponentKeySec::Subkey((sssk, _)) => { let stack = SigStack::from_iter(sssk.signatures.iter()); if let Some(sig) = stack.active_at(Some(&Utc::now())) { Self::match_flag(sig, check) } else { false } } } } pub fn decrypt_session_key( &self, pkesk: &PublicKeyEncryptedSessionKey, key_pw: F, ) -> Result where F: FnOnce() -> String, { let typ = match pkesk.version() { PkeskVersion::V3 => pgp::types::EskType::V3_4, PkeskVersion::V6 => pgp::types::EskType::V6, v => { return Err(Error::Rpgp(pgp::errors::Error::Message(format!( "Unsupported PKESK version {:?}", v )))) } }; match self { SignedComponentKeySec::Primary(ssk) => { Ok(decrypt_session_key(&ssk, key_pw, pkesk.values()?, typ)?) } SignedComponentKeySec::Subkey((sssk, _)) => { Ok(decrypt_session_key(&sssk, key_pw, pkesk.values()?, typ)?) } } } } impl From<&SignedComponentKeySec> for ComponentKeySec { fn from(value: &SignedComponentKeySec) -> Self { match value { SignedComponentKeySec::Primary(ssk) => { ComponentKeySec::Primary(ssk.primary_key.clone()) } SignedComponentKeySec::Subkey((sssk, _)) => ComponentKeySec::Subkey(sssk.key.clone()), } } } impl SignedComponentKeyPub { pub fn algorithm_name(&self) -> String { match &self { SignedComponentKeyPub::Primary((spk, _)) => { util::algo_name(spk.primary_key.public_params()) } SignedComponentKeyPub::Subkey((spsk, _)) => util::algo_name(spsk.key.public_params()), } } pub(crate) fn key_creation_time(&self) -> &DateTime { match &self { SignedComponentKeyPub::Primary((spk, _)) => spk.primary_key.created_at(), SignedComponentKeyPub::Subkey((spsk, _)) => spsk.key.created_at(), } } /// This fn is intended for use on signing-capable component keys. /// It returns "false" is a subkey is not validly "backward-bound" to the primary. pub(crate) fn has_valid_backsig_at(&self, reference: &DateTime) -> bool { if let SignedComponentKeyPub::Subkey(sssk) = self { // If a subkey binding signature has no embedded back signature, // the subkey is not reasonably bound to the certificate for signing let key_creation: &DateTime = self.key_creation_time(); let binding = SigStack::from_iter(sssk.0.signatures.iter()).active_at(Some(reference)); if let Some(binding) = binding { binding.embedded_signature().iter().any(|backsig| { signature::is_signature_valid_at(backsig, key_creation, reference) }) } else { // we don't even have a binding at the reference time false } } else { // A primary doesn't need a backsig to be "validly bound" true } } /// This fn presupposes that the primary is valid at the reference time pub(crate) fn is_component_subkey_valid_at(&self, reference: &DateTime) -> bool { match &self { SignedComponentKeyPub::Subkey(sk) => Self::is_subkey_valid_at(&sk.0, reference), SignedComponentKeyPub::Primary(_) => true, } } /// takes into account the semantics of hard and soft revocation fn subkey_revoked_at(sk: &SignedPublicSubKey, reference: &DateTime) -> bool { SigStack::from_iter(sk.signatures.iter()).revoked_at(reference) } fn is_subkey_valid_at(spsk: &SignedPublicSubKey, reference: &DateTime) -> bool { // subkey is revoked if Self::subkey_revoked_at(spsk, reference) { return false; } let key_creation = spsk.key.created_at(); let stack = SigStack::from_iter(spsk.signatures.iter()); if !stack.has_valid_binding_at(reference, key_creation) { return false; } true } /// Are we willing to encrypt to the algorithm/ECDH parameters of self? pub(crate) fn valid_encryption_algo(&self) -> bool { // FIXME: where should this go? let pp = self.public_params(); crate::policy::accept_for_encryption(pp) } pub(crate) fn public_params(&self) -> &PublicParams { match &self { SignedComponentKeyPub::Primary((pk, _)) => pk.primary_key.public_params(), SignedComponentKeyPub::Subkey((sk, _)) => sk.key.public_params(), } } pub fn revoked(&self, reference: &DateTime) -> bool { let sck = SignedComponentKey::Pub(self.clone()); sck.revoked(reference) } /// Checks if this component key by itself is valid. /// /// For a subkey this fn does *not* check if the certificate itself is valid! /// /// CAUTION: this currently is *not* implemented for primary component keys and will panic! pub fn is_valid_at(&self, reference: &DateTime) -> bool { match self { SignedComponentKeyPub::Primary(_) => unimplemented!("FIXME"), SignedComponentKeyPub::Subkey((spsk, _dks)) => { Self::is_subkey_valid_at(spsk, reference) } } } } impl ComponentKeyPub { pub fn pkesk_from_session_key_v3( &self, rng: &mut R, session_key: &[u8], alg: SymmetricKeyAlgorithm, ) -> Result { match &self { Self::Primary(pk) => Ok(PublicKeyEncryptedSessionKey::from_session_key_v3( rng, session_key, alg, pk, )?), Self::Subkey(psk) => Ok(PublicKeyEncryptedSessionKey::from_session_key_v3( rng, session_key, alg, psk, )?), } } pub fn pkesk_from_session_key_v6( &self, rng: &mut R, session_key: &[u8], ) -> Result { match &self { Self::Primary(pk) => Ok(PublicKeyEncryptedSessionKey::from_session_key_v6( rng, session_key, pk, )?), Self::Subkey(psk) => Ok(PublicKeyEncryptedSessionKey::from_session_key_v6( rng, session_key, psk, )?), } } pub fn verify(&self, s: &Signature, payload: &[u8]) -> Result<(), Error> { // policy check on s if !signature::signature_acceptable(s) { return Err(Error::Message( "Signature doesn't satisfy our policy".to_string(), )); } match self { ComponentKeyPub::Primary(pri) => Ok(s.verify(pri, payload)?), ComponentKeyPub::Subkey(sub) => Ok(s.verify(sub, payload)?), } } pub fn created_at(&self) -> &DateTime { match self { ComponentKeyPub::Primary(pri) => pri.created_at(), ComponentKeyPub::Subkey(sub) => sub.created_at(), } } pub fn algorithm(&self) -> PublicKeyAlgorithm { match self { ComponentKeyPub::Primary(pri) => pri.algorithm(), ComponentKeyPub::Subkey(sub) => sub.algorithm(), } } pub fn key_id(&self) -> KeyId { match self { ComponentKeyPub::Primary(pri) => pri.key_id(), ComponentKeyPub::Subkey(sub) => sub.key_id(), } } pub fn fingerprint(&self) -> Fingerprint { match self { ComponentKeyPub::Primary(pri) => pri.fingerprint(), ComponentKeyPub::Subkey(sub) => sub.fingerprint(), } } pub fn public_params(&self) -> &PublicParams { match self { ComponentKeyPub::Primary(pri) => pri.public_params(), ComponentKeyPub::Subkey(sub) => sub.public_params(), } } } rpgpie-0.5.4/src/lib.rs000064400000000000000000000050371046102023000130300ustar 00000000000000// SPDX-FileCopyrightText: Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! # rpgpie 🦀️🔐🥧 //! //! rpgpie is an experimental higher level [OpenPGP](https://www.rfc-editor.org/rfc/rfc4880) //! library based on [rPGP](https://github.com/rpgp/rpgp). //! //! It exposes a convenient API for some simple common OpenPGP operations. //! //! rpgpie implements semantics for OpenPGP expiration and revocation mechanisms, key flags, //! and applies some amount of policy (to limit reliance on obsolete algorithms). //! //! Main goals of rpgpie include simplicity and collaboration 🥳. //! //! ## "OpenPGP for application developers" //! //! rpgpie aims to apply the terminology outlined in the //! ["OpenPGP for application developers"](https://openpgp.dev/) documentation. //! //! ## Stateless OpenPGP (SOP) //! //! See [rsop](https://crates.io/crates/rsop) for a //! [stateless OpenPGP (SOP)](https://datatracker.ietf.org/doc/draft-dkg-openpgp-stateless-cli/) //! tool based on rpgpie. pub mod certificate; pub(crate) mod key; pub mod message; pub mod policy; pub mod signature; pub mod tsk; pub(crate) mod util; pub use crate::key::ComponentKeyPub; /// rpgpie version, via Cargo.toml pub const VERSION: &str = env!("CARGO_PKG_VERSION"); /// version of the rpgp dependency we built against pub const RPGP_VERSION: &str = pgp::VERSION; /// Error type for this crate #[derive(thiserror::Error, Debug)] #[non_exhaustive] pub enum Error { #[error("rPGP error: {0}")] Rpgp(pgp::errors::Error), #[error("IO error: {0}")] Io(std::io::Error), #[error("Internal error: {0}")] Message(String), #[error("No binding signature for the primary key")] NoPrimaryBinding, } impl From for Error { fn from(e: pgp::errors::Error) -> Self { match e { pgp::errors::Error::IOError(io) => Error::Io(io), _ => Error::Rpgp(e), } } } impl From for Error { fn from(value: std::io::Error) -> Self { Error::Io(value) } } impl From for Error { fn from(value: pgp::SubkeyParamsBuilderError) -> Self { Error::Rpgp(pgp::errors::Error::Message(format!( "SubkeyParamsBuilderError: {}", value ))) } } impl From for Error { fn from(value: pgp::SecretKeyParamsBuilderError) -> Self { Error::Rpgp(pgp::errors::Error::Message(format!( "SecretKeyParamsBuilderError: {}", value ))) } } rpgpie-0.5.4/src/message.rs000064400000000000000000000473501046102023000137120ustar 00000000000000// SPDX-FileCopyrightText: Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Handling of OpenPGP messages. use std::io; use std::io::Read; use pgp::crypto::aead::AeadAlgorithm; use pgp::crypto::hash::HashAlgorithm; use pgp::crypto::sym::SymmetricKeyAlgorithm; use pgp::packet::{ LiteralData, Packet, PublicKeyEncryptedSessionKey, SymEncryptedProtectedData, SymKeyEncryptedSessionKey, }; use pgp::ser::Serialize; use pgp::types::StringToKey; use pgp::{decrypt_session_key_with_password, ArmorOptions, PlainSessionKey}; use pgp::{Deserializable, Edata, Esk}; use pgp::{Message, Signature}; use zeroize::Zeroizing; use crate::certificate::Certificate; use crate::certificate::Checked; use crate::key::{ComponentKeyPub, SignedComponentKey}; use crate::policy::MAX_RECURSION; use crate::tsk::Tsk; use crate::Error; /// Any mechanism that can decrypt a PKESK into a PlainSessionKey. /// This abstraction allows use of either software-backed or hardware-backed private keys. pub trait PkeskDecryptor { fn decrypt(&self, pkesk: &PublicKeyEncryptedSessionKey) -> Option; } /// A decryptor backed by a "softkey" [Tsk] (as opposed to hardware-backed private key material). /// /// A SoftkeyPkeskDecryptor can be used to decrypt PKESKs and obtain decrypted session keys. pub struct SoftkeyPkeskDecryptor<'a> { tsk: Tsk, key_passwords: Vec<&'a [u8]>, } impl<'a> SoftkeyPkeskDecryptor<'a> { pub fn new(tsk: Tsk, key_passwords: Vec<&'a [u8]>) -> Self { Self { tsk, key_passwords } } } impl PkeskDecryptor for SoftkeyPkeskDecryptor<'_> { fn decrypt(&self, pkesk: &PublicKeyEncryptedSessionKey) -> Option { for key in self.tsk.decryption_capable_component_keys() { let key_pw = match self.key_passwords.len() { 0 => "".to_string(), 1 => String::from_utf8_lossy(self.key_passwords[0]).to_string(), _ => { // FIXME return None; } }; // Match key id/fp or check for wildcard. if pkesk.match_identity(&key) { match key { SignedComponentKey::Sec(s) => { match s.decrypt_session_key(pkesk, || key_pw.clone()) { Ok(sk0) => { return Some(sk0); } Err(e) => eprintln!("err {:?}", e), } } SignedComponentKey::Pub(_) => { // we can't decrypt with this key } } }; } None } } /// Result of decrypting and/or verifying the signatures of a message with [unpack]. /// /// Depending on the format of the processed message, this result contains: /// - The cleartext of the message. /// - The session key, if the message was encrypted. /// - A list of signatures that were found to be valid. pub struct MessageResult { pub validated: Vec<(Certificate, ComponentKeyPub, Signature)>, pub session_key: Option<(u8, Vec)>, pub cleartext: LiteralData, } /// Process an existing message: Decrypt message, and/or verify signatures. /// /// This function handles messages that are encrypted, signed or both. /// /// More specifically: OpenPGP messages may consist of layers of encryption, signing and /// compression. This function processes arbitrary combinations of these layers, up to a maximum /// layering depth. /// /// The return value encodes (to some degree) the properties the message: /// /// - It returns the cleartext of the message, and /// - A list of the signatures on the message that were found to be valid (if any). /// /// **Decryption** /// /// Decryption can be attempted using two distinct mechanisms: /// /// 1. Using a private OpenPGP component key provided via `decryptor`. /// The OpenPGP component key may optionally be protected with a passphrase. Unlocking such /// protected keys will be attempted with the passphrases provided in `key_passwords` (if any). /// /// 2. A symmetric key, represented by a passphrase, in `skesk_passwords` (OpenPGP messages can be /// encrypted to a recipient who is not using an OpenPGP key, with this method). /// /// **Signature verification** /// /// If the message has been signed, signatures will be verified against the certificates in `verifier`. /// Any correct signatures will be reported in the `MessageResult`. /// /// **Compression layers** /// /// If the message has compression layers, they will be unpacked, silently. /// /// FIXME: This function should take `msg: &Message` pub fn unpack( msg: Message, pkesk_decryptors: &[Box], skesk_passwords: Vec<&[u8]>, verifier: &[Certificate], ) -> Result { unpack_int(&msg, pkesk_decryptors, skesk_passwords, None, verifier, 0) } /// Try to decrypt msg with all known session key versions pub fn unpack_by_session_key( msg: &Message, key: &[u8], sym_alg: SymmetricKeyAlgorithm, verifier: &[Certificate], ) -> Result { // Try to decrypt with a v3/4 session key let sk_34 = PlainSessionKey::V3_4 { sym_alg, key: key.into(), }; if let Ok(mr) = unpack_int(msg, &[], vec![], Some(sk_34), verifier, 0) { return Ok(mr); } // Try to decrypt with a v6 session key let sk_6 = PlainSessionKey::V6 { key: key.into() }; if let Ok(mr) = unpack_int(msg, &[], vec![], Some(sk_6), verifier, 0) { return Ok(mr); } // All attempts to decrypt failed Err(Error::Message("Failed to decrypt by session key".into())) } // internal variant of the unpack() function with depth handling and limitation fn unpack_int( msg: &Message, pkesk_decryptors: &[Box], skesk_passwords: Vec<&[u8]>, session_key: Option, verifier: &[Certificate], depth: usize, ) -> Result { if depth > MAX_RECURSION { return Err(Error::Message( "Excessive message nesting depth".to_string(), )); } match &msg { Message::Encrypted { ref esk, ref edata } => { let mut sk = session_key.clone(); // Session key, if any is found if sk.is_none() { 'esks: for e in esk { match e { Esk::PublicKeyEncryptedSessionKey(pkesk) => { for dec in pkesk_decryptors { if let Some(esk) = dec.decrypt(pkesk) { sk = Some(esk); break 'esks; } } } Esk::SymKeyEncryptedSessionKey(skesk) => { if !skesk_passwords.is_empty() { let sym_pw = match skesk_passwords.len() { 0 => "".to_string(), 1 => String::from_utf8_lossy(skesk_passwords[0]).to_string(), _ => { // FIXME return Err(Error::Message( "More than one SKESK password currently unsupported" .to_string(), )); } }; if let Ok(sk0) = decrypt_session_key_with_password(skesk, || sym_pw) { sk = Some(sk0); break 'esks; } } } } } } let Some(sk) = sk else { // FIXME: return more specific error type return Err(Error::Message("Failed to get session key".to_string())); }; let (sym_alg, key) = match &sk { PlainSessionKey::V3_4 { sym_alg, key } => (*sym_alg, key.to_vec()), PlainSessionKey::V6 { key } => match edata { Edata::SymEncryptedProtectedData(seipd) => match &seipd.data() { pgp::packet::Data::V2 { sym_alg, .. } => (*sym_alg, key.to_vec()), pgp::packet::Data::V1 { .. } => { return Err(Error::Rpgp(pgp::errors::Error::Message(format!( "V6 session key with unexpected symmetric data version {}", seipd.version() )))); } }, Edata::SymEncryptedData(_) => { return Err(Error::Rpgp(pgp::errors::Error::Message( "SED packet is not supported with V6 session keys".to_string(), ))); } }, psk => { return Err(Error::Rpgp(pgp::errors::Error::Message(format!( "Unsupported plain session key version {:?}", psk )))); } }; let msg = edata.decrypt(sk.clone())?; let mut mr = unpack_int( &msg, pkesk_decryptors, skesk_passwords.clone(), session_key, verifier, depth + 1, )?; // Return the session key in MessageResult mr.session_key = Some(((sym_alg).into(), key)); Ok(mr) } Message::Literal(lit) => Ok(MessageResult { validated: vec![], session_key: None, cleartext: lit.clone(), // FIXME: avoid clone }), Message::Compressed(cd) => { let payload = cd.decompress()?; let msg = Message::from_bytes(payload)?; unpack_int( &msg, pkesk_decryptors, skesk_passwords, session_key, verifier, depth + 1, ) } Message::Signed { one_pass_signature: _, message: inner, signature, } => { let mut vals: Vec<(Certificate, ComponentKeyPub, Signature)> = vec![]; // FIXME: handle in streaming mode? // only consider "signature" if it passes our policy check, skip validation if not if crate::signature::signature_acceptable(signature) { // get the data we need to validate this signature against (either the bare literal // data [without pgp framing], or the raw bytes of the message). fn grab_data(m: &Message, depth: usize) -> Result, Error> { if depth > MAX_RECURSION { return Err(Error::Message( "Excessive message nesting depth".to_string(), )); } match m { Message::Literal(lit) => Ok(lit.data().to_vec()), Message::Compressed(cd) => { let payload = cd.decompress()?; let msg = Message::from_bytes(payload)?; grab_data(&msg, depth + 1) } Message::Signed { message, .. } => { let Some(message) = message else { return Err(Error::Message( "No inner message found in signed message".to_string(), )); }; // FIXME: return raw message data, if notarizing signature? grab_data(message.as_ref(), depth + 1) } Message::Encrypted { .. } => Ok(m.to_bytes()?), } } let Some(inner) = inner else { return Err(Error::Message("Inner message is None".to_string())); }; let data = grab_data(inner.as_ref(), 0)?; for vcert in verifier { if let Some(reference) = signature.created() { // Only consider signers that are valid at signature creation time. let cv = Checked::from(vcert); let verifiers = cv.valid_signing_capable_component_keys_at(reference); for v in verifiers { if v.verify(signature, &data).is_ok() { vals.push(( vcert.clone(), v.as_componentkey().clone(), signature.clone(), )); } } } } } let Some(inner) = inner else { return Err(Error::Message("Inner message is None".to_string())); }; let mut mr = unpack_int( inner.as_ref(), pkesk_decryptors, skesk_passwords, session_key, verifier, depth + 1, )?; mr.validated.append(&mut vals); // FIXME: deduplicate validations? Ok(mr) } } } /// Configuration for encryption, either as SeipdV1 or SeipdV2 #[derive(Debug, Copy, Clone, PartialEq)] pub enum EncryptionMechanism { SeipdV1(SymmetricKeyAlgorithm), SeipdV2(AeadAlgorithm, SymmetricKeyAlgorithm), } /// Encrypt (and optionally sign) a message. /// /// NOTE: `source` is expected to contain raw data, not an OpenPGP Message /// /// FIXME: we need passwords to unlock signers! /// /// FIXME: pass recipient primary (to set as intended recipient) #[allow(clippy::too_many_arguments)] pub fn encrypt( mechanism: EncryptionMechanism, recipients: Vec, skesk_passwords: Vec<&[u8]>, signers: Vec, hash_algo: Option, source: &mut (dyn Read + Send + Sync), mut sink: &mut (dyn io::Write + Send + Sync), armor: bool, ) -> Result>, Error> { let mut rng = rand::thread_rng(); // 1. Define a new session key let session_key = match mechanism { EncryptionMechanism::SeipdV1(sym) | EncryptionMechanism::SeipdV2(_, sym) => { sym.new_session_key(&mut rng) } }; let mut esk = vec![]; // 2. Encrypt (pub) the session key, to each PublicKey. for recipient in recipients { let pkesk = match mechanism { EncryptionMechanism::SeipdV1(sym) => { recipient.pkesk_from_session_key_v3(&mut rng, &session_key, sym)? } EncryptionMechanism::SeipdV2(_, _) => { recipient.pkesk_from_session_key_v6(&mut rng, &session_key)? } }; esk.push(Esk::PublicKeyEncryptedSessionKey(pkesk)); } // 3. Encrypt the session key to each symmetric password for pw in skesk_passwords { let pass = String::from_utf8_lossy(pw).to_string(); let skesk = match mechanism { EncryptionMechanism::SeipdV1(sym) => SymKeyEncryptedSessionKey::encrypt_v4( || pass, &session_key, StringToKey::new_default(&mut rng), sym, )?, EncryptionMechanism::SeipdV2(aead, sym) => { // "If much less memory is available, a uniformly safe option is Argon2id with // t=3 iterations, p=4 lanes, m=2^(16) (64 MiB of RAM), // 128-bit salt, and 256-bit tag size. This is the SECOND RECOMMENDED option." // FIXME: move these settings up to rPGP, as part of a convenience constructor? let s2k = StringToKey::new_argon2(&mut rng, 3, 4, 16); SymKeyEncryptedSessionKey::encrypt_v6( &mut rng, || pass, &session_key, s2k, sym, aead, )? } }; esk.push(Esk::SymKeyEncryptedSessionKey(skesk)); } // 4. Wrap the plaintext into a literal let mut data = vec![]; source.read_to_end(&mut data)?; let lit = LiteralData::from_bytes((&[]).into(), &data); // 5. Maybe sign the message. let hash_algo = hash_algo.unwrap_or_default(); let msg = if !signers.is_empty() { // FIXME: set Intended Recipient Fingerprint? (at least for v6) // "The OpenPGP Key fingerprint of the intended recipient primary key" // "When generating this subpacket in a version 6 signature, it SHOULD be marked as critical." let mut packets = vec![]; packets.push(Packet::from(lit.clone())); for signer in signers { let data_signers: Vec<_> = signer.signing_capable_component_keys().collect(); // FIXME: use all signing capable keys? if let Some(ds) = data_signers.first() { // FIXME: key decryption password! if let Message::Signed { one_pass_signature, signature, .. } = ds.sign_msg(Message::Literal(lit.clone()), String::default, hash_algo)? { if let Some(mut ops) = one_pass_signature { if packets.len() > 1 { // only the innermost signature should be marked "last", // so we mark all others as non-last. ops.last = 0; } packets.insert(0, Packet::from(ops)); } packets.push(Packet::from(signature)); } } else { return Err(Error::Message( "No signing capable component key found for signer".to_string(), )); } } if let Some(Ok(msg)) = Message::from_packets(packets.into_iter().map(Ok).peekable()).next() { msg } else { // This shouldn't happen, if we construct a reasonable "packets" return Err(Error::Message( "Failed to construct Message from packets".to_string(), )); } } else { // we're not signing Message::Literal(lit) }; // 6. Symmetrically Encrypt the message to the session key. let edata = match mechanism { EncryptionMechanism::SeipdV1(sym) => { Edata::SymEncryptedProtectedData(SymEncryptedProtectedData::encrypt_seipdv1( &mut rng, sym, &session_key, &msg.to_bytes()?, )?) } EncryptionMechanism::SeipdV2(aead, sym) => { Edata::SymEncryptedProtectedData(SymEncryptedProtectedData::encrypt_seipdv2( &mut rng, sym, aead, crate::policy::AEAD_CHUNK_SIZE, &session_key, &msg.to_bytes()?, )?) } }; // 7. Put together encrypted message let msg = Message::Encrypted { esk, edata }; // 8. Write encrypted message to sink match armor { true => msg.to_armored_writer(&mut sink, ArmorOptions::default())?, false => msg.to_writer(&mut sink)?, } // 9. Return session key Ok(session_key) } rpgpie-0.5.4/src/policy.rs000064400000000000000000000161001046102023000135520ustar 00000000000000// SPDX-FileCopyrightText: Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Policy decisions in rpgpie. //! //! These include: //! - Algorithm preferences. //! - Which algorithms do we accept, for specific operations, depending on context? //! //! rpgpie currently couples policy limitations to signature creation timestamps: //! Obsolete cryptographic mechanisms are considered valid for signatures that show a sufficiently //! old creation time. //! //! This approach represents a tradeoff: //! //! - The upside is that old OpenPGP artifacts can be cryptographically validated and used //! seamlessly. //! - The downside is that an attacker may trick users with weak, new (or newly modified) artifacts //! that show "old" signature creation timestamps. use chrono::{DateTime, Utc}; use pgp::crypto::aead::AeadAlgorithm; use pgp::crypto::hash::HashAlgorithm; use pgp::crypto::public_key::PublicKeyAlgorithm; use pgp::crypto::sym::SymmetricKeyAlgorithm; use pgp::types::{CompressionAlgorithm, EcdhPublicParams, PublicParams}; /// FIXME: where should this go? -> upstream to rpgp? #[derive(Debug, Copy, Clone, PartialEq)] pub enum Seipd { SED, SEIPD1, SEIPD2, } /// Preferred symmetric-key algorithms (in descending order of preference) pub const PREFERRED_SEIPD_MECHANISMS: &[Seipd] = &[Seipd::SEIPD2, Seipd::SEIPD1]; /// FIXME: what's a good default? pub const AEAD_CHUNK_SIZE: u8 = 8; // Chunk size: 16 KiB /// Preferred symmetric-key algorithms (in descending order of preference) pub const PREFERRED_SYMMETRIC_KEY_ALGORITHMS: &[SymmetricKeyAlgorithm] = &[ SymmetricKeyAlgorithm::AES256, SymmetricKeyAlgorithm::AES192, SymmetricKeyAlgorithm::AES128, SymmetricKeyAlgorithm::Twofish, SymmetricKeyAlgorithm::Camellia256, SymmetricKeyAlgorithm::Camellia192, SymmetricKeyAlgorithm::Camellia128, ]; pub const PREFERRED_AEAD_ALGORITHMS: &[(SymmetricKeyAlgorithm, AeadAlgorithm)] = &[ (SymmetricKeyAlgorithm::AES256, AeadAlgorithm::Ocb), (SymmetricKeyAlgorithm::AES192, AeadAlgorithm::Ocb), (SymmetricKeyAlgorithm::AES128, AeadAlgorithm::Ocb), (SymmetricKeyAlgorithm::AES256, AeadAlgorithm::Eax), (SymmetricKeyAlgorithm::AES192, AeadAlgorithm::Eax), (SymmetricKeyAlgorithm::AES128, AeadAlgorithm::Eax), (SymmetricKeyAlgorithm::AES256, AeadAlgorithm::Gcm), (SymmetricKeyAlgorithm::AES192, AeadAlgorithm::Gcm), (SymmetricKeyAlgorithm::AES128, AeadAlgorithm::Gcm), ]; /// Preferred hash algorithms (in descending order of preference) pub const PREFERRED_HASH_ALGORITHMS: &[HashAlgorithm] = &[ HashAlgorithm::SHA2_512, HashAlgorithm::SHA2_384, HashAlgorithm::SHA2_256, HashAlgorithm::SHA2_224, ]; /// Preferred compression algorithms (in descending order of preference) pub const PREFERRED_COMPRESSION_ALGORITHMS: &[CompressionAlgorithm] = &[CompressionAlgorithm::ZLIB]; /// How many layers deep are we willing to unpack into a message? pub const MAX_RECURSION: usize = 10; /// Cutoff time for MD5 hash acceptance: January 1, 2010 12:00:00 AM (GMT) const MD5_REJECT_AFTER: i64 = 1262304000; /// Cutoff time for SHA1 hash acceptance in data signatures: January 1, 2014 12:00:00 AM (GMT) /// (See https://csrc.nist.gov/projects/hash-functions) const SHA1_FOR_DATA_REJECT_AFTER: i64 = 1388534400; /// Cutoff time for SHA1 hash acceptance: February 1, 2023 12:00:00 AM (GMT) const SHA1_REJECT_AFTER: i64 = 1675209600; /// Cutoff time for RSA <2k bits acceptance: January 1, 2014 12:00:00 AM (GMT) /// (e.g. NIST guidance was to drop RSA 1k after 2013). const RSA_UNDER_2K_REJECT_AFTER: i64 = 1388534400; const RSA_UNDER_2K_CUTOFF_BIT_SIZE: usize = 2048; /// Cutoff time for validation and creation of DSA signatures: February 3, 2023 12:00:00 AM (GMT) /// See https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-5.pdf const DSA_REJECT_AFTER: i64 = 1675382400; /// Returns our policy about the general acceptability of a `PublicParams` at a reference time. pub(crate) fn acceptable_pk_algorithm(pp: &PublicParams, reference: &DateTime) -> bool { if let PublicParams::RSA { n, e: _ } = pp { let mut rsa_bits = n.len() * 8; // There may be leading zero bits in this count, compensate if let Some(first) = n.first() { rsa_bits -= first.leading_zeros() as usize; } // Reject RSA with key size under 2048 bit, after 31.12.2013 if rsa_bits < RSA_UNDER_2K_CUTOFF_BIT_SIZE && reference.timestamp() > RSA_UNDER_2K_REJECT_AFTER { return false; } } if let PublicParams::DSA { .. } = pp { // Reject DSA after 03.02.2023 if reference.timestamp() > DSA_REJECT_AFTER { return false; } } true } /// Returns our policy about a `HashAlgorithm` at a reference time. /// /// Note that we reject SHA-1 at different dates, depending on whether it is used for data /// signatures or structural signatures. pub(crate) fn acceptable_hash_algorithm( hash_algo: &HashAlgorithm, reference: &DateTime, data_signature: bool, ) -> bool { // #![allow(clippy::match_like_matches_macro)] match hash_algo { // Consider MD5 signatures invalid if they claim to have been made after the cutoff timestamp. HashAlgorithm::MD5 => { // only return "true" for legacy signatures, false for new ones MD5_REJECT_AFTER > reference.timestamp() } // Consider SHA1 signatures invalid if they claim to have been made after the cutoff timestamp. HashAlgorithm::SHA1 => { // only return "true" for legacy signatures, false for new ones match data_signature { true => SHA1_FOR_DATA_REJECT_AFTER > reference.timestamp(), false => SHA1_REJECT_AFTER > reference.timestamp(), } } HashAlgorithm::RIPEMD160 => { // FIXME: reject RIPEMD160 (RFC 9580) [starting when?] true } _ => true, } } /// Return our policy about acceptable encryption mechanisms. /// /// Note that encryption happens "now", by definition, so we're not allowing historical algorithms /// (which we may still allow for decryption of historical data). pub(crate) fn accept_for_encryption(pp: &PublicParams) -> bool { match pp { PublicParams::ECDH(EcdhPublicParams::Known { hash, alg_sym, .. }) => { if !PREFERRED_HASH_ALGORITHMS.contains(hash) { return false; } if !PREFERRED_SYMMETRIC_KEY_ALGORITHMS.contains(alg_sym) { return false; } true } _ => true, } } /// Return our policy about acceptable public key mechanisms for signatures. /// /// This policy applies validation of existing signatures based on their (claimed) creation time, /// and to issuing new signatures. pub(crate) fn accept_for_signatures(pk_alg: PublicKeyAlgorithm, reference: &DateTime) -> bool { if pk_alg == PublicKeyAlgorithm::DSA { // Only allow DSA signatures until the cutoff date (03.02.2023) reference.timestamp() <= DSA_REJECT_AFTER } else { true } } rpgpie-0.5.4/src/signature.rs000064400000000000000000000360101046102023000142560ustar 00000000000000// SPDX-FileCopyrightText: Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Handling of OpenPGP signatures. use std::io; use std::ops::Add; use chrono::{DateTime, Utc}; use pgp::packet::{Packet, RevocationCode, SignatureType, SubpacketData}; use pgp::ser::Serialize; use pgp::{Deserializable, Signature, StandaloneSignature}; use crate::policy::{accept_for_signatures, acceptable_hash_algorithm}; use crate::Error; fn is_revocation(sig: &Signature) -> bool { match sig.typ() { SignatureType::KeyRevocation | SignatureType::CertRevocation | SignatureType::SubkeyRevocation => { // this is a revocation true // unless explicitly marked as "soft" (by the reason code), we consider a revocation to be "hard" } _ => false, // this signature is not a revocation } } fn is_hard_revocation(sig: &Signature) -> bool { // FIXME: DRY with is_revocation match sig.typ() { SignatureType::KeyRevocation | SignatureType::CertRevocation | SignatureType::SubkeyRevocation => { // this is a revocation, but is it a hard revocation? match sig.revocation_reason_code() { Some(RevocationCode::KeyRetired) | Some(RevocationCode::CertUserIdInvalid) | Some(RevocationCode::KeySuperseded) => { return false; // these are soft revocation codes } _ => {} } true // unless explicitly marked as "soft" (by the reason code), we consider a revocation to be "hard" } _ => false, // this signature is not a revocation } } /// Reports if a signature meets basic acceptability criteria, including the cryptographic /// primitives it uses: /// /// - Rejects signatures that have no creation time stamp set in the hashed area /// - Rejects signatures that have a creation time stamp in the future /// - Rejects signatures that contain unknown critical subpackets /// /// - Rejects signatures based on weak cryptographic mechanisms, based on (claimed) signature creation time: /// /// md5 hashes are considered broken (effective January 1, 2010, based on the signature creation time). /// We consider a more recently dated md5-based signature equally broken as one with an invalid cryptographic hash digest. /// /// sha1 hashes for data signatures are considered broken effective January 1, 2014; /// sha1 hashes for other signature types are considered broken effective February 1, 2023. /// /// DSA signatures are considered unacceptable starting February 3, 2023. /// /// (Rejecting signatures that are technically correct, but use broken primitives is a defensive /// tradeoff: we consider legacy signatures that were made after a cutoff time as either attacks or /// mistakes.) pub(crate) fn signature_acceptable(sig: &Signature) -> bool { // FIXME: break this fn up, it does too many different things let Some(sig_creation_time) = sig.created() else { return false; }; // A signature with a future creation time is not currently valid let now: DateTime = chrono::offset::Utc::now(); if *sig_creation_time > now { return false; } // critical unknown subpackets or notations invalidate a signature for sp in &sig.config.hashed_subpackets { if sp.is_critical { if let SubpacketData::Notation(_notation) = &sp.data { // Unknown critical notation (by default) // FIXME: how would an application use critical notations? initialize rpgpie with a good-list? return false; } } } let data_sig = sig.typ() == SignatureType::Binary || sig.typ() == SignatureType::Text; // reject signature if our policy rejects the hash algorithm at signature creation time if !acceptable_hash_algorithm(&sig.config.hash_alg, sig_creation_time, data_sig) { return false; } // reject signature if our policy rejects the signature's public key algorithm at creation time if !accept_for_signatures(sig.config.pub_alg, sig_creation_time) { return false; } true } pub(crate) fn not_older_than(sig: &Signature, created: &DateTime) -> bool { sig.created() .is_some_and(|sig_created| sig_created >= created) } /// How long is `sig` valid? /// /// Some(dt): valid until `dt` /// None: unlimited validity /// /// NOTE: Also returns `None` if `sig` has no creation time. pub(crate) fn validity_end(sig: &Signature) -> Option> { let Some(sig_creation) = sig.created() else { // This is an error case, but we don't handle it here. // Callers are expected to handle it independently of this fn. return None; }; if let Some(sig_expiration) = sig.signature_expiration_time() { if sig_expiration.num_seconds() != 0 { Some(sig_creation.add(*sig_expiration)) } else { None } } else { None } } /// Check temporal validity of a signature pub(crate) fn is_signature_valid_at( sig: &Signature, key_creation: &DateTime, reference: &DateTime, ) -> bool { if let Some(creation) = sig.created() { // If the signature is created after the reference time, it is invalid at reference time if creation > reference { return false; } // If the signature expires before the reference time, the signature is invalid if let Some(sig_exp) = validity_end(sig) { if sig_exp < *reference { return false; } } // If the key expires and expiration is before the reference time, the signature is invalid if let Some(key_exp) = sig.key_expiration_time() { if key_exp.num_seconds() != 0 && (key_creation.add(*key_exp) < *reference) { return false; } } true } else { // A signature with unset creation time is invalid false } } /// Read a list of Signatures from an input pub fn load(source: &mut R) -> Result, Error> { let (iter, _header) = StandaloneSignature::from_reader_many(source)?; let mut sigs = vec![]; for res in iter { match res { Ok(sig) => sigs.push(sig.signature), Err(e) => return Err(Error::Message(format!("Bad data: {:?}", e))), } } Ok(sigs) } /// Write a list of Signatures to an output pub fn save( signatures: &[Signature], armored: bool, mut sink: &mut dyn io::Write, ) -> Result<(), Error> { if armored { let packets: Vec<_> = signatures.iter().map(|s| Packet::from(s.clone())).collect(); pgp::armor::write( &packets, pgp::armor::BlockType::Signature, &mut sink, None, true, )?; } else { for s in signatures { let p = Packet::from(s.clone()); p.to_writer(&mut sink)?; } } Ok(()) } pub(crate) struct SigStack<'inner> { hard: Vec<&'inner Signature>, soft: Vec<&'inner Signature>, regular: Vec<&'inner Signature>, #[allow(dead_code)] invalid: Vec<&'inner Signature>, #[allow(dead_code)] third_party: Vec<&'inner Signature>, } impl<'a> FromIterator<&'a Signature> for SigStack<'a> { fn from_iter>(iter: T) -> Self { let mut hard = vec![]; let mut soft = vec![]; let mut regular = vec![]; let mut invalid = vec![]; let third_party = vec![]; // FIXME: pre-process? // - Different Enum variants based on hard-revoked vs. not? // - Sort signatures by creation time? // - Calculate time spans for validity of the component? for sig in iter.into_iter() { if is_hard_revocation(sig) { hard.push(sig) } else if is_revocation(sig) { soft.push(sig) } else if signature_acceptable(sig) { regular.push(sig) } else { invalid.push(sig) // FIXME: handle third party sigs } } Self { hard, soft, regular, invalid, third_party, } } } impl<'a> SigStack<'a> { // FIXME: this is an odd hack, and kind of expensive to make. // This probably should be replaced with a more reasonable access mechanism. #[allow(dead_code)] pub fn all(&self) -> Vec<&Signature> { // TODO: always return two sets: "valid" and "invalid"? // // -> for hard revoked, all other sigs are "invalid" (because they are suspect and can't be // relied on, they are only of informational value) // // -> for non-hard-revoked, only "self.invalid" are considered "invalid"? (unsound // signatures of all kinds might be of interest to viewers, but shouldn't be relied on for // anything) let mut all: Vec<&Signature> = vec![]; self.hard.iter().for_each(|&s| all.push(s)); self.soft.iter().for_each(|&s| all.push(s)); self.regular.iter().for_each(|&s| all.push(s)); // FIXME: handle "invalid" sigs? // FIXME: handle third party sigs? // sort by creation, newest first // FIXME: missing creation times are not legal, do we want to handle them here? all.sort_by(|a, b| b.created().cmp(&a.created())); all } /// Get the latest active signature in this stack. pub fn active(&self) -> Option<&'a Signature> { self.active_at(None) } /// Get the currently active signature in this stack at a reference time. /// If the reference time is `None`, then the latest signature. pub(crate) fn active_at(&self, reference: Option<&DateTime>) -> Option<&'a Signature> { log::debug!("search active_at: {:?}", reference); // If there is any hard revocation, that is always active // (if there are multiple, picking a specific one is probably pointless) if let Some(&hard) = self.hard.first() { log::debug!( " found hard: {}", hard.created() .map(|dt| format!("{:?}", dt)) .unwrap_or("".to_string()) ); return Some(hard); } // If any soft revocations exist that are created <= `reference`, we return the latest of them. let mut latest_soft: Option<&Signature> = None; self.soft .iter() .filter(|&&s| { if let Some(reference) = reference { if let Some(sig_created) = s.created() { // only consider signatures that were created before the reference time sig_created <= reference } else { false // signature has no creation time, we ignore it } } else { true } }) .for_each(|s| { // replace "latest_soft" with any newer soft revocation if let Some(cur) = latest_soft { if let Some(sig_created) = s.created() { if let Some(cur_created) = cur.created() { if cur_created < sig_created { latest_soft = Some(s); } } } } else { latest_soft = Some(s); } }); if let Some(latest_soft) = latest_soft { log::debug!( " found soft: {}", latest_soft .created() .map(|dt| format!("{:?}", dt)) .unwrap_or("".to_string()) ); return Some(latest_soft); } // Otherwise we return the latest regular signature, if any let mut latest: Option<&Signature> = None; self.regular .iter() .filter(|s| { if let Some(reference) = reference { if let Some(expired) = validity_end(s) { // only consider signatures that are valid longer than the reference time expired > *reference } else { true } } else { true } }) .filter(|&&s| { if let Some(reference) = reference { if let Some(sig_created) = s.created() { // only consider signatures that were created before the reference time sig_created <= reference } else { false // signature has no creation time, we ignore it } } else { true } }) .for_each(|s| { // replace "latest" with any newer Signatures if let Some(cur) = latest { if let Some(sig_created) = s.created() { if let Some(cur_created) = cur.created() { if cur_created < sig_created { latest = Some(s); } } } } else { latest = Some(s); } }); if let Some(latest) = latest { log::debug!( " latest regular: {}", latest .created() .map(|dt| format!("{:?}", dt)) .unwrap_or("".to_string()) ); } latest } /// Does this signature stack contain a (non-revocation) Signature that is temporally valid at /// `reference`? pub fn has_valid_binding_at( &self, reference: &DateTime, key_creation: &DateTime, // FIXME: factor out? ) -> bool { // FIXME: we could search more efficiently in large stacks, if the stack were sorted (maybe later) self.regular .iter() .any(|s| is_signature_valid_at(s, key_creation, reference)) } pub(crate) fn revoked_at(&self, reference: &DateTime) -> bool { self.contains_hard_revocation() || self.soft_revoked_at(reference) } /// CAUTION: this fn doesn't consider hard revocations! fn soft_revoked_at(&self, reference: &DateTime) -> bool { log::debug!("soft_revoked_at {:?}", reference); if self.soft.iter().any(|&s| match s.created() { None => false, // we just ignore signatures without creation time Some(created) => created <= reference, }) { return true; // We consider this revocation active } false } fn contains_hard_revocation(&self) -> bool { !self.hard.is_empty() } } rpgpie-0.5.4/src/tsk.rs000064400000000000000000000357071046102023000130720ustar 00000000000000// SPDX-FileCopyrightText: Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 //! Wrapper types and logic around OpenPGP private keys, aka TSKs ("transferable secret keys"). use std::io; use chrono::{DateTime, SubsecRound, Utc}; use pgp::cleartext::CleartextSignedMessage; use pgp::crypto::hash::HashAlgorithm; use pgp::packet::{SignatureConfig, SignatureType, Subpacket, SubpacketData}; use pgp::ser::Serialize; use pgp::types::{Fingerprint, KeyVersion, PublicKeyTrait}; use pgp::{ ArmorOptions, KeyType, Message, PublicOrSecret, SecretKeyParamsBuilder, SignedPublicKey, SignedSecretKey, SubkeyParamsBuilder, }; use rand::thread_rng; use crate::certificate::{Certificate, Checked}; use crate::key::{ComponentKeySec, KeyFlagMatch, SignedComponentKey, SignedComponentKeySec}; use crate::policy::{ PREFERRED_AEAD_ALGORITHMS, PREFERRED_COMPRESSION_ALGORITHMS, PREFERRED_HASH_ALGORITHMS, PREFERRED_SYMMETRIC_KEY_ALGORITHMS, }; use crate::signature::SigStack; use crate::{ComponentKeyPub, Error}; /// A "transferable secret key (TSK)," also known as an "OpenPGP private/secret key." #[derive(Clone)] pub struct Tsk { ssk: SignedSecretKey, checked: Checked, // keep a "checked" version of the ssk, to find valid component keys on } impl TryFrom<&[u8]> for Tsk { type Error = Error; fn try_from(input: &[u8]) -> Result { use pgp::Deserializable; let (ssk, _) = SignedSecretKey::from_reader_single(input)?; Ok(ssk.into()) } } impl From for Tsk { fn from(ssk: SignedSecretKey) -> Self { let spk = SignedPublicKey::from(ssk.clone()); let checked = (&Certificate::from(spk)).into(); Self { ssk, checked } } } /// A signer that's based on a private component key that is marked as capable of producing data /// signatures. pub struct DataSigner { key: ComponentKeySec, } impl DataSigner { /// Takes a message as input, and produces a signed message pub fn sign_msg( &self, msg: Message, key_pw: F, hash_algo: HashAlgorithm, ) -> Result where F: FnOnce() -> String, { self.key.sign_msg(msg, key_pw, hash_algo) } /// Takes a message as input, and produces a cleartext signed message pub fn sign_csf(&self, body: &str, key_pw: F) -> Result where F: FnOnce() -> String, { match &self.key { // FIXME: DRY? ComponentKeySec::Primary(sk) => Ok(CleartextSignedMessage::sign( thread_rng(), body, &sk, key_pw, )?), ComponentKeySec::Subkey(ssk) => Ok(CleartextSignedMessage::sign( thread_rng(), body, &ssk, key_pw, )?), } } /// The fingerprint of the underlying component private key pub fn fingerprint(&self) -> Fingerprint { self.key.fingerprint() } } impl Tsk { /// Load a set of `Tsk`s from a source. /// /// The source data may be armored or binary. pub fn load(read: &mut R) -> Result, Error> { let (keys, _headers) = pgp::composed::signed_key::from_reader_many(read)?; let mut tsk = vec![]; for res in keys { match res { Ok(pos) => match pos { PublicOrSecret::Public(spk) => { eprintln!("Bad data {:?}", spk); } PublicOrSecret::Secret(ssk) => { tsk.push(ssk.into()); } }, Err(_) => eprintln!("Bad data {:?}", res), } } if tsk.is_empty() { Err(Error::Message("No TSKs found".to_string())) } else { Ok(tsk) } } /// Save this Tsk to a writer pub fn save(&self, armored: bool, sink: &mut dyn io::Write) -> Result<(), Error> { Self::save_all([self], armored, sink) } /// Save a set of Certificates to a writer pub fn save_all<'a>( tsks: impl IntoIterator, armored: bool, mut sink: &mut dyn io::Write, ) -> Result<(), Error> { if armored { let spks: Vec<_> = tsks.into_iter().map(|tsk| &tsk.ssk).collect(); let opts = ArmorOptions::default(); pgp::armor::write( &spks, pgp::armor::BlockType::PrivateKey, &mut sink, opts.headers, opts.include_checksum, )?; } else { for tsk in tsks { tsk.ssk.to_writer(&mut sink)?; } } Ok(()) } /// FIXME: remove this from the API again? pub fn key(&self) -> &SignedSecretKey { &self.ssk } pub fn generate_v4( key_type_primary: KeyType, key_type_encrypt: impl Into>, primary_user_id: Option, secondary_user_ids: Vec, key_password: Option<&str>, ) -> Result { let Some(primary_user_id) = primary_user_id else { return Err(Error::Rpgp(pgp::errors::Error::Message( "Generating UID-less keys not supported".to_string(), ))); }; let mut rng = thread_rng(); let subkeys = if let Some(key_type_encrypt) = key_type_encrypt.into() { vec![SubkeyParamsBuilder::default() .key_type(key_type_encrypt) .can_encrypt(true) .build()?] } else { vec![] }; let mut key_params = SecretKeyParamsBuilder::default(); key_params .key_type(key_type_primary) .can_certify(true) .can_sign(true) .primary_user_id(primary_user_id) .user_ids(secondary_user_ids) .preferred_symmetric_algorithms(PREFERRED_SYMMETRIC_KEY_ALGORITHMS.into()) .preferred_hash_algorithms(PREFERRED_HASH_ALGORITHMS.into()) .preferred_compression_algorithms(PREFERRED_COMPRESSION_ALGORITHMS.into()) .subkeys(subkeys); let secret_key_params = key_params.build()?; let secret_key = secret_key_params.generate(&mut rng)?; let passwd_fn = String::new; let mut signed_secret_key = secret_key.sign(&mut thread_rng(), passwd_fn)?; if let Some(key_password) = key_password { signed_secret_key .primary_key .set_password(&mut rng, || key_password.to_string())?; for sk in &mut signed_secret_key.secret_subkeys { sk.key.set_password(&mut rng, || key_password.to_string())?; } }; Ok(signed_secret_key.into()) } pub fn generate_v6( key_type_primary: KeyType, key_type_encrypt: KeyType, primary_user_id: Option, secondary_user_ids: Vec, key_password: Option<&str>, ) -> Result { let mut rng = thread_rng(); let (primary_user_id, uidless) = match primary_user_id { Some(uid) => (uid, false), None => (String::default(), true), }; let mut key_params = SecretKeyParamsBuilder::default(); key_params .version(KeyVersion::V6) .key_type(key_type_primary) .can_certify(true) .can_sign(true) .primary_user_id(primary_user_id) .user_ids(secondary_user_ids) .preferred_symmetric_algorithms(PREFERRED_SYMMETRIC_KEY_ALGORITHMS.into()) .preferred_hash_algorithms(PREFERRED_HASH_ALGORITHMS.into()) .preferred_compression_algorithms(PREFERRED_COMPRESSION_ALGORITHMS.into()) .preferred_aead_algorithms(PREFERRED_AEAD_ALGORITHMS.into()) .subkeys(vec![SubkeyParamsBuilder::default() .version(KeyVersion::V6) .key_type(key_type_encrypt) .can_encrypt(true) .build()?]); let secret_key_params = key_params.build()?; let secret_key = secret_key_params.generate(&mut rng)?; let passwd_fn = String::new; let mut signed_secret_key = secret_key.sign(&mut thread_rng(), passwd_fn)?; // drop userid if it's just a placeholder if uidless { signed_secret_key.details.users = vec![]; } // Add a DKS for v6 keys let hash_alg = HashAlgorithm::SHA2_512; // FIXME? let mut config = SignatureConfig::v6( &mut thread_rng(), SignatureType::Key, signed_secret_key.algorithm(), hash_alg, )?; config.hashed_subpackets = vec![ Subpacket::regular(SubpacketData::SignatureCreationTime( chrono::Utc::now().trunc_subsecs(0), )), Subpacket::regular(SubpacketData::KeyFlags([3].into())), // FIXME, set CS Subpacket::regular(SubpacketData::Features([0x01 | 0x08].into())), // FIXME, set SEIPDv2 + SEIPDv1 Subpacket::regular(SubpacketData::PreferredSymmetricAlgorithms( PREFERRED_SYMMETRIC_KEY_ALGORITHMS.into(), )), Subpacket::regular(SubpacketData::PreferredHashAlgorithms( PREFERRED_HASH_ALGORITHMS.into(), )), Subpacket::regular(SubpacketData::PreferredCompressionAlgorithms( PREFERRED_COMPRESSION_ALGORITHMS.into(), )), Subpacket::regular(SubpacketData::IssuerFingerprint( signed_secret_key.fingerprint(), )), ]; let passwd_fn = String::new; let dks = config.sign_key(&signed_secret_key, passwd_fn, &signed_secret_key)?; signed_secret_key.details.direct_signatures.push(dks); if let Some(key_password) = key_password { signed_secret_key .primary_key .set_password(&mut rng, || key_password.to_string())?; for sk in &mut signed_secret_key.secret_subkeys { sk.key.set_password(&mut rng, || key_password.to_string())?; } }; Ok(signed_secret_key.into()) } fn component_keys(&self) -> Vec { let x = SignedComponentKeySec::Primary(self.ssk.clone()); let mut v = vec![SignedComponentKey::Sec(x)]; self.ssk.secret_subkeys.iter().for_each(|sssk| { let dks = SigStack::from_iter(self.ssk.details.direct_signatures.iter()).active(); let x = SignedComponentKeySec::Subkey((sssk.clone(), dks.cloned())); v.push(SignedComponentKey::Sec(x)); }); v } fn get_matching_secret_key(&self, ckp: &ComponentKeyPub) -> Option { match ckp { ComponentKeyPub::Primary(pri) => { let sk = &self.ssk.primary_key; if pri.fingerprint() == sk.fingerprint() { // FIXME: compare more efficiently? Some(ComponentKeySec::Primary(sk.clone())) } else { None } } ComponentKeyPub::Subkey(sub) => { for sssk in &self.ssk.secret_subkeys { // FIXME: compare more efficiently? if sub.fingerprint() == sssk.fingerprint() { return Some(ComponentKeySec::Subkey(sssk.key.clone())); } } // Found no match None } } } /// Get list of all valid signing capable component keys /// (this fn is specific to *signing*, not validation: it uses "now" as its reference time and is thus stricter) pub fn signing_capable_component_keys(&self) -> impl Iterator + '_ { let now = Utc::now(); let sv = self.checked.valid_signing_capable_component_keys_at(&now); let mut ds = vec![]; for ckp in sv.iter().map(|sv| sv.as_componentkey()) { // Find associated secret key packet let key = self.get_matching_secret_key(ckp); if let Some(key) = key { ds.push(DataSigner { key }); } else { log::warn!( "signing_capable_component_keys: failed to find secret key packet for {:?}", ckp.fingerprint() ); } } ds.into_iter() } /// Get list of all decryption capable component keys /// (this fn is specific to *decryption*, not encryption: it is very lenient in allowing use of keys) pub fn decryption_capable_component_keys( &self, ) -> impl Iterator + '_ { let now: DateTime = chrono::offset::Utc::now(); // FIXME: filter out unknown notations self.component_keys() .into_iter() .filter(move |sck| sck.is_encryption_capable(&now)) } fn sec_components(&self) -> Vec { let x = SignedComponentKeySec::Primary(self.ssk.clone()); let mut v = vec![x]; self.ssk.secret_subkeys.iter().for_each(|sssk| { let dks = SigStack::from_iter(self.ssk.details.direct_signatures.iter()).active(); let x = SignedComponentKeySec::Subkey((sssk.clone(), dks.cloned())); v.push(x); }); v } /// Get all component secret keys that have a "signing capable" key flag. /// /// NOTE: this function doesn't cryptographically verify self-signatures, it's not safe to use /// on potentially attacker-controlled Tsks. pub fn signing_keys_sec(&self) -> Vec { // FIXME: filter out revoked subkeys self.sec_components() .iter() .filter(|x| x.has_key_flag(KeyFlagMatch::Sign)) .map(Into::into) .collect() } /// Get all component secret keys that have an "encryption capable" key flag. /// /// NOTE: this function doesn't cryptographically verify self-signatures, it's not safe to use /// on potentially attacker-controlled Tsks. pub fn decryption_keys_sec(&self) -> Vec { // This should actually not filter out revoked subkeys - we may still want to decrypt with them! self.sec_components() .iter() .filter(|x| x.has_key_flag(KeyFlagMatch::Enc)) .map(Into::into) .collect() } /// Get all component secret keys that have an "authentication capable" key flag. /// /// NOTE: this function doesn't cryptographically verify self-signatures, it's not safe to use /// on potentially attacker-controlled Tsks. pub fn auth_keys_sec(&self) -> Vec { // FIXME: filter out revoked subkeys self.sec_components() .iter() .filter(|x| x.has_key_flag(KeyFlagMatch::Auth)) .map(Into::into) .collect() } } rpgpie-0.5.4/src/util.rs000064400000000000000000000016231046102023000132340ustar 00000000000000// SPDX-FileCopyrightText: Heiko Schaefer // SPDX-License-Identifier: MIT OR Apache-2.0 use chrono::Duration; use pgp::types::{EcdhPublicParams, PublicParams}; /// Map expiration Duration to OpenPGP style seconds. /// /// If the duration is too large, this returns u32::MAX /// (such overflows can not happen for durations that come directly from an OpenPGP packet). pub(crate) fn duration_to_seconds(d: Option<&Duration>) -> Option { d.map(|d| u32::try_from(d.num_seconds()).unwrap_or(u32::MAX)) } pub(crate) fn algo_name(pp: &PublicParams) -> String { match pp { PublicParams::RSA { n, .. } => format!("RSA {}", n.len() * 8), PublicParams::ECDH(EcdhPublicParams::Known { curve, .. }) | PublicParams::EdDSALegacy { curve, .. } => curve.name().to_string(), PublicParams::ECDSA(_) => "ECDSA".to_string(), _ => "TODO".to_string(), } } rpgpie-0.5.4/tests/608B00ABE1DAA3501C5FF91AE58271326F9F4937000064400000000000000000000125721046102023000166650ustar 00000000000000-----BEGIN PGP PUBLIC KEY BLOCK----- Comment: 608B 00AB E1DA A350 1C5F F91A E582 7132 6F9F 4937 Comment: Lukas Pitschl xsJuBExQyJ0RCACKKHscgiZgAr018oLOuCSNQrf/p/cR9B8kd0KwwXTxQz6Np8LO hbF4yEvlull7OBD3zVK4GYycQvJvyFpf3syfDGvnnR54aeFPS5VM57M46U/FQlNZ RyMxhgPZtCFXReAClWzIF4qOXITe2MryKcz6wfZllciFWTq8jJ/KB19ZB5dYyvMz cXwmcDAnU3XpJWOSEqLi6SkbDZG3lfrV+bPAUPNJmfpxBWS2HwiCmeWiOPrtGppR sYy23uRF++IerK0Yq62RK1HeT/QHXp1pxcphAKHADL3gqRqzYukRY3xxd3GjyG5A 4OjvVuBR995aGk46DXqvJToTHD9WeoO8TNTPAQC/s2S7Bln/zbTBaShenSLzLg+l msG/2YzK9QHRphRVbwf/YNYySFjbhWZQ8finot5oVFEjNE4F5FeA87G4d0ReL5ru shN4GuVYXO09o6jl2rd+by42fESy9cmVuNXUuBCKPztp4bABUBGumzWQvXUKaxAF uHC6rOBDsQmQNpChevqjT+3fSYDvhlw7koxeyzm13tOUq9Epoa35XBi6i8YZ0OK2 qayeo7rJCsCHHVh+R2bC96GwzyL+N5kYyfG6ckwAa1RJyQBM+pcvZT/+Vcc8POGa cjMyK/ocJ23ZRnEytC4+cjWzbY/N04ptCDOky/mscoDNb7Bjt2WNgS/O6e5MZgmM ywm52+TEuHhY2N+OFh/Etvt0xAhooEm5QrvYI26VKQf/Z1mh3iKz0ELTvESUOux0 NbuUZUCgqWJj7+vztIWyz+yGP51nNKSmJXfJeiJ1CrEPTm1Y7q7u1LwFDZTsgxra 1ONR3PCI3GAnKO8q9bBW29YjiOnkLCNjXe7X+khXzC4kv/zCbwB2qWsfXAqFLvgy eiUxlrAQJ5UqZTNWV3w5frQByOQh0XXbCAMCZGTJNt+N9ijT7q3eY0z8g9Oina9Q h4c9Ko1NxoYwE1diAGRydSBVI3yge4bWpLMeZOsT4berei8bfVYk6aaeoITcP9qQ RMgq+EzVEA5xofLF1MOnpGQPSl1ZO/7uC51rRf56qzkzyK4ILJkFF3QZgUiBZoks e80jTHVrYXMgUGl0c2NobCA8bHVrZWxlQGdwZ3Rvb2xzLm9yZz7CmgQTEQoAQgIb AwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4ACGQEWIQRgiwCr4dqjUBxf+RrlgnEy b59JNwUCZl2+0wUJHc9dNQAKCRDlgnEyb59JN6JbAQC7/+c8B16I7oymimtJ5D5j 1g9MRbYQg6jigy/58UaefwEAo9xv6wT1w2cR9FAzL0hJj5dqAMt8v3ECMfe6/lQe IdnCmgQTEQoAQgIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4ACGQEWIQRgiwCr 4dqjUBxf+RrlgnEyb59JNwUCYqD/DwUJGhKdcgAKCRDlgnEyb59JNyvIAQCbEijk qOxUHsjiNB3XYNt0+pv+ZLuRIum7zMyVEprBewD+Ig4bCNzicTdn/+H/ADMGrOJh bZVfN0y86xCkIYHwfiDCmgQTEQoAQgIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgEC F4ACGQEWIQRgiwCr4dqjUBxf+RrlgnEyb59JNwUCYqD+7QUJGhKdUAAKCRDlgnEy b59JN7YVAP4rfeNDQeI7T6V4zcY3gCKr722FsciIafnDnQ4fpeQNOwD/ZArRmSAD K9Vqm5R2nxpMT+QJTS/8H+of/r/Dtb4C5VPCmgQTEQoAQgIbAwYLCQgHAwIGFQgC CQoLBBYCAwECHgECF4ACGQEWIQRgiwCr4dqjUBxf+RrlgnEyb59JNwUCXwM9+gUJ FnT4fQAKCRDlgnEyb59JNwzIAP4yLPMWDElCDpNS9CVM5TQld58bEUQ0LNZotqIp DSd4eQD/YbNJ+ZpinZZIj6hW0YMvZr2XMiCn1wWRkbUi/FqcIB/CmgQTEQoAKwIb AwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4ACGQEFAlmB1GgFCRLXSUsAIQkQ5YJx Mm+fSTcWIQRgiwCr4dqjUBxf+RrlgnEyb59JN7y7AP99puGOxrgIWbqT98CK3XEU yJQyw1OlUqKV8mdxXkv4LQD/ZS4P5VEduK/LtWU02p2Cd29K6e/TzX2wL/oNtchk ekTCmgQTEQoAKwIbAwYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4ACGQEFAlW77AIF CQ0wLWUAIQkQ5YJxMm+fSTcWIQRgiwCr4dqjUBxf+RrlgnEyb59JNwZrAPkBYWAB kSMnyinQEGBQHDAIPmVmXuUJWj+7MKjZZSbHZQEAn0KRY4hxwCwnVHjNEd2CtWc+ M+eE/qDjuBxAYVwT62jCmgQTEQoAKwIbAwUJCWYBgAYLCQgHAwIGFQgCCQoLBBYC AwECHgECF4AFAlSdl6gCGQEAIQkQ5YJxMm+fSTcWIQRgiwCr4dqjUBxf+RrlgnEy b59JN3JVAP46b/utGhjGBNc/eSgt5TzYRC+fKoXNj1UAExIYvmiuCAD9FE+qCrbd BptmBt+t0UcJYWdicR75ifSMl1sbp+qLfQDClwQTEQgAKAUCTsV9FgIbAwUJCWYB gAYLCQgHAwIGFQgCCQoLBBYCAwECHgECF4AAIQkQ5YJxMm+fSTcWIQRgiwCr4dqj UBxf+RrlgnEyb59JNwdhAQCs4S9kOPvNILEG/o9jzrfKv6H2zU7x+aF1jAqt4U2j PAD+OyZI6RNgf7B43T1mAoT96S7rtQCme1DIWhgtp52uWBrOwU0EWYHUIwEQAOyf TJKlGiOfxs1oJQAagw/p6cPKbqnpcesPkUSMHuKGMKI0pCleGxbItsmX3X8bB7iY DYDYSvpHW+fvhPwGmgi9VDBEdooudA+f7RnrW2DFlrBF+dUB4Z4knrPl0mne7fXh RGALErU4j+XxFqkK/JiMvqCv0bgu1GosAzRgz+LTv7+s3YRLgrXcvGPY80lCiLri PLU/XxTX4NPjKH5U+qsE2XYc1QctO2cuHIW+0a9t0pxBmF0/wrelf6xj8Oowc+Ne x/7aGRFtIDabCCCC+SBZcmVlQgyGAk/YxgtwhixKYnC8eAyawAWYkyovUsEj7ydc mqIlDp3jCf7C6hD/723LpxB29e7Lb46WennIqzUuFXtGrTxypCqVbnElE67uz4NQ SoSmu8KCO0pBqoUBhYth11ytqP81F/s1A70ny8bKTtOG8Ik1VC2CD/XlbFLBfH2C Gdt7lopOsHFkrRJLn0tJSlRxoEWZcCVO8rNMw1UefpzhA3xaaJa+WGelJdYyRIZf pZ50+ZjCQxWG/T9ImlKUB+rIyLFduz4yEkr/1gzFEqNCxlTgH5QsNjr59osc/C1B /pQLYilP7ybJHLB5ACq9emc2JGzLWsn4uwKfxt4QI1vsZxGRBGt7zAXQpwIw2jBQ IYgohm99dTniSA9NwFrkTtRikRWu6VluN/8xcUdLABEBAAHCfgQYEQoAJgIbDBYh BGCLAKvh2qNQHF/5GuWCcTJvn0k3BQJmxRkrBQkRFMvvAAoJEOWCcTJvn0k3nsUA /1dw3acTuV6owqVvZ6bOl9PorSAvkH4rWpbEQvmDvZehAQC1XM9Ze4g6od6a6Zls jQyy/Pb7/SFeFk7JIGxBBt2EVMJ+BBgRCgAmAhsMFiEEYIsAq+Hao1AcX/ka5YJx Mm+fSTcFAmTHhB8FCQ0xb28ACgkQ5YJxMm+fSTdt8gD/Xv+Zf/iJEB9W21IvpAwX 2msuN/sr2mTdfyMLOTnkoDkBAL2u6JQhBahECEmk5DluXPn9xPxXrtzU16iRIBu7 jY2Xwn4EGBEKACYCGwwWIQRgiwCr4dqjUBxf+RrlgnEyb59JNwUCYQkynAUJC0sW +QAKCRDlgnEyb59JNwcWAP9So2ZWWsqjF7+/gpArD5pnmJxQ/wkSNGQEcKfckJFz VAD9H9ttiKIV24o/8UCmK35x3sjCRzAH+sSRXMcEDCfIfFjCfgQYEQoADwUCWYHU IwIbDAUJB4YfgAAhCRDlgnEyb59JNxYhBGCLAKvh2qNQHF/5GuWCcTJvn0k379IA /2cV3dIFHDMX3Qeg8KIzblBwbudXdSa83wa1Us6iHrY/AQC4Le5uU/hzGH4bJ+N7 /eiZKrA5Sydnvj9f/foYfnbSJM7BTQRMUMidEAgAsFV863lQbFXkcUGcG2niefNO V4Qu0E7yIDddy96LzmwSc8QVQEcZ98paikocfPQw3cw4bH1FPReS9QRFBpPMmr31 cG/WpgQj1cr8y4EcQd5At0pFbQWOL9XO+FZDMgwzz/kCCYg5oPpax5Esi7FifOvu r/owQPR4O5hMaPwEYDdPOqDWjaAlEyRGKRgmc582DSjxsTBLnepFnxykPi6a694A p/fahK0CpbGMwu1933jKaMvIw+cZePvlUploCzG0rNXmiv0gXweieeVPWiwC5H+F 77WqHbH25W4RubJAIl4ucIIZJgudNAxHDyMfSEc+be98Xl05DWUskHXCrtH3RwAD Bwf7BBo/TCu8nN3X292HAqm9ECmxPn7wKYLO6AyI5nSj8/kkrWBg3QQ4JGuXG0NP Lq8/e2Zfyy9ruazpxRpTA/eLbtZyrGDULH02aCy6L7UTFOcA2vCsdUGxKPxEvunD GvW6XuDLQBpxhx7gQlrTiz2bDtyJLnWm/Q6Wr3DHER2uxmC56Pz2OAw2cLFMlu6X HCvRN7WMg7f60PyL0ETIbH56rEZrqiCL/+FGqBt75XNyVMo7B9C+YZpD51qM0ZZp cMAMfi7Wa4qJ8HKrt0XCV/9SuTxtw1ctPnClaKIhtXMMfHfuEm+h0dzO9jlbmavo +wKu9+bSq1zLKED/PILuHfI5fMJ+BBgRCAAPAhsMBQJVu+yFBQkNMC3oACEJEOWC cTJvn0k3FiEEYIsAq+Hao1AcX/ka5YJxMm+fSTfAwgD+JD38P4fDXFBNJPWy/roD xCnxLnRt50wsJ6NtHBYdpbgA/ApGI8JBfhY1+WC4+gXBIZ2POK8h/kqtY+p6VpKv o3GDwn4EGBEIAA8FAkxQyJ0CGwwFCQlmAYAAIQkQ5YJxMm+fSTcWIQRgiwCr4dqj UBxf+RrlgnEyb59JN8MCAP9ppv/xF7jJgAsVW8IqUCVdgffavk/K+nKOC9/ZiWAi DgEAtzzHbpPY/9DNuRukBqbBmZumDWrJ7UknV0tQZxi5d38= =H4kY -----END PGP PUBLIC KEY BLOCK-----